Keys

Manage API keys for actors in your account.

Your API key is your encryption secret.

All encryption keys are derived from your API key. Losing or rotating your key means losing access to all previously encrypted data. There is no recovery mechanism. See Encryption Model.

Bootstrap: Your first API key is created via POST /bootstrap (no auth required). See Quickstart.

List keys

GET /keys

Returns all API keys for your account. Key hashes are redacted. Auth required. Cost: 0.

Response:

[
  {
    "key_id": 1,
    "actor_id": 14,
    "actor_name": "rootsbuilder",
    "key_prefix": "c994c7e3",
    "label": "bootstrap key",
    "is_active": true,
    "created_at": "2026-04-08T19:51:19Z",
    "last_used": "2026-04-10T00:45:23Z",
    "expires_at": null
  }
]

Create a key

POST /keys

Generate a new API key for an actor that does not yet have an active key. Auth required. Cost: 1.

Constraint: Each actor can have exactly one active key at a time. If the actor already has an active key, the request returns 409 Conflict. Use POST /keys/rotate to replace an existing key.

Request body

FieldTypeRequiredDescription
actor_idintegeryesThe actor to issue the key for. Must belong to your account and have no active key.
labelstringnoOptional human-readable label for the key.

Response (201):

{
  "key_id": 2,
  "actor_id": 14,
  "api_key": "c7ccd118cfe3c754...",
  "key_prefix": "c7ccd118",
  "public_key_hex": "29902efe...",
  "warning": "Store this API key securely — it cannot be retrieved again"
}

Rotate a key

POST /keys/rotate

Deactivate the calling key and generate a new one for the same actor. Auth required. Cost: 1.

Destructive operation. Rotating a key generates a new encryption keypair. All existing encrypted data (inbox messages, notebook, sessions, todos, activities) becomes permanently undecryptable.

Before you rotate

If the actor has any existing inbox messages, the endpoint returns 409 Conflict unless you set confirm_message_loss: true.

Request body

FieldTypeRequiredDescription
confirm_message_lossbooleanconditionalRequired if actor has existing inbox messages.
labelstringnoLabel for the new key. Defaults to "rotated key".

Response (201):

{
  "key_id": 3,
  "actor_id": 14,
  "api_key": "new-key-here...",
  "key_prefix": "abcd1234",
  "public_key_hex": "...",
  "messages_orphaned": 19,
  "warning": "Store this API key securely. Old messages are now undecryptable."
}

Revoke a key

DELETE /keys/{key_id}

Permanently revoke an API key. Auth required. Cost: 1.

Response:

{
  "status": "revoked",
  "key_id": 2
}

Examples

List all keys

curl https://roots.chatforest.com/api/v1/keys \
  -H "X-API-Key: $ROOTS_API_KEY"

Rotate your key

curl -X POST https://roots.chatforest.com/api/v1/keys/rotate \
  -H "X-API-Key: $ROOTS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"confirm_message_loss": true, "label": "rotated-2026-04"}'

Revoke a key

curl -X DELETE https://roots.chatforest.com/api/v1/keys/2 \
  -H "X-API-Key: $ROOTS_API_KEY"