Skip to content

TypeScript SDK

Agents & Keys

Managed agents (app.agents namespace), Agent runtime (me/trace/withConstraints), scoped key lifecycle (keys namespace).

This page covers the full lifecycle of managed agents and the scoped-key primitives they’re built on:

  • app.agents.* — managed agent CRUD and per-agent key rotation. App-only.
  • Agent runtime — workload-side primitives: me(), trace(), withConstraints().
  • keys.* — scoped sub-key lifecycle (derive, rotate, revoke).
// Operator side
const created = await app.agents.create({ name: "research-agent" });
const page = await app.agents.list();
const info = await app.agents.get(agentId);
await app.agents.update(agentId, { displayName: "Research Agent v2" });
await app.agents.delete(agentId);
// Workload side
const me = await agent.me();
await agent.trace({ runId: "run_42" }, async () => {
await agent.request(...);
});
const narrowed = agent.withConstraints({ scopes: ["grants:read"] });
// Scoped key lifecycle
const derived = await app.keys.derive({ scopes: ["tokens:retrieve"], expiresIn: 3600 });
const rotated = await app.keys.rotate({ keyId: old.id });
const revoked = await app.keys.revoke({ keyId: leaked.id });

import { App } from "@alter-ai/alter-sdk";
const app = new App({ apiKey });
const created = await app.agents.create({ name: "research-agent" });
console.log(created.apiKey); // shown ONCE

Provision a managed agent and mint its initial API key.

async create(options: AgentCreateOptions): Promise<AgentCreateResult>
OptionTypeDescription
namestringStable per-app identifier. Required.
displayNamestringHuman-readable label.
type"agent" | "service"Defaults to "agent".
scopesRecord<string, string[]>Per-provider scope allowlist.
metadataRecord<string, unknown>Free-form metadata (≤8 KB).
policyRecord<string, unknown>HITL config + policy attributes.
idempotencyKeystringCaller-supplied idempotency key. No CR/LF.

Returns AgentCreateResult. The plaintext apiKey is returned exactly once — store it securely on receipt. On an idempotency replay where the original key cannot be re-issued, apiKey is null; branch on result.apiKey === null to detect replay.

Throws AlterValueError, AgentNameExistsError, AgentCannotMintSubagentsError, IdempotencyKeyBodyMismatchError, IdempotencyKeyAgentRevokedError, IdempotencyKeyAgentInactiveError.

Paginated list of agents in the app.

async list(options?: AgentListOptions): Promise<AgentListResult>
OptionTypeDefaultDescription
includeRevokedbooleanfalseInclude tombstoned agents.
limitnumber100Page size (1..1000).
offsetnumber0Page offset.

Returns AgentListResult.

Fetch a single agent by UUID.

async get(agentId: string): Promise<AgentInfo>

Throws AgentNotFoundError.

Fetch an active agent by exact name. Returns null when no matching agent exists — doesn’t throw on the common “does it exist?” check.

async getByName(name: string): Promise<AgentInfo | null>
const info = await app.agents.getByName("research-agent");
if (info === null) {
throw new Error("Agent not provisioned");
}
const agent = app.getAgent(info.id);

Update label or scope/policy block on an existing agent.

async update(
agentId: string,
options?: AgentUpdateOptions,
): Promise<AgentInfo>
OptionTypeDescription
displayNamestringNew display name.
metadataRecord<string, unknown>Replacement metadata (pass {} to clear).
scopesRecord<string, string[]>Replacement per-provider scope allowlist. Narrowing rejected.
policyRecord<string, unknown>Replacement policy block.

Passing no options is a no-op round-trip. Returns the updated AgentInfo.

Throws AgentScopeNarrowingNotSupportedError when scope updates drop providers or remove scopes from an existing provider.

Hard-revoke an agent and cascade-revoke every API key under it.

async delete(agentId: string): Promise<AgentInfo>

Idempotent — calling delete() on an already-revoked agent returns the same row.

Each managed agent can hold multiple API keys, exactly one of which is active at any time after the initial mint. Use the rotation surface for credential rolls without bricking the workload.

async mintKey(agentId: string): Promise<AgentKeyMintResult>
async listKeys(agentId: string): Promise<AgentKeyList>
async deprecateKey(agentId: string, keyId: string): Promise<AgentKey>
async undeprecateKey(agentId: string, keyId: string): Promise<AgentKey>
async revokeKey(
agentId: string,
keyId: string,
options?: { force?: boolean },
): Promise<AgentKey>
  • mintKey — mint a successor key. The plaintext is returned once.
  • listKeys — return every key for the agent (active, deprecated, revoked).
  • deprecateKey — keep the key working but mark it as the old key. The SDK surfaces a deprecation warning when a deprecated key is used so operations dashboards spot “time to roll the workload.”
  • undeprecateKey — clear the deprecation marker.
  • revokeKey — terminal revoke. Default-protective: refuses to revoke the agent’s last active key. Pass force: true to override.

Throws KeyNotFoundError, KeyAlreadyRevokedError, LastActiveKeyError.


Return the calling agent’s own record.

async me(): Promise<AgentInfo>

Calling me() on an App client throws MeRequiresAgentKeyError from the backend. The check is enforced server-side; the carve-out lets paused or revoked agents call me() for self-diagnosis (but a revoked individual key still throws KeyRevokedError).

const agent = new Agent({ apiKey: process.env.AGENT_API_KEY! });
const info = await agent.me();
console.log(info.status, info.scopes);

Run a callback inside an audit-context scope. Every nested SDK call propagates the agent identity, run ID, and optional metadata for audit attribution.

async trace<T>(
options: {
runId?: string;
threadId?: string;
parent?: string | null;
[metadataKey: string]: unknown;
},
callback: () => Promise<T>,
): Promise<T>
OptionTypeDescription
runIdstringStable per-run identifier (e.g. LangChain run_id). Threads through to the audit row.
threadIdstringStable per-conversation identifier.
parentstring | nullExplicit parent identity. Overrides ambient detection.
[metadataKey]stringFree-form metadata. Values must be strings.

Inside the callback, every request(), proxyRequest(), or nested trace() on this agent — or on any other client constructed in the same async context — carries the scope automatically.

await agent.trace({ runId: "run_abc", role: "writer" }, async () => {
await agent.request("GET", url, { grantId });
await researcher.trace({}, async () => {
// parent_agent populated automatically via ambient detection.
await researcher.request(...);
});
});

When an outer trace() of a different agent is active, parent_agent is recorded automatically. Re-entering trace() for the same agent does not set parent_agent.

These keys are reserved and throw AlterValueError when passed via metadata:

  • agent
  • parent_agent
  • run_id (use the explicit runId option instead)
  • thread_id (use the explicit threadId option instead)
  • tool
  • tool_call_id
  • framework

Return a constrained sibling Agent. Pass scopes to narrow the scope set, rule to attach a one-off deny rule, or both; at least one is required. Calling it on a closed client throws AlterSDKError. See withConstraints on the Client page for the full surface, including the RequestRule shape.

const readOnly = agent.withConstraints({ scopes: ["grants:read"] });
const page = await readOnly.listGrants();
// Attach a one-off deny rule:
const restricted = agent.withConstraints({
rule: {
ruleType: "json_match",
ruleBody: { when: { method: "POST" }, effect: "deny" },
},
});

The constrained sibling preserves the parent agent’s trace() identity — the run ID, thread ID, and metadata propagate through the scope-narrowed client.


The keys namespace lifecycle-manages scoped API keys. Available on both App and Agent — agent callers typically only have permission to call derive().

import { App } from "@alter-ai/alter-sdk";
const app = new App({ apiKey });
const derived = await app.keys.derive({
scopes: ["tokens:retrieve", "grants:read"],
expiresIn: 3600,
cidrAllowlist: ["10.0.0.0/8"],
metadata: { purpose: "ci-deploy" },
});
process.env.CHILD_KEY = derived.apiKey; // shown ONCE

Mint a short-lived attenuated key under the calling key.

async derive(options: DeriveKeyOptions): Promise<MintedKey>
OptionTypeDescription
scopesreadonly string[]Scopes the derived key holds. Must be a non-empty subset of the caller’s effective scopes. keys:derive is rejected — derived keys cannot themselves derive.
expiresInnumberLifetime in seconds. Capped by org policy (maxDerivedKeyTtlHours, default 24 h) and by the caller’s remaining TTL.
cidrAllowlistreadonly string[]Optional IP allowlist. Must be a subset of the caller’s allowlist.
namestringOptional label.
metadataRecord<string, unknown>Free-form metadata stored on the key.

Returns MintedKey — extends APIKeyInfo with the plaintext apiKey. The plaintext is returned exactly once. Store it securely.

Throws:

Rotate an existing key: mint a same-scope successor with an overlap window where both keys work.

async rotate(options: RotateKeyOptions): Promise<MintedKey>
OptionTypeDefaultDescription
keyIdstringThe key to rotate. Required.
overlapDaysnumber7Days the old key remains valid. Range 0..30.

Returns MintedKey — the successor’s plaintext is on apiKey, returned once.

Derived (dk) keys cannot be rotated. Derive a new sub-key from the parent instead.

Throws:

Revoke a key and cascade-revoke every derived (dk) descendant.

async revoke(options: RevokeKeyOptions): Promise<APIKeyInfo>
OptionTypeDefaultDescription
keyIdstringThe key to revoke. Required.
forcebooleanfalseBypass the last-active-key guard.

Default-protective: refuses to revoke the last active key on a managed agent. Pass force: true to override (e.g. incident response on a leaked key without a successor).

Returns the post-revoke APIKeyInfo.

Throws:

Synchronous, no-network format check for a plaintext key. Re-derives the embedded CRC checksum and compares.

import { isValidKey } from "@alter-ai/alter-sdk";
isValidKey("alter_rk_…"); // true | false
isValidKey(undefined); // false
function isValidKey(plainKey: unknown): boolean

Returns false for non-strings, empty strings, unknown key-type discriminators, the wrong segment count, or a mismatched checksum. Returns true for legacy alter_key_… keys (which have no checksum) when the body is non-empty. Does not throw.

This is a usability gate that catches single-character typos before paying a network round-trip. It is not a security boundary — a forged checksum cannot pass backend authentication because the server verifies the key against its hashed fingerprint.