TypeScript SDK
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(),});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> | — | Resolves the current end-user JWT. Required for identity-mode request(). |
logger | AlterLogger | global console | Pluggable logger. See AlterLogger. |
Throws AlterValueError if apiKey is missing or empty.
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. |
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. |
Methods
Section titled “Methods”The App class exposes:
request(),proxyRequest()listGrants(),revokeGrant(),revokeDelegation(),createManagedSecretGrant()createConnectSession(),createManagedSecretConnectSession(),pollConnectSession(),createConnectSessionForError(),connect()authenticate(),createAuthSession(),pollAuthSession(),verifyUserToken()getApprovalStatus(),awaitApproval()resolveIdentity(),assertIdentity()— the identity-export surface (see Propagate identity into memory layers and Identity types)oauthProvidersnamespace —oauthProviders.list()(OAuth provider catalog)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(),});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 if apiKey is missing or empty.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
actorId | string | null | Backend-resolved actor UUID. |
lastRetryInfo | RetryInfo | null | Token-refresh retry metadata. |
baseUrl | string | Backend the client is pinned to. |
keys | KeysNamespace | Scoped sub-key lifecycle. |
scopes | ScopesNamespace | Scope catalog discovery. |
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()oauthProviders.list()— OAuth provider catalog.createConnectSession(),connect()getApprovalStatus(),awaitApproval()resolveIdentity(),assertIdentity()— the identity-export surface; the agent shape additionally accepts the consent-edgeappUserIdshortcut (see Propagate identity into memory layers)close()
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 one-off rule, evaluated server-side, that can only further restrict each request the returned client makes — it can never widen access. It is either a deny rule ({ ruleType: "json_match", ruleBody: { when: {...}, effect: "deny" } }) or a require-approval (HITL) rule ({ ruleType: "require_approval", ruleBody: { effect: "require_approval", approval: { approvers?: [...], channels?: ["email"], ... }, when?: {...} } }), which holds a matching request for human approval instead of denying it (channels: []sends no email). 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. Endpoints cannot be scoped by a rule (the URL is not a matchable attribute). 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". The ruleBody field names are the rule grammar’s own — they are not camelCased.
Both the parent and the constrained sibling share the same underlying HTTP pool. 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.
const readOnly = app.withConstraints({ scopes: ["grants:read"] });const page = await readOnly.listGrants();
// Attach a one-off deny rule:const restricted = app.withConstraints({ rule: { ruleType: "json_match", ruleBody: { when: { method: "POST" }, effect: "deny" }, },});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");await research.request("GET", url, { provider: "google", userToken });Drain pending audit and background tasks, then release HTTP resources.
async close(): Promise<void>Both App and Agent expose close(). The SDK does not auto-close on process exit; call it explicitly (typically from a try/finally):
const app = new App({ apiKey });try { // ...} finally { await app.close();}close() is idempotent. Subsequent calls on a closed client throw AlterSDKError.