Get a token
https://api.granska.cloud/v1/oauth/tokenExchanges a client id and secret for a short-lived bearer token.
The narrative version of this — where a client id and secret come from, how long a token lives, what to do when one expires — is on Authentication. This page is the endpoint itself.
Request body
JSON, three fields, all required. grant_type must be client_credentials; any other value is
rejected with 400 UNSUPPORTED_GRANT_TYPE rather than ignored, so a client that sends the wrong
grant finds out immediately instead of receiving a token it did not ask for.
The client_secret is sent here and to nowhere else. Every other endpoint takes the resulting
access_token as Authorization: Bearer <token>.
| Parameter | Description |
|---|---|
grant_type"client_credentials"·body·required | The only grant this gateway supports. |
client_idstring·body·required | The identifier issued with the API key. |
client_secretstring·body·required | The secret issued with the API key. Sent only to this endpoint, never on other calls. |
curl -X POST https://api.granska.cloud/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "cli_9f3a2b7c",
"client_secret": "sk_live_2c8e41d0a7b6"
}'{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}The response
access_token is the bearer token. token_type is always Bearer. expires_in is the remaining
lifetime in seconds — read it rather than hardcoding the current value, which is one hour.
There is no refresh token and no revocation endpoint. When a token expires you request another one
the same way; requesting a token spends no quota, so a client that fetches one per hour, or simply
one on every 401 TOKEN_EXPIRED, is behaving correctly.
Errors
401 UNAUTHORIZED does not distinguish an unknown client id from a wrong secret from a deactivated
key. That is deliberate: the alternative lets a caller enumerate valid client ids by watching which
ones come back with a different status.
503 SERVICE_UNAVAILABLE is the one failure here worth retrying. It means the credential store was
briefly unreachable, not that the credentials are wrong, and the response carries Retry-After.
| Error | When |
|---|---|
400BAD_REQUEST | client_id or client_secret is missing. |
400UNSUPPORTED_GRANT_TYPE | grant_type is anything other than client_credentials. |
401UNAUTHORIZED | The client id is unknown, or the secret does not match it. |
500INTERNAL_ERROR | An unexpected server-side failure. Not probable from outside — reaching it means something is wrong. |
503SERVICE_UNAVAILABLE | The credential store is transiently unavailable; a Retry-After header says when to try again. Not probed: it needs an injected infrastructure failure. |
Send it without writing a client
If your organisation already has an account, an administrator can exchange a key for a token in the
API tester at /admin/api-tester, which is also the fastest way to check that a
newly created key works.