Skip to content

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(),
});
new App(options: AppOptions)
OptionTypeDefaultDescription
apiKeystringApplication API key (alter_rk_…). Required.
timeoutnumber30000HTTP timeout in milliseconds.
callerstringCaller identifier for audit attribution.
callerTypeCallerType | "agent" | "service"SERVICEDistinguishes 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().
loggerAlterLoggerglobal consolePluggable logger. See AlterLogger.

Throws AlterValueError if apiKey is missing or empty.

PropertyTypeDescription
actorIdstring | nullBackend-resolved actor UUID. Populated after the first authenticated call.
lastRetryInfoRetryInfo | nullToken-refresh retry metadata from the most recent request() call.
baseUrlstringBackend the client is pinned to. Diagnostic accessor.
agentsAgentsNamespaceManaged agent CRUD.
keysKeysNamespaceScoped sub-key lifecycle.
scopesScopesNamespaceScope catalog discovery.

The App class exposes:

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(),
});
new Agent(options: AgentOptions)
OptionTypeDefaultDescription
apiKeystringAgent API key (alter_ak_…). Required.
timeoutnumber30000HTTP timeout in milliseconds.
callerstringCaller 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.
loggerAlterLoggerglobal consolePluggable logger.

callerType is forced to AGENT on this class. Throws AlterValueError if apiKey is missing or empty.

PropertyTypeDescription
actorIdstring | nullBackend-resolved actor UUID.
lastRetryInfoRetryInfo | nullToken-refresh retry metadata.
baseUrlstringBackend the client is pinned to.
keysKeysNamespaceScoped sub-key lifecycle.
scopesScopesNamespaceScope catalog discovery.

Agent has no agents namespace — agent CRUD is application-scoped.

The Agent class exposes:

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:

  • scopes narrows 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.
  • rule attaches 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, so method-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 }): App
withConstraints(options: { scopes?: readonly string[]; rule?: RequestRule }): Agent

RequestRule 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" },
},
});

Return an Agent instance that impersonates a named managed agent for audit attribution.

getAgent(agentId: string): Agent
ParameterTypeDescription
agentIdstringUUID 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.