@alter-ai/alter-sdk

中文文档
0.31.0发布于 2026/11/04MITTypeScriptESM + CJS近 30 天下载 2367

Official TypeScript SDK for Alter Vault — privileged access for AI agents: vaulted credentials, default-deny policy, approval policies, and an audit trail for every call made through Alter

原始使用文档
文档目录11 节
  1. Install
  2. Quick example
  3. Provider calls
  4. Policy rule helpers
  5. Recovering from missing-grant errors
  6. Managed-secret Connect: a user's own key, or a delegation
  7. Onward delegation (agent to agent)
  8. OpenTelemetry trace propagation
  9. Documentation
  10. License
  11. Support

Alter SDK for TypeScript / Node.js

Official TypeScript SDK for Alter Vault — privileged access management for AI agents: vaulted credentials, default-deny policy down to the parameter, approval policies for risky calls, and an audit trail for every call made through Alter.

Provider calls execute through Alter’s proxy. Credentials stay in Alter, where policy and approval requirements are enforced.

Install

npm install @alter-ai/alter-sdk

Runs on Node.js 20+ and is intended for server-side environments. For browser-side OAuth popup flows, see @alter-ai/connect.

Quick example

Make an authenticated API call — no token ever touches application code.

import { App, HttpMethod } from "@alter-ai/alter-sdk";

const app = new App({ apiKey: "<api-key>" });
try {
  let response = await app.proxyRequest({ method: HttpMethod.POST, url: "https://api.example.com/resource", grantId: "<grant-id>",
      json: { example: "payload" }, });
  if ("status" in response) {
    for (const gate of response.gates) {
      console.log("Approval required:", gate.approvalUrl);
    }
    response = await app.awaitApproval(response.approvalId);
  }
  console.log(response.statusCode, await response.bodyJson());
} finally {
  await app.close();
}

For a full walkthrough — sign-up, key minting, OAuth — see the Quickstart.

Provider calls

Use app.proxy_request(...) (Python) / app.proxyRequest({...}) (TypeScript) for provider calls. The backend holds the credential, executes the request, and returns the provider result.

  • app.proxyUploadRequest(...) — proxy mode for large request bodies: the body is declared by size and SHA-256, every policy runs, and it is then sent once without Alter holding it. app.deliverApprovedUpload(...) sends one that needed approval.
  • app.proxyStreamRequest(...) — proxy mode for downloads and Server-Sent Events: provider bytes stream through with backpressure. Binary request bodies are up to 5 MiB. Neither can wait for approval.

Proxy mode returns an ApprovalResult with statusCode, response headers/body, bodyTruncated, and durationMs (provider round-trip milliseconds; null for results created by older workers). Managed-secret secondary header/query injections and AWS SigV4—including query parameters, raw/JSON bodies, and temporary session tokens—are injected by the backend.

See provider calls for the execution boundary.

Policy rule helpers

contentMatchRule(...) builds operation-aware request rules for withConstraints({ rule }) with local validation before the first API call. It accepts operation ids (a cataloged provider's attested ids, or the ids a custom integration's developer mapped) and/or operation families, optional parameter conditions, and one of three effects: deny, redact, or step_up. A custom integration's operations project only the request's query-string parameters, compared as text, so any other parameter condition (a body, header or path parameter, or a numeric comparison) denies the matching request; a redact rule that names only custom operations is refused, and a redaction that reaches one through a family denies the request. Redaction strips named outbound request-body fields; step-up requires maxSessionAgeSeconds as an integer from 1 through 86400. Parameter conditions compare with equals, the glob operators any_in / not_subset_of (where *, ? and [...] are wildcards), their literal twins in / not_all_in (the same array compared literally, case-insensitively — in fires when any member is listed, not_all_in when not all members are; use them for literal statements, ids and channels), the presence operator exists (a boolean: true fires when the request carries the parameter, false when it does not), the numeric gt / gte / lt / lte, and outside_windows.

operationAllowlistRule(...) is the positive counterpart: it builds an operation_allowlist request rule under which only the listed operation ids and/or operation families may run. Every other classified operation is denied, and so is any request the catalog cannot classify. Several allowlists on one chain intersect, so a per-request allowlist can only narrow access. At least one of operations / families is required.

import { operationAllowlistRule } from "@alter-ai/alter-sdk";

const readOnly = app.withConstraints({
  rule: operationAllowlistRule({ operations: ["query", "generalledger/query_a_report"] }),
});

Recovering from missing-grant errors

When a request fails because the user hasn't authorized the provider yet, the SDK exposes recovery context on the typed error so you can drive a re-consent flow without re-deriving anything from the call site:

import { Agent, HttpMethod, NoDelegatedGrantError } from "@alter-ai/alter-sdk";

const agent = new Agent({ apiKey: "<agent-api-key>" });
try {
  try {
    await agent.proxyRequest({ method: HttpMethod.GET, url: "https://api.example.com/v1/resources", provider: "<provider-id>",
      userToken: jwt, });
  } catch (e) {
    if (e instanceof NoDelegatedGrantError) {
      const session = await agent.createConnectSessionForError(e, {
        allowedOrigin: "https://app.example.com",
      });
      // Surface session.connectUrl to the user, then wait for consent.
      const results = await agent.pollConnectSession(session.sessionToken);
      let response = await agent.proxyRequest({ method: HttpMethod.GET, url: "https://api.example.com/v1/resources", grantId: results[0].grantId, userToken: jwt });
      if ("status" in response) {
        for (const gate of response.gates) {
          console.log("Approval required:", gate.approvalUrl);
        }
        response = await agent.awaitApproval(response.approvalId);
      }
      console.log(response.statusCode);
    } else {
      // Re-throw every other error class so network errors and
      // programming bugs aren't silently swallowed.
      throw e;
    }
  }
} finally {
  await agent.close();
}

createConnectSessionForError and pollConnectSession are available on both App and Agent so the catch block can recover from whichever client raised. For CredentialRevokedError, the helper binds the session to the error's exact grant. A successful repair reports operation === "reauth" and the same grantId — as does any completion that returns an already-connected grant: "reauth" means an existing grant came back with its id preserved, not that it was necessarily broken. Naming a target is an optimization, not a requirement: an error carrying no grant id (an older backend), a grant this caller cannot address (pass userToken to reach an end user's connection), or a grant that is no longer active all fall back to an ordinary session that reports operation === "creation" with a new grantId. Read the id off the result.

If Alter observes the session pending but receives no callback before the local deadline or server expiry, polling throws ConnectTimeoutError. Inspect error.details.reason (poll_deadline_elapsed or session_expired). A provider may keep an authorization error on its own page and never redirect, so the exception lists that alongside browser closure or an abandoned flow rather than claiming an exact provider error Alter did not receive.

If a fresh provider authorization cannot start, pollConnectSession throws ConnectConfigError. This includes the typed provider_configuration_unavailable and shared_dev_credential_unavailable terminals; show the error's safe message to the caller and ask the app administrator to correct provider availability before creating a new session. Existing grants may remain usable.

ConnectConfigError also carries a third terminal, managed_oauth_scope_approval_required, thrown by createConnectSession (HTTP 409) as well as by pollConnectSession: the scopes the application requests exceed what Alter's managed OAuth client is approved for. The app administrator cannot widen it — contact Alter to have the scopes approved, or narrow the application's configured scopes to the approved set, and only then create a new session. The response is retryable: false, so every fresh session fails identically until the approval changes. On the createConnectSession path the error's details is the full response body, so details.details.unapproved_scopes names exactly which scopes need approving.

A fourth terminal, agent_allowlist_exceeded (HTTP 422), refuses a Connect session that delegates to an agent (agent, or an agent's own key with a user token) when the agent's capability allowlist cannot use what the user would grant. createConnectSession (and connect(), which creates the session first) throws it before the user is sent to the provider, with the check at details.details.detail_reason (provider_not_allowlisted, scopes_not_allowlisted or grant_scopes_malformed) and every refused provider in details.details.uncovered_providers. pollConnectSession throws it when the allowlist was narrowed before the user finished; that failure carries only details.error_code. An administrator widens the agent's allowlist, or the application narrows the scopes it requests or passes a covering scopeConstraint, before a new session. A provider the session names in neither allowedProviders nor requiredScopes is dropped instead when the agent cannot use it, and listed in ConnectSession.droppedProviders (providerId, check). Onward delegate() throws BackendError for the same code.

NoDelegatedGrantError and GrantNotFoundError carry providerId / agentId / appUserId recovery context when the original lookup was identity-mode; CredentialRevokedError carries providerId / appUserId. See the error reference for the full surface.

On the agent path, a grant_not_found for an explicit grantId surfaces as AgentDelegationMissingError — a subclass of GrantNotFoundError, so a catch on the base class still fires. It means the grant is not delegated to this agent, or a user/base grant id was passed where the agent's own delegation id is required (get it from agent.listGrants). Recover by delegating the agent through Connect (agent.createConnectSession), or resolve by provider instead of passing a grantId.

Managed-secret Connect: a user's own key, or a delegation

One Connect session names a managed-secret provider (provider, the provider's slug) and the user. The slug is unique per app: several providers can share a template or display name, so the slug, not the template name, selects one. When the user holds no grant on the provider, the hosted page asks for their own API key; when they do, it lets them approve its use. With agent, the user's access is also delegated to that agent:

import { App } from "@alter-ai/alter-sdk";

const app = new App({ apiKey: "<app-api-key>" });

const session = await app.createManagedSecretConnectSession({
  provider: "<provider-slug>",
  userToken: "<user-jwt>",
  agent: "<agent-id-or-name>", // optional: omit for the application's own use
  requestedGrant: { label: "<label>" }, // optional: names the key's grant
});
// Open session.connectUrl in the user's browser (a popup, the system browser
// or the @alter-ai/connect widget). session.path is "key_entry" when the user
// will enter a key, "fast_path" when they already hold a grant.

The user signs in, enters the key (or picks the grant), may set their own usage limits, and confirms. The key is the user's own grant: the application can revoke it but not edit it. Use it like an OAuth grant, through the grant: by grantId, or by provider with the user's token, which resolves one grant. Several matching grants throw AmbiguousGrantError (HTTP 409 ambiguous_grant); narrow the call with label or account, or pass grantId. Once the user has set a limit, use proxyRequest() (request() throws RestrictedGrantRequiresProxyError). repairGrantId lets the user replace their key in place (same grantId).

An agent asks for itself with agent.createManagedSecretConnectSession, which takes no target:

import { Agent } from "@alter-ai/alter-sdk";

const agent = new Agent({ apiKey: "<agent-api-key>" });

const session = await agent.createManagedSecretConnectSession({
  provider: "<provider-slug>",
  userToken: "<user-jwt>",
  delegable: true, // optional: ask that this agent may share the access onward
});
// On approval the page delivers the delegation id this agent passes as
// grantId to agent.proxyRequest() (a delegation is proxy-only).

The user decides whether the delegation may be shared onward: for an agent's own request the page's checkbox starts off for a new delegation, so the user opts the agent in; for an existing delegation it starts from the stored setting. session.delegableRequested echoes the request.

ConnectConfigError means an administrator or the application must change something first; error.details.error names it (secret_not_found, secret_template_mismatch, idp_signin_not_configured, binding_hosts_required, provider_contract_repair_required, no_eligible_grants). Against an Alter backend that predates an option the call used, the SDK throws ServerFeatureUnsupportedError. templateSlug and delegatedAgentId are deprecated aliases of provider and agent: they still work and emit a DeprecationWarning.

Onward delegation (agent to agent)

An agent that holds a grant can hand a scoped-down copy to another agent — without asking the credential owner to consent again. Use agent.delegate() to mint a child grant for the second agent:

import { Agent, GrantNotDelegableError } from "@alter-ai/alter-sdk";

const agent = new Agent({ apiKey: "<agent-api-key>" });

try {
  const result = await agent.delegate(
    "<grant-id>",                // a grant this agent already holds
    "<other-agent-id>",          // the agent that should receive access
    {
      scopeConstraint: ["resource:write"], // optional: narrow to fewer scopes
      ttlSeconds: 3600,                // optional: shorten the lifetime
    },
  );
  console.log(result.grantId, result.depth, result.expiresAt);
} catch (e) {
  if (e instanceof GrantNotDelegableError) {
    // e.detailReason says why: "holder_not_delegable" (the user did not allow
    // onward sharing), "ceiling_off" (the operator ceiling is off), ...
    console.warn("cannot share onward:", e.detailReason);
  } else {
    throw e;
  }
} finally {
  await agent.close();
}

The held grant must have been created as delegable: the user allowed onward sharing when they approved it (Connect with delegable: true asks them; only their choice is stored), and the credential's operator ceiling — a managed-secret provider's delegation policy or the OAuth provider's agent sharing setting — must still allow it on every call. The child can only narrow — fewer scopes, a shorter lifetime — never widen. A grant narrowed with scopeConstraint is proxy-only: call it with agent.proxyRequest(...). Onward delegation is opt-in at every hop: pass delegable: true only when the recipient should be allowed to delegate further. Omit delegable to keep the existing delegation's setting — a new delegation is then a leaf, and delegating again to the same agent leaves its current setting unchanged; pass delegable: false to turn onward delegation off.

delegate() also takes rules, tags and label: the rules are created with the child and can only narrow, tags start from the parent's, and the recipient selects the child by label on proxyRequest() and listGrants(). The result's approverWarnings names any require_approval rule whose approvers were not verified against the directory. An application tags an existing grant with app.updateGrantTags(grantId, tags), which replaces the whole map, and tags a new one with tags on createConnectSession(), createManagedSecretConnectSession() or mintGrant().

agent.listGrants() returns each grant with parentGrantId (the grant it was minted under, or null for a root) and depth (its distance from the root), so the full delegation chain can be reconstructed from the flat list.

OpenTelemetry trace propagation

When the application runs an OpenTelemetry SDK, Alter requests automatically carry the active span's W3C traceparent, so the audit trail — and any spans the organization streams to its own OTLP collector — join the application's traces. No configuration is required: @opentelemetry/api is an optional peer dependency that the SDK never installs or requires — when the application has it, the SDK uses it (including under pnpm's isolated mode and Yarn PnP); without it (or without an active span) the SDK behaves exactly as before.

import { trace } from "@opentelemetry/api";
import { App, HttpMethod } from "@alter-ai/alter-sdk";

const tracer = trace.getTracer("the-application");

const tracedApp = new App({ apiKey: "<api-key>" });
try {
  await tracer.startActiveSpan("handle-user-request", async (span) => {
    try {
      // This call's audit events share the surrounding trace's ids.
      let response = await tracedApp.proxyRequest({ method: HttpMethod.GET, url: "https://api.example.com/v1/resources", grantId: "<grant-id>" });
      if ("status" in response) {
        for (const gate of response.gates) {
          console.log("Approval required:", gate.approvalUrl);
        }
        response = await tracedApp.awaitApproval(response.approvalId);
      }
      console.log(response.statusCode);
    } finally {
      span.end();
    }
  });
} finally {
  await tracedApp.close();
}

Documentation

Full docs are at https://docs.alterauth.com.

TopicPage
Getting started end-to-endQuickstart
The mental modelHow Alter works
Calling APIs on behalf of users (OAuth + JWT)Guide
Identity provider setupGuide
Provisioning backend secretsGuide
Scoped credentials for AI agentsGuide
Embedding the Connect widgetGuide
Human-in-the-loop approvalsGuide
OpenTelemetry trace propagationCalling APIs
Provider calls through AlterConcept
Per-method API referenceTypeScript SDK reference
Browser popup SDK@alter-ai/connect
ErrorsError reference

License

MIT. See LICENSE.

Support

Email [email protected] or open an issue at https://github.com/alter-ai/alter-vault.

版本与发布记录

@alter-ai/alter-sdk 共发布 35 个版本,首次发布于 2026/02/19;过去 12 个月发布了 35 个版本。

版本发布日期距今
0.31.0latest2026/11/044 天前
0.30.22026/10/2910 天前
0.30.12026/10/2810 天前
0.29.02026/10/2018 天前
0.28.02026/10/1523 天前
0.27.02026/09/291 个月前

关于 @alter-ai/alter-sdk 的常见问题

@alter-ai/alter-sdk 是什么?

Official TypeScript SDK for Alter Vault — privileged access for AI agents: vaulted credentials, default-deny policy, approval policies, and an audit trail for every call made through Alter

如何安装 @alter-ai/alter-sdk?

在项目目录运行 npm install @alter-ai/alter-sdk,或使用 pnpm add / yarn add / bun add。

@alter-ai/alter-sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@alter-ai/alter-sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@alter-ai/alter-sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@alter-ai/alter-sdk 可以商用吗?

@alter-ai/alter-sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@alter-ai/alter-sdk 的下载量有多少?

根据 npm 官方下载统计,@alter-ai/alter-sdk 在 2026/10/06 – 2026/11/04 这 30 天内被下载 2,367 次,日均约 79 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@alter-ai/alter-sdk 还在维护吗?

最新版本 0.31.0 发布于 2026/11/04(4 天前);至今共发布 35 个版本,其中过去 12 个月发布了 35 个;首次发布于 2026/02/19。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。