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()
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
agent_idstroptionalFilter to keys bound to a specific agent. Omit to list all keys in the org.
limitintoptionalNumber of summaries per page. Server clamps to [1, 100].
cursorstroptionalOpaque 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()
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
key_idstrrequiredThe 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()
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
namestrrequiredHuman-readable label for the key, e.g. 'prod-v1'. Visible in the
dashboard and in list() responses.
agent_idstrrequiredThe 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()
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
key_idstrrequiredID 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()
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
key_idstrrequiredID 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
RecallAuthError401/403fatalAPI key missing, expired, revoked, or lacks admin scope.
RecallNotFoundError404fatalKey ID does not exist or belongs to a different org.
RecallValidationError422fatalInvalid name, missing required agent_id, or malformed body.
RecallRateLimitError429fatalRate limit exceeded. Inspect .retry_after_ms.
RecallServerError5xxfatalServer-side failure. Retried automatically up to max_retries.
Was this page helpful?