Brainby arc-labs/docs
Sdk typescriptAPI Keys
TypeScript SDK

API Keys

Issue, list, and rotate API keys with recall.keys — atomic rotation with zero authentication gap, secret shown once on creation.

Every Recall API key carries a bound (user, agent, namespace) identity. The server reads that binding on every request and derives the authoritative scope from it — clients never construct or override scope on the wire. Managing keys is therefore how you control which agent a caller is acting as and what data it can reach.

keys.list(opts?, options?)

GET/v1/api-keysAPI key (admin)stable
recall.keys.list(
  opts?: { limit?: number; cursor?: string },
  options?: TransportRequestOptions
): PagedRequest<ApiKeySummary>

Returns a PagedRequest<ApiKeySummary>await it for the first page, for await it to auto-paginate. The summary type never includes the plaintext key — only the prefix is exposed for identification.

Parameters

ParameterTypeRequired
opts.limitnumberoptional
Items per page, clamped server-side to [1, 100].
opts.cursorstringoptional
Explicit cursor override for resuming an interrupted iteration.
optionsTransportRequestOptionsoptional
Per-request transport overrides.

ApiKeySummary (per item)

FieldTypePresence
idApiKeyIdalways
Stable opaque identifier for the key record.
namestringalways
Human-readable label set at creation.
agentIdAgentIdalways
The agent this key is bound to.
keyPrefixstringalways
First 8 characters of the key, used for visual identification (e.g. rcall_pro). Safe to log.
createdAtstringalways
ISO 8601 timestamp.
lastUsedAtstring
ISO 8601 timestamp of the most recent authenticated request. Absent when the key has never been used.

Example

// First page
const page = await recall.keys.list({ limit: 50 })
console.log(page.items, page.hasMore)

// Auto-paginate every key in the org
for await (const key of recall.keys.list()) {
  console.log(key.id, key.keyPrefix, key.lastUsedAt ?? 'never used')
}

Errors

CodeStatusRetry
UNAUTHORIZED401fatal
Missing or invalid API key.
FORBIDDEN403fatal
Key lacks admin scope.

keys.get(id, options?)

GET/v1/api-keys/:idAPI key (admin)stable
recall.keys.get(id: ApiKeyId, options?: TransportRequestOptions): Promise<ApiKeySummary>

Returns the ApiKeySummary for a single key by its record ID. No secret field is present — the plaintext is gone after creation.

Example

import { type ApiKeyId } from '@arc-labs/recall'

const summary = await recall.keys.get('key_abc123' as ApiKeyId)
console.log(summary.name, summary.keyPrefix, summary.agentId)

Errors

CodeStatusRetry
UNAUTHORIZED401fatal
Missing or invalid API key.
FORBIDDEN403fatal
Key lacks admin scope.
NOT_FOUND404fatal
Key ID does not exist or has been deleted.

keys.create(opts, options?)

POST/v1/api-keysAPI key (admin)stable
recall.keys.create(
  opts: { name: string; agentId: AgentId },
  options?: TransportRequestOptions
): Promise<ApiKeyWithSecret>

// ApiKeyWithSecret = ApiKeySummary & { key: string }

Issues a new API key bound to the specified agent. The response includes the full plaintext key field exactly once. The server stores only a bcrypt hash — there is no recovery path.

The key field is returned exactly once. The server never stores the plaintext. Copy it to your secret manager before this function returns — once the Promise resolves and you discard the result, the key cannot be retrieved again. If you lose it, delete the key and issue a new one.

Parameters

ParameterTypeRequired
opts.namestringrequired
Human-readable label for the key. Shown in keys.list() output for identification. Does not need to be unique.
opts.agentIdAgentIdrequired
The agent this key should be bound to. The key will carry the agent's (user, namespace) binding.
optionsTransportRequestOptionsoptional
Per-request transport overrides.

Returns

ApiKeyWithSecret — all fields of ApiKeySummary plus:

FieldTypePresence
keystringalways
Full plaintext API key (e.g. rcall_prod_…). One-time only.
idApiKeyIdalways
Record ID for subsequent get, delete, and rotate calls.
keyPrefixstringalways
Safe-to-log prefix for visual identification.

Example

const agentId = 'agt_abc123' as AgentId

const created = await recall.keys.create({ name: 'prod-v1', agentId })
// ▲ Store created.key in your secret manager NOW.
// created.key is a one-time secret — it cannot be retrieved again.
const safeToLog = { id: created.id, prefix: created.keyPrefix }
console.log('Key issued:', safeToLog)

// Typical pattern: write to secret store before returning
await secretManager.set('RECALL_API_KEY', created.key)

Errors

CodeStatusRetry
UNAUTHORIZED401fatal
Missing or invalid API key.
FORBIDDEN403fatal
Key lacks admin scope.
NOT_FOUND404fatal
Specified agentId does not exist.

keys.delete(id, options?)

DELETE/v1/api-keys/:idAPI key (admin)stable
recall.keys.delete(id: ApiKeyId, options?: TransportRequestOptions): Promise<DeleteResult>

Permanently revokes a key. The key stops authenticating immediately — there is no grace period. Returns DeleteResult: { id, deletedAt }.

Deletion is irreversible. If you want zero-downtime key replacement, use keys.rotate() instead — it mints a replacement in the same atomic transaction.

Example

const result = await recall.keys.delete('key_abc123' as ApiKeyId)
console.log('Revoked at:', result.deletedAt)

Errors

CodeStatusRetry
UNAUTHORIZED401fatal
Missing or invalid API key.
FORBIDDEN403fatal
Key lacks admin scope.
NOT_FOUND404fatal
Key ID does not exist or was already deleted.

keys.rotate(id, options?)

POST/v1/api-keys/:id/rotateAPI key (admin)stable
recall.keys.rotate(id: ApiKeyId, options?: TransportRequestOptions): Promise<ApiKeyWithSecret>

Atomically revokes the existing key and mints a replacement in a single server-side transaction. The old key is dead and the new key is live before the response reaches the client — there is no window where neither key works.

Rotation is atomic — there is no window where neither key works. The old key is dead before the response reaches the client. Update your secret manager with the new key immediately after the call resolves, then redeploy your services.

Returns

ApiKeyWithSecret — the new key, with a new id, keyPrefix, createdAt, and the one-time key secret. The old key's ID is gone; reference the new id for future get, delete, and rotate calls.

Example — 30-day rotation routine

// Rotate every 30 days
const fresh = await recall.keys.rotate(currentKeyId)
// Update your secret manager with fresh.key
await secretManager.set('RECALL_API_KEY', fresh.key)
// Update RECALL_API_KEY in your deployment environment
await deployment.setEnv('RECALL_API_KEY', fresh.key)
// The old key is already revoked — no overlap window
currentKeyId = fresh.id

Errors

CodeStatusRetry
UNAUTHORIZED401fatal
Missing or invalid API key.
FORBIDDEN403fatal
Key lacks admin scope.
NOT_FOUND404fatal
Key ID does not exist or was already deleted.

Was this page helpful?

On this page