Skip to content
Hardcount
Run it locally
Pre-1.0 · one maintainer · Stripe and cards only · no SDKs (client libraries) yet · no production users yet. Docs describe v0.9.1 (29 Oct 2026) · What’s new →

Rotate an API key

POST
/v1/api-keys/{id}/rotate
curl --request POST \
--url http://localhost:8080/v1/api-keys/example/rotate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "expires_in_seconds": 0 }'

Issues a new key with the same name, type, mode and expires_at as the old one, and returns its full value once (raw_key).

The body is optional. With no body, or expires_in_seconds: 0, the old key is revoked at once. A positive value keeps the old key working for that many seconds (at most 604800, 7 days), so deployed clients can switch without downtime. Writes an audit row.

id
required
string
Idempotency-Key
string

Makes the call safe to retry. Send a unique value (a UUID works) and the same value on every retry. For 24 hours, a repeat with the same key, method, path and body gets the first response again, with an Idempotent-Replayed: true header, instead of running again. The error cases are in docs/api-surface.md. The usage routes do not take it: each usage event carries its own idempotency_key.

Media typeapplication/json
object
expires_in_seconds

Grace period for the old key. 0 revokes it immediately.

integer format: int64
0 <= 604800

Rotation done. raw_key is the new key, shown only in this response.

Media typeapplication/json
object
old_key
required
object
id
required
string
key_prefix
required

The start of the key (type, mode and a few characters), enough to tell keys apart. Never the full key.

string
key_type
required
string
Allowed values: platform secret
livemode
required
boolean
name
required
string
tenant_id
required
string
created_at
required
string format: date-time
expires_at

When the key stops working. Also set on the old key during a rotation grace period.

string format: date-time
revoked_at
string format: date-time
last_used_at

Updated at most about once per interval, so it can lag a little.

string format: date-time
new_key
required
object
id
required
string
key_prefix
required

The start of the key (type, mode and a few characters), enough to tell keys apart. Never the full key.

string
key_type
required
string
Allowed values: platform secret
livemode
required
boolean
name
required
string
tenant_id
required
string
created_at
required
string format: date-time
expires_at

When the key stops working. Also set on the old key during a rotation grace period.

string format: date-time
revoked_at
string format: date-time
last_used_at

Updated at most about once per interval, so it can lag a little.

string format: date-time
raw_key
required

The new key’s full value. Shown only once.

string

Example

{
"old_key": {
"id": "hc_key_1a2b3c4d5e6f7890",
"key_prefix": "hc_secret_test_1a2b3c4d",
"key_type": "platform"
},
"new_key": {
"id": "hc_key_1a2b3c4d5e6f7890",
"key_prefix": "hc_secret_test_1a2b3c4d",
"key_type": "platform"
}
}

Body is present but not valid JSON

Media typeapplication/json
object
error
object
type

One of invalid_request_error, authentication_error, rate_limit_error or api_error. Every error the API answers has this shape, including an unknown path (404, code route_not_found) and a method the path does not take (405, code method_not_allowed, with an Allow header). The few exceptions are listed in docs/api-surface.md.

string
code
string
message
string
request_id
string
param

The request field the error is about. Sent only when the error names one. A request body may hold only the fields its operation lists, spelled and cased as listed: any other field, or one the server sets (such as id), is refused with 422 and param naming it. An operation with no request body lists no fields: send no body, {} or null. The Stripe and Postmark webhooks and the LiteLLM callback take their sender’s body as it comes.

string

Example

{
"error": {
"type": "invalid_request_error"
}
}

The key is a platform key and the caller is not a platform key

Media typeapplication/json
object
error
object
type

One of invalid_request_error, authentication_error, rate_limit_error or api_error. Every error the API answers has this shape, including an unknown path (404, code route_not_found) and a method the path does not take (405, code method_not_allowed, with an Allow header). The few exceptions are listed in docs/api-surface.md.

string
code
string
message
string
request_id
string
param

The request field the error is about. Sent only when the error names one. A request body may hold only the fields its operation lists, spelled and cased as listed: any other field, or one the server sets (such as id), is refused with 422 and param naming it. An operation with no request body lists no fields: send no body, {} or null. The Stripe and Postmark webhooks and the LiteLLM callback take their sender’s body as it comes.

string

Example

{
"error": {
"type": "invalid_request_error"
}
}

No such key in this mode

Media typeapplication/json
object
error
object
type

One of invalid_request_error, authentication_error, rate_limit_error or api_error. Every error the API answers has this shape, including an unknown path (404, code route_not_found) and a method the path does not take (405, code method_not_allowed, with an Allow header). The few exceptions are listed in docs/api-surface.md.

string
code
string
message
string
request_id
string
param

The request field the error is about. Sent only when the error names one. A request body may hold only the fields its operation lists, spelled and cased as listed: any other field, or one the server sets (such as id), is refused with 422 and param naming it. An operation with no request body lists no fields: send no body, {} or null. The Stripe and Postmark webhooks and the LiteLLM callback take their sender’s body as it comes.

string

Example

{
"error": {
"type": "invalid_request_error"
}
}

The key is already revoked (code invalid_state)

Media typeapplication/json
object
error
object
type

One of invalid_request_error, authentication_error, rate_limit_error or api_error. Every error the API answers has this shape, including an unknown path (404, code route_not_found) and a method the path does not take (405, code method_not_allowed, with an Allow header). The few exceptions are listed in docs/api-surface.md.

string
code
string
message
string
request_id
string
param

The request field the error is about. Sent only when the error names one. A request body may hold only the fields its operation lists, spelled and cased as listed: any other field, or one the server sets (such as id), is refused with 422 and param naming it. An operation with no request body lists no fields: send no body, {} or null. The Stripe and Postmark webhooks and the LiteLLM callback take their sender’s body as it comes.

string

Example

{
"error": {
"type": "invalid_request_error"
}
}

expires_in_seconds is negative or over 604800, or the old key’s expires_at has passed (the new key would copy it)

Media typeapplication/json
object
error
object
type

One of invalid_request_error, authentication_error, rate_limit_error or api_error. Every error the API answers has this shape, including an unknown path (404, code route_not_found) and a method the path does not take (405, code method_not_allowed, with an Allow header). The few exceptions are listed in docs/api-surface.md.

string
code
string
message
string
request_id
string
param

The request field the error is about. Sent only when the error names one. A request body may hold only the fields its operation lists, spelled and cased as listed: any other field, or one the server sets (such as id), is refused with 422 and param naming it. An operation with no request body lists no fields: send no body, {} or null. The Stripe and Postmark webhooks and the LiteLLM callback take their sender’s body as it comes.

string

Example

{
"error": {
"type": "invalid_request_error"
}
}