Rotation

How to replace an API token with a fresh key.

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.

Rotation returns the new raw API key only once. Store it securely when the request succeeds.

You can rotate a token in 3 ways:

  • Directly calling rotateApiKey.
  • Using the privateActionManager to validate ownership before rotating.
  • Calling the POST /api/manage/rotate endpoint from an authenticated client.
Follow the Creating Tokens docs to create a token first before attempting to rotate it.

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.

example.ts
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.

When Expiry is null NO expiry will be checked in the verification process.

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:

FieldTypeDescription
rawOldTokenstringThe 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.
namestringThe name assigned to the new token record.
deleteOnRotationbooleanIf true, the old token row is deleted after it is revoked.
ipAddressstring[] | undefinedThe IP restriction list assigned to the new token.
expiresnumber | undefinedThe TTL for the new token in milliseconds.
prefixstringThe prefix used when generating the new raw token.
This action should be performed by a fully authenticated client.
This function does not validate a 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.

example.ts
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.

The manager only selects rows where 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"
}
Check the introduction page to see the standard error response.

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 - A burstLimiter and a slowLimiterunion 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:

LimiterDescription
rate_limiters.apiTokensLimiters.operationRateLimits.rotationRateLimiterThe main rate limiter for the rotation endpoint
rate_limiters.apiTokensLimiters.generalUnionLimiterGeneral burst limiter

Metadata returned on success

FieldDescription
msgSuccess message returned by the rotation helper
newRawTokenThe newly generated raw API token
newExpiryThe expiration timestamp for the new token, or null
Logo