Skip to content

Replace a key with a new secret carrying the same authority

POST
/v1/api-keys/{key_id}/rotate
curl --request POST \
--url https://api.vocapable.com/v1/api-keys/example/rotate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "name": "example", "expires_at": "2026-04-15T12:00:00Z" }'

Retire one key and mint its replacement, in one transaction.

Rotation exists because the two-call alternative - create, then delete - has no safe order. Create-then-delete leaves both live if the second call is lost; delete-then-create leaves the integration with no credential at all. Here the revoke is the claim (revoke_returning), so a lost response leaves exactly one live key and a replaying client gets 404 api_key_not_found rather than a second replacement.

The replacement carries the retired key’s mode and scopes, not the caller’s. That is what lets a narrow contractor key rotate itself without the caller having to hold what it holds - and it is why the request body has no scopes field: there is no widening path here to guard, only one that does not exist.

key_id
required
Key Id
string
Media typeapplication/json
ApiKeyRotateRequest

Replace one key with another carrying the same authority.

Mode and scopes are deliberately not settable here: rotation replaces a credential, it does not re-grant one. Widening on rotate would route around the scope-escalation refusal POST /v1/api-keys makes.

object
name
Any of:
string
>= 1 characters <= 120 characters
expires_at
Any of:
string format: date-time
Examplegenerated
{
"name": "example",
"expires_at": "2026-04-15T12:00:00Z"
}

Successful Response

Media typeapplication/json
ApiKeyRotateResponse

The replacement key, plus the id of the credential it retired.

Both halves in one body because they happen in one transaction: a client that stored the new secret and never learned which key stopped working would have to diff a list to find out.

object
id
required
Id
string
name
Any of:
string
display_prefix
required
Display Prefix
string
mode
required
Mode
string
Allowed values: live test
scopes
required
Scopes
Array<string>
created_at
required
Created At
string format: date-time
expires_at
Any of:
string format: date-time
revoked_at
Any of:
string format: date-time
secret
required
Secret
string
replaced_api_key_id
required
Replaced Api Key Id
string
Example
{
"mode": "live"
}

Validation Error

Media typeapplication/json
HTTPValidationError
object
detail
Detail
Array<object>
ValidationError
object
loc
required
Location
Array
msg
required
Message
string
type
required
Error Type
string
input
Input
ctx
Context
object
Examplegenerated
{
"detail": [
{
"loc": [
"example"
],
"msg": "example",
"type": "example",
"input": "example",
"ctx": {}
}
]
}