Brainby arc-labs/docs
Sdk pythonrecall.keys
Python SDK

recall.keys

Issue, inspect, and rotate API keys with recall.keys (SyncApiKeys / AsyncApiKeys). Atomic rotation with zero authentication gap.

All methods exist on both SyncApiKeys (via Recall) and AsyncApiKeys (via AsyncRecall) with identical signatures. Use <await> on the async path.

list()

GET/v1/api-keysAPI key (admin)stable
def list(
    self,
    *,
    agent_id: Optional[str] = None,
    limit: int = 20,
    cursor: Optional[str] = None,
) -> dict: ...

Return a paginated list of API key summaries. The plaintext key field is never returned in list responses — only the non-secret keyPrefix.

Parameters

ParameterTypeRequired
agent_idstroptional

Filter to keys bound to a specific agent. Omit to list all keys in the org.

limitintoptional

Number of summaries per page. Server clamps to [1, 100].

cursorstroptional

Opaque cursor from the previous page's meta.nextCursor.

Returns

{
    "items": [
        {
            "id": "key_01HEX…",
            "name": "prod-v1",
            "agentId": "ag_01HEX…",
            "keyPrefix": "rcall_prod",   # safe to log
            "createdAt": "2026-04-01T09:00:00Z",
            "lastUsedAt": "2026-05-09T22:00:00Z",  # null if never used
        },
        # …
    ],
    "meta": {
        "count": 2,
        "limit": 20,
        "nextCursor": None,
    },
}

Examples

# List keys for a specific agent
keys = recall.keys.list(agent_id=agent["id"])
for k in keys["items"]:
    print(k["keyPrefix"], "last used:", k.get("lastUsedAt", "never"))
keys = await recall.keys.list(agent_id=agent["id"])
for k in keys["items"]:
    print(k["keyPrefix"], "last used:", k.get("lastUsedAt", "never"))

get()

GET/v1/api-keys/:idAPI key (admin)stable
def get(self, key_id: str) -> dict: ...

Fetch a single ApiKeySummary by its ID. Like list(), the response contains no plaintext key field — only the keyPrefix.

Parameters

ParameterTypeRequired
key_idstrrequired

The id of the key to fetch, e.g. 'key_01HEX…'.

Examples

summary = recall.keys.get("key_01HEX…")
print(summary["name"], summary["keyPrefix"])
summary = await recall.keys.get("key_01HEX…")
print(summary["name"], summary["keyPrefix"])

Raises RecallNotFoundError when the key ID does not exist or belongs to a different org.

create()

POST/v1/api-keysAPI key (admin)stable
def create(self, name: str, agent_id: str) -> dict: ...

Issue a new API key bound to the specified agent. Returns ApiKeyWithSecret — an ApiKeySummary extended with the plaintext key field.

Parameters

ParameterTypeRequired
namestrrequired

Human-readable label for the key, e.g. 'prod-v1'. Visible in the dashboard and in list() responses.

agent_idstrrequired

The agent this key is bound to. The server derives the agent's namespace and permission scope from this binding.

Store the key value immediately after creation. The server does not log or cache the plaintext secret — it is hashed before being written to the database. If you lose it, call rotate() to mint a new one; there is no recovery path.

Returns

{
    "id": "key_01HEX…",
    "name": "prod-v1",
    "agentId": "ag_01HEX…",
    "keyPrefix": "rcall_prod",
    "createdAt": "2026-05-10T08:00:00Z",
    "lastUsedAt": None,
    "key": "rcall_prod_…",   # plaintext — copy to secret manager NOW
}

Examples

new_key = recall.keys.create(name="prod-v1", agent_id=agent["id"])

secret = new_key["key"]             # copy to secret manager NOW
safe_to_log = new_key["keyPrefix"]  # this is safe to log/print

print(f"Created key {safe_to_log} — secret stored.")
new_key = await recall.keys.create(name="prod-v1", agent_id=agent["id"])

secret = new_key["key"]
safe_to_log = new_key["keyPrefix"]

print(f"Created key {safe_to_log} — secret stored.")

delete()

DELETE/v1/api-keys/:idAPI key (admin)stable
def delete(self, key_id: str) -> dict: ...

Revoke an API key. Revocation is immediate — any in-flight request authenticated with this key that has not yet been validated by the server will be rejected.

Parameters

ParameterTypeRequired
key_idstrrequired

ID of the key to revoke.

Returns

{
    "id": "key_01HEX…",
    "deletedAt": "2026-05-10T09:00:00Z",
}

Examples

result = recall.keys.delete("key_01HEX…")
print("Revoked at:", result["deletedAt"])
result = await recall.keys.delete("key_01HEX…")
print("Revoked at:", result["deletedAt"])

rotate()

POST/v1/api-keys/:id/rotateAPI key (admin)stable
def rotate(self, key_id: str) -> dict: ...

Atomically revoke the current key and mint a replacement with the same name, agent binding, and permissions. Returns ApiKeyWithSecret containing the new plaintext secret.

Parameters

ParameterTypeRequired
key_idstrrequired

ID of the key to rotate. The original key is revoked in the same database transaction that creates the replacement.

Rotation is a single atomic transaction — there is no window where neither the old nor the new key is valid. Zero authentication gap. Deploy the new secret to your service before the transaction commits, or retrieve it from the response and push it immediately after.

Returns

Same shape as create()ApiKeyWithSecret with the new key value.

Examples

fresh = recall.keys.rotate(current_key_id)

# 1. Update your secret manager with the new secret
secrets_manager.put("recall-api-key", fresh["key"])

# 2. Deploy the new key to your running service
#    (old key is already revoked — do this quickly)
deploy_config(recall_api_key=fresh["key"])

print("Rotated to:", fresh["keyPrefix"])
fresh = await recall.keys.rotate(current_key_id)

await secrets_manager.put_async("recall-api-key", fresh["key"])
print("Rotated to:", fresh["keyPrefix"])

Errors

CodeStatusRetry
RecallAuthError401/403fatal

API key missing, expired, revoked, or lacks admin scope.

RecallNotFoundError404fatal

Key ID does not exist or belongs to a different org.

RecallValidationError422fatal

Invalid name, missing required agent_id, or malformed body.

RecallRateLimitError429fatal

Rate limit exceeded. Inspect .retry_after_ms.

RecallServerError5xxfatal

Server-side failure. Retried automatically up to max_retries.

Was this page helpful?

On this page