Exchange an active client secret for a short-lived ClientToken.
Client tokens are the credential for existing integrations that call the
client API routes under /api/client. They are separate from the Commercial
API key documented in Authentication.
POST /api/client/token
Send JSON from your backend:
Content-Type: application/json{
"clientId": "your-client-id",
"clientSecret": "your-active-client-secret"
}The clientSecret must be an active secret provisioned for that client.
Revoked or expired secrets are rejected. A Commercial API key is a different
credential and must not be exchanged at this endpoint.
{
"token": "<client-token>",
"apiKeyId": "<opaque-key-id>",
"expiresAt": "2026-09-08T12:00:00.000Z"
}The returned JWT is valid for 12 hours from issuance. expiresAt is the
server-generated ISO timestamp for that expiry. Request a new token after it
expires; do not put the token in a URL or a browser bundle.
| Status | Body |
|---|---|
400 | { "error": "Missing clientId or clientSecret" } |
401 | { "error": "Invalid credentials" } |
403 | { "error": "Legacy client token exchange is disabled", "code": "legacy_client_token_exchange_disabled" } |
500 | { "error": "Internal server error" } |
The 403 response means the client has disabled this exchange, is inactive or
suspended, or is the demo tenant. It is a client configuration result rather
than an indication that the secret should be retried.
Send the token in the ClientToken header on a client API request:
GET /api/client/your-client-id/users
ClientToken: <client-token>The JWT contains the issuing clientId, admin: true, and an apiKeyId.
The issuing client must match the {clientId} in a client API path.
Client-token access to protected client APIs also requires the client API
module to be enabled. A disabled module returns 403. Use the custom
ClientToken header; do not send this token as
Authorization: Bearer ..., which is the format for a Firebase ID token or a
Commercial API key on their respective surfaces.