Rotation
Rotation replaces an existing API token with a fresh one. The system revokes the old token first, then generates a new raw key so the caller can continue using machine-to-machine access without keeping the previous credential active.
When you rotate through the privateActionManager or the
POST /api/manage/rotate route, the new token inherits the
current token name, privilege, prefix, remaining expiration time, and existing
IP restrictions. When you call rotateApiKey directly, you can override those
attributes and choose whether to delete the old row after revocation.
You can rotate a token in 3 ways:
- Directly calling
rotateApiKey. - Using the
privateActionManagerto validate ownership before rotating. - Calling the
POST /api/manage/rotateendpoint from an authenticated client.
Rotating Tokens
You can rotate tokens either directly in the library or through the route. The direct helper gives you full control over the replacement token attributes, while the route preserves the current token settings and focuses on safe client-driven rotation.
Using the library
To rotate a token directly, you call rotateApiKey. This function revokes
the old token, optionally deletes its invalidated row, and creates a new token
with the privilege and attributes you provide.
import { rotateApiKey } from '@riavzon/auth'
const results = await rotateApiKey(
oldToken,
'restricted',
'server token',
false,
['203.0.113.10'],
1000 * 60 * 60,
'api'
)
if (results.ok) {
console.log(results.data.newRawToken)
}
On success you get back an object with:
{
ok: true,
date: new Date().toISOString(),
data: {
msg: 'Successfully rotated an api key',
newRawToken: 'api_xxx',
newExpiry: new Date('2026-05-01T12:00:00.000Z')
}
}
If the new token does not have an expiration date, newExpiry is null.
On error, rotateApiKey returns the branch that failed:
If revokeApiKey reports that the token is already invalid, rotation stops and
returns:
{
ok: false,
date: new Date().toISOString(),
reason: 'Cannot rotate revoked token'
}
If the revoke step fails for another reason, or if deleteOnRotation is
enabled and the invalidated row cannot be deleted, rotation returns:
{
ok: false,
date: new Date().toISOString(),
reason: 'Token cannot be rotated this time'
}
If an unexpected exception is thrown inside rotateApiKey, the catch block
returns:
{
ok: false,
date: new Date().toISOString(),
reason: 'Server error rotating a token.'
}
If the token creation step fails after the old token is revoked, rotateApiKey
forwards the reason returned by createApiKey. That usually means the user hit
the maximum token limit or the provided prefix is invalid.
Signature
The direct helper exposes the following signature:
export async function rotateApiKey(
rawOldToken: string,
privilegeType: 'demo' | 'restricted' | 'protected' | 'full' | 'custom',
name: string,
deleteOnRotation?: boolean,
ipAddress?: string[],
expires?: number,
prefix?: string
): Promise<Results<ApiTokenRotationSuccess>>
Parameters
The rotation helper accepts the following parameters:
| Field | Type | Description |
|---|---|---|
rawOldToken | string | The current token to rotate. It can be hashed or Raw. |
privilegeType | 'demo' | 'restricted' | 'protected' | 'full' | 'custom' | The privilege assigned to the current token and the replacement token. |
name | string | The name assigned to the new token record. |
deleteOnRotation | boolean | If true, the old token row is deleted after it is revoked. |
ipAddress | string[] | undefined | The IP restriction list assigned to the new token. |
expires | number | undefined | The TTL for the new token in milliseconds. |
prefix | string | The prefix used when generating the new raw token. |
publicIdentifier, and it does not
authenticate the caller for you. Only call it directly in trusted server-side
code.With the privateActionManager
The safest internal method is using the privateActionManager. This function
validates the publicIdentifier, token id, token name, user id, and current
valid status before it dispatches the rotation.
const rotationRes = await privateActionManager(
userId,
tokenId,
publicIdentifier,
tokenName,
{ action: 'rotate' }
)
When the manager rotates a token, it preserves the stored token attributes. It
passes the current token name, prefix, remaining expiration time, and IP
restrictions to rotateApiKey, and it keeps deleteOnRotation set to false.
The privateActionManager returns the response of rotateApiKey directly.
Learn more at the
introduction page.
valid = 1. Because of that,
rotation cannot rotate an already revoked token, and it returns
Bad Request before it reaches rotateApiKey.Using the route
Let's say you want to rotate a token from your authenticated client or BFF.
POST /api/manage/rotate body:
{
"tokenId": 12,
"publicIdentifier": "public_identifier",
"name": "the token name"
}
The name field identifies the current token during ownership validation. The
route does not use it to rename the replacement token.
On success you will get the following response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"ok": true,
"date": "current date",
"data": {
"msg": "Successfully rotated an api key",
"newRawToken": "new raw token",
"newExpiry": "current expiry or null"
}
}
Aside from the standard errors related to authentication, rate limits, and provided bad data, the route returns the same success object the library users get when the manager finds a valid token and the rotation completes.
If the body does not match the schema, the route returns:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"ok": false,
"date": "current date",
"reason": "Bad Request"
}
If the publicIdentifier checksum is invalid, the route returns Invalid identity.
If the manager cannot match a currently valid token for the given tokenId, publicIdentifier, name, and userId, it returns Bad Request. That also means the token is already revoked, belongs to a different user, or the body does not point to an active row.
If the manager finds the token but the underlying rotation action fails, the
route forwards the reason returned by rotateApiKey, such as
Token cannot be rotated this time, a createApiKey validation reason, or
Server error rotating a token.:
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"ok": false,
"date": "current date",
"reason": "reason from privateActionManager or rotateApiKey"
}
Rate Limits
The endpoint also enforces rate limits controlled under the following configuration options:
rate_limiters.apiTokensLimiters.operationRateLimits.rotationRateLimiter- The main limiter for the rotation endpoint. The default allows 5 rotations in a window of 10 minutes and will trigger a block for 2 hours if this limit is met. Consecutive triggers in this period will block the client permanently.rate_limiters.apiTokensLimiters.generalUnionLimiter- AburstLimiterand aslowLimiterunion limiter. It enforces no more than one request per second, and only 50 per minute. No consecutive triggers in this limiter are provided, and triggering it again will result in a permanent ban. The limiter is restarted on successful rotations.burstLimiter- Will block the client for 15 minutes.slowLimiter- Will block for 1 hour.
Rotation Process
Rotation uses a controlled replace workflow so the old credential stops working before the new one becomes active.
Revoke the current token
The direct helper starts by calling revokeApiKey for the current token and
privilege. If this step fails because the token is already revoked,
rotateApiKey returns Cannot rotate revoked token. If the revoke step fails
for another reason, it returns Token cannot be rotated this time.
Optionally delete the old row
When deleteOnRotation is true, rotateApiKey deletes the invalidated row
from api_tokens after revocation. The route does not enable
this option, so the old row stays in the database by default.
Create the replacement token
After the old token is invalid, the helper calls createApiKey to generate the
replacement. When you call rotateApiKey directly, the new token uses the
attributes you provide. When you rotate through the manager or route, the new
token inherits the current token privilege, name, prefix, remaining expiration,
and IP restrictions.
Return the new credential
When rotation succeeds, the API returns the new raw token and the new expiry timestamp. The old token remains invalid, and the new raw token is only exposed in that success response.
Configuration Reference
These configuration keys control rotate-specific throttling in the service.
Rate limiters
The rotation flow relies on the following limiter configuration:
| Limiter | Description |
|---|---|
rate_limiters.apiTokensLimiters.operationRateLimits.rotationRateLimiter | The main rate limiter for the rotation endpoint |
rate_limiters.apiTokensLimiters.generalUnionLimiter | General burst limiter |
Metadata returned on success
| Field | Description |
|---|---|
msg | Success message returned by the rotation helper |
newRawToken | The newly generated raw API token |
newExpiry | The expiration timestamp for the new token, or null |