Create a Token
Exchanges your client credentials for an access token and a refresh token. Every other partner endpoint takes the access token as Authorization: Bearer <access_token>. expires_in is a lifetime in seconds (RFC 6749 §5.1), not a timestamp.
The endpoint is public at the gateway (no bearer token). The integration service still checks the client credentials, the client's usage window and, when the client requires one, its client certificate.
Each client has exactly one valid access token at a time. Issuing a new token, with any grant, immediately invalidates the previous access token of that client: the next call made with the old token is refused with 401. If several processes share a client, let one of them own the token and hand it to the others, or give each process its own client.
Request
POST /v2/oauth/token
Headers
| Name | Description |
|---|---|
Content-Type | Required. application/json |
Body
| Field | Type | Required | Description |
|---|---|---|---|
client_id | string | Yes | Your client id |
client_secret | string | Yes | Your client secret |
grant_type | string | Yes | client_credentials, password or refresh_token. The grant must be enabled on your client. |
username | string | password grant | Email of a supervisor on your client's node |
password | string | password grant | That supervisor's password |
refresh_token | string | refresh_token grant | The most recent refresh token issued to your client |
Grant types
| Grant | Acts as | Permissions in the token | Access token lifetime | Refresh token lifetime |
|---|---|---|---|---|
client_credentials | The client itself (machine to machine) | The scopes configured on the client | The client's token_expiry.duration; no expiry when token_expiry.enabled is false (expires_in is then 0) | Same as the access token |
password | A supervisor of the client's node | The supervisor's role permissions | 15 minutes (expires_in: 900) | 30 days |
refresh_token | Whoever the refresh token was issued to | Same as the original grant | Same as the original grant | Same as the original grant |
Regardless of the client's policy, a refresh token is never redeemable more than 365 days after it was issued.
Client credentials
curl --request POST \
--url '{{baseUrl}}/v2/oauth/token' \
--header 'Content-Type: application/json' \
--data '{
"client_id": "c1a9beda-b14c-4eb5-b880-113023e6fc2d",
"client_secret": "<client_secret>",
"grant_type": "client_credentials"
}'
Password
curl --request POST \
--url '{{baseUrl}}/v2/oauth/token' \
--header 'Content-Type: application/json' \
--data '{
"client_id": "c1a9beda-b14c-4eb5-b880-113023e6fc2d",
"client_secret": "<client_secret>",
"grant_type": "password",
"username": "supervisor@example.com",
"password": "<password>"
}'
The supervisor must belong to the same node as the client; a supervisor of another node is refused exactly like a wrong password. Repeated failed passwords for the same username or from the same address are throttled with 429.
Refresh token
curl --request POST \
--url '{{baseUrl}}/v2/oauth/token' \
--header 'Content-Type: application/json' \
--data '{
"client_id": "c1a9beda-b14c-4eb5-b880-113023e6fc2d",
"client_secret": "<client_secret>",
"grant_type": "refresh_token",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}'
A refresh token works once. The call spends the presented refresh token and answers a new pair; store both. Presenting a spent refresh token, or one that a newer issuance superseded, is refused with 400 and code 10006. Only the newest refresh token issued to a client is live, so a fresh client_credentials or password token also supersedes the outstanding refresh token.
Response
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"scope": "token:view token:create default",
"created_at": 1762687200
}
Response Fields
| Field | Type | Description |
|---|---|---|
access_token | string | JWT to send as Authorization: Bearer <access_token> |
token_type | string | Always Bearer |
expires_in | number | Access token lifetime in seconds. 0 when the client's policy issues tokens without expiry |
refresh_token | string | Single-use token for the refresh_token grant |
scope | string | Space-separated permissions the token carries (role permissions for password, client scopes for client_credentials) |
created_at | number | Unix time the token was issued |
Errors
This endpoint predates the shared error format and answers most refusals with HTTP 409 and a JSON string body that embeds the code:
"Code: 10003, Domain: 20, Message: invalid request"
| Status | Body | Cause |
|---|---|---|
| 400 | string | The body does not parse, or client_id, client_secret or grant_type is missing / not a known grant |
| 400 | {"code": 10006, "domain": 20, "message": "refresh token is no longer valid"} | The refresh token was already redeemed or a newer issuance superseded it. Sign in again with your client's own grant |
| 401 | {"code": 10005, "domain": 20, "message": "client certificate required"} | The client requires a certificate and the TLS handshake did not present the one issued to it. See mTLS Client Certificates |
| 409 | Code: 10001 … client not found | Unknown client_id / client_secret pair |
| 409 | Code: 10002 … client is inactive | The operator deactivated the client |
| 409 | Code: 10004 … client has expired | The client's usage window has ended. No grant issues a token, not even refresh_token |
| 409 | Code: 10003 … invalid request | The grant is not enabled on the client; a grant parameter is missing; wrong username or password; the supervisor is on another node; the refresh token is malformed, expired, older than 365 days or belongs to another client |
| 429 | {"code": 10001, "domain": 86, "message": "too many failed password attempts; try again later"} | Too many failed password grant attempts |
Calls to every other endpoint use the shared error format.
Using the token
curl --request GET \
--url '{{baseUrl}}/v2/admin/tokens/68aeb272d9336c619ce249f6' \
--header 'Authorization: Bearer <access_token>'
A protected call answers 401 when the token is missing, malformed, expired, is a refresh token, or is no longer the client's live access token. Request a new token (or redeem the refresh token) and retry.