Reference
Client
App and Agent — constructors, properties, lifecycle, withConstraints, getAgent.
import { App, Agent } from "@alter-ai/alter-sdk";The SDK exposes two top-level client classes. They share most of the request surface but model two different principal kinds on the wire. Pick the right class for the workload:
App— application-side. Holds the application’s API key (alter_rk_…). Provisions agents, mints Connect sessions, calls provider APIs under any stored grant.Agent— workload-side. Holds an agent’s API key (alter_ak_…). Can only reach credentials delegated or directly bound to the agent.
Methods that do not apply to a principal kind are intentionally absent from that class — TypeScript flags the missing methods at compile time.
The application-side client.
import { App, CallerType } from "@alter-ai/alter-sdk";
const app = new App({ apiKey: process.env.ALTER_API_KEY!, timeout: 30_000, caller: "customer-portal", callerType: CallerType.SERVICE, userTokenGetter: () => getCurrentUserJwt(),});try { const page = await app.listGrants(); console.log(page.total);} finally { await app.close();}Constructor
Section titled “Constructor”new App(options: AppOptions)| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Application API key (alter_rk_…). Required. |
timeout | number | 30000 | HTTP timeout in milliseconds. |
caller | string | — | Caller identifier for audit attribution. |
callerType | CallerType | "agent" | "service" | SERVICE | Distinguishes backend services from AI agents. Only effective when caller is set. |
userTokenGetter | () => string | Promise<string> | — | Supplies the current end-user JWT for user-scoped identity resolution. A per-call userToken overrides it; headless system-grant resolution can omit it. |
logger | AlterLogger | global console | Pluggable logger. See AlterLogger. |
Throws AlterValueError when the constructor argument is not an object. Throws AlterSDKError if apiKey is missing or malformed, if userTokenGetter is present but not callable at runtime, or if the configured backend URL is invalid.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
actorId | string | null | Backend-resolved actor UUID. Populated after the first authenticated call. |
lastRetryInfo | RetryInfo | null | Token-refresh retry metadata from the most recent request() call. |
lastRateLimit | RateLimitSnapshot | null | Latest complete Alter rate-limit reading from a metered backend response. null means unknown, never unlimited. |
baseUrl | string | Backend the client is pinned to. Diagnostic accessor. |
agents | AgentsNamespace | Managed agent CRUD. |
keys | KeysNamespace | Scoped sub-key lifecycle. |
scopes | ScopesNamespace | Scope catalog discovery. |
oauthProviders | OAuthProvidersNamespace | OAuth provider catalog discovery. |
providerSpecs | ProviderSpecsNamespace | Provider operation discovery. |
spans | SpansNamespace | User-defined trace-span submission. |
Methods
Section titled “Methods”The App class exposes:
request(),proxyRequest()listGrants(),revokeGrant(),revokeDelegation(),createManagedSecretGrant(),mintGrant()createConnectSession(),createManagedSecretConnectSession(),pollConnectSession(),createConnectSessionForError(),connect()authenticate(),createAuthSession(),pollAuthSession(),verifyUserToken()getApprovalStatus(),awaitApproval()resolveIdentity(),assertIdentity()— the identity-export surfaceoauthProvidersnamespace —oauthProviders.list()(OAuth provider catalog)providerSpecsnamespace — operation discovery documented under Provider discoveryspansnamespace —spans.emit()withConstraints(),getAgent(),close()
The workload-side client.
import { Agent } from "@alter-ai/alter-sdk";
const agent = new Agent({ apiKey: process.env.AGENT_API_KEY!, caller: "research-agent", userTokenGetter: () => getCallingUserJwt(),});try { const info = await agent.me(); console.log(info.status);} finally { await agent.close();}Constructor
Section titled “Constructor”new Agent(options: AgentOptions)| Option | Type | Default | Description |
|---|---|---|---|
apiKey | string | — | Agent API key (alter_ak_…). Required. |
timeout | number | 30000 | HTTP timeout in milliseconds. |
caller | string | — | Caller identifier for audit attribution. |
userTokenGetter | () => string | Promise<string> | — | Resolves the calling end-user JWT. Used for delegation disambiguation when the agent holds delegations from multiple users on the same provider. |
logger | AlterLogger | global console | Pluggable logger. |
callerType is forced to AGENT on this class. Throws AlterValueError when the constructor argument is not an object or apiKey is not a non-empty string. Throws AlterSDKError if a non-empty apiKey is malformed, if userTokenGetter is present but not callable at runtime, or if the configured backend URL is invalid.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
actorId | string | null | Backend-resolved actor UUID. |
lastRetryInfo | RetryInfo | null | Token-refresh retry metadata. |
lastRateLimit | RateLimitSnapshot | null | Latest complete Alter rate-limit reading from a metered backend response. null means unknown, never unlimited. |
baseUrl | string | Backend the client is pinned to. |
keys | KeysNamespace | Scoped sub-key lifecycle. |
scopes | ScopesNamespace | Scope catalog discovery. |
oauthProviders | OAuthProvidersNamespace | OAuth provider catalog discovery. |
providerSpecs | ProviderSpecsNamespace | Provider operation discovery. |
spans | SpansNamespace | User-defined trace-span submission. |
Agent has no agents namespace — agent CRUD is application-scoped.
Methods
Section titled “Methods”The Agent class exposes:
me()— return the calling agent’s own record.request(),proxyRequest()trace(),withConstraints()listGrants(),revokeDelegation(),delegate()oauthProviders.list()— OAuth provider catalog.createConnectSession(),pollConnectSession(),createConnectSessionForError(),connect()providerSpecsnamespace — operation discovery documented under Provider discoveryspansnamespace —spans.emit()getApprovalStatus(),awaitApproval()resolveIdentity(),assertIdentity()— the identity-export surface; the agent shape additionally accepts the consent-edgeappUserIdshortcutclose()
resolveIdentity
Section titled “resolveIdentity”Resolve the canonical identity set for a request.
// Appasync resolveIdentity(options?: { userToken?: string; includeProfile?: boolean;}): Promise<IdentityContext>
// Agentasync resolveIdentity(options?: { userToken?: string; appUserId?: string; includeProfile?: boolean;}): Promise<IdentityContext>App falls back to its configured userTokenGetter when userToken is omitted. Agent requires an explicit userToken or appUserId to resolve an end user; its constructor getter is not forwarded by this identity method. The agent-only appUserId shortcut succeeds only when an existing consent edge authorizes that agent for the user. With no user selector, the method returns the headless app or agent identity.
includeProfile defaults to false; enable it only when email and displayName are needed. Returns IdentityContext. Invalid options throw AlterValueError; identity or backend failures use the standard typed error hierarchy.
assertIdentity
Section titled “assertIdentity”Mint a short-lived, identity-only assertion for a downstream audience.
// Appasync assertIdentity(options: { audience: string; userToken?: string; ttlSeconds?: number; includeProfile?: boolean;}): Promise<IdentityAssertion>
// Agentasync assertIdentity(options: { audience: string; userToken?: string; appUserId?: string; ttlSeconds?: number; includeProfile?: boolean;}): Promise<IdentityAssertion>audience is a required URI with a scheme and at most 512 characters. ttlSeconds defaults to 120 and must be an integer from 10 through 300. User selection follows resolveIdentity(), including the agent-only appUserId shortcut and the App getter fallback. Returns IdentityAssertion.
verifyUserToken
Section titled “verifyUserToken”App only. Validate an incoming end-user bearer token and return its verified subject.
async verifyUserToken(token: string): Promise<string | null>This method is fail-closed: it returns null for a blank or untrusted token, transport failure, malformed response, unexpected status, or a closed client. Treat every null result as an authentication rejection.
withConstraints
Section titled “withConstraints”Return a constrained sibling client. Macaroon-style attenuation that can only narrow access, never broaden it. Two attenuations are available, and at least one of scopes / rule must be supplied:
scopesnarrows the scope set. The argument must be a subset of the parent client’s effective scope set; the backend enforces the narrowing on every authenticated call.ruleattaches an optional request rule, evaluated server-side, that can only further restrict each request the returned client makes — it can never widen access. Supported shapes includejson_matchdeny rules,require_approvalHITL rules, andcontent_matchrules built withcontentMatchRule(...). A raw-token retrieval carries no request method/URL, somethod-keyed conditions are treated as satisfied there (a raw token can issue any method) — scope method restrictions through the proxy surface, which carries the request method. If a request is frozen for human approval (HITL 202), the per-request rule is not re-evaluated on the deferred post-approval execution — it cannot widen access (the rule did not match the frozen request, and the grant plus every stored policy rule still bound the execution), but prefer a stored rule when the constraint must hold at execute time.
withConstraints(options: { scopes?: readonly string[]; rule?: RequestRule }): AppwithConstraints(options: { scopes?: readonly string[]; rule?: RequestRule }): AgentRequestRule is shaped:
interface RequestRule { ruleType: string; ruleBody: Record<string, unknown>;}For the json_match rule type, ruleBody is { when: { <attr>: string | string[] }, effect: "deny" }, where <attr> is one of method, provider_id, app_id, agent_id, api_key_id, environment, client_ip, resource_kind, and effect is always "deny". For operation-aware policies, prefer contentMatchRule(...); it emits the snake_case content_match body, validates parameter-condition shape, requires redactFields iff effect: "redact", and requires maxSessionAgeSeconds iff effect: "step_up" as an integer in [1, 86400]. The ruleBody field names are the rule grammar’s own — they are not camelCased.
Both the parent and constrained sibling share the same internal transport clients, signing state, and background-task sets. Closing the parent also makes the sibling unusable; closing the sibling alone leaves the parent untouched.
withConstraints() cannot be chained — calling it on an already-constrained client throws AlterValueError. It also throws AlterValueError if neither scopes nor rule is supplied. Calling it on a closed client throws AlterSDKError.
import { contentMatchRule } from "@alter-ai/alter-sdk";
const readOnly = app.withConstraints({ scopes: ["grants:read"] });const page = await readOnly.listGrants();
// Attach a json_match deny rule to every request from this constrained client:const restricted = app.withConstraints({ rule: { ruleType: "json_match", ruleBody: { when: { method: "POST" }, effect: "deny" }, },});
// Attach an operation-aware content rule. The helper validates that// maxSessionAgeSeconds is an integer in [1, 86400].const freshSession = app.withConstraints({ rule: contentMatchRule({ operations: ["resource.create"], effect: "step_up", maxSessionAgeSeconds: 300, }),});getAgent
Section titled “getAgent”Return an Agent instance that impersonates a named managed agent for audit attribution.
getAgent(agentId: string): Agent| Parameter | Type | Description |
|---|---|---|
agentId | string | UUID of the managed agent to impersonate. |
Throws AlterValueError if agentId is not a valid UUID, or if the App has been closed.
const research = app.getAgent("d3f4e5a6-7b8c-9d0e-1f2a-3b4c5d6e7f80");try { await research.request("GET", url, { provider: "<provider-id>", userToken, });} finally { await research.close();}Complete any pending work, then mark the client closed so subsequent operations fail deterministically.
async close(): Promise<void>async [Symbol.asyncDispose](): Promise<void>Both App and Agent expose close() and Symbol.asyncDispose. The SDK does not auto-close on process exit; call it explicitly (typically from a try/finally) or use await using in runtimes that support explicit resource management:
const app = new App({ apiKey });try { // ...} finally { await app.close();}
await using scopedApp = new App({ apiKey });// scopedApp.close() runs when the scope exits.close() is idempotent. Closing a root client drains the shared background-task set; closing a constrained sibling only marks that sibling closed, so close the root before process shutdown. Subsequent calls on a closed client throw AlterSDKError.