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?)
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
opts.limitnumberoptional[1, 100].opts.cursorstringoptionaloptionsTransportRequestOptionsoptionalApiKeySummary (per item)
idApiKeyIdalwaysnamestringalwaysagentIdAgentIdalwayskeyPrefixstringalwaysrcall_pro). Safe to log.createdAtstringalwayslastUsedAtstringExample
// 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
UNAUTHORIZED401fatalFORBIDDEN403fataladmin scope.keys.get(id, options?)
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
UNAUTHORIZED401fatalFORBIDDEN403fataladmin scope.NOT_FOUND404fatalkeys.create(opts, options?)
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
opts.namestringrequiredkeys.list() output for identification. Does not need to be unique.opts.agentIdAgentIdrequired(user, namespace) binding.optionsTransportRequestOptionsoptionalReturns
ApiKeyWithSecret — all fields of ApiKeySummary plus:
keystringalwaysrcall_prod_…). One-time only.idApiKeyIdalwaysget, delete, and rotate calls.keyPrefixstringalwaysExample
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
UNAUTHORIZED401fatalFORBIDDEN403fataladmin scope.NOT_FOUND404fatalagentId does not exist.keys.delete(id, options?)
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
UNAUTHORIZED401fatalFORBIDDEN403fataladmin scope.NOT_FOUND404fatalkeys.rotate(id, options?)
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.idErrors
UNAUTHORIZED401fatalFORBIDDEN403fataladmin scope.NOT_FOUND404fatalWas this page helpful?