OAuth2 (client credentials)
Server-to-server integrations can use the standard client credentials flow (RFC 6749 §4.4): an OAuth client tied to one of your API keys exchanges its client_id and client_secret for an access token that is sent as a Bearer. The API key does not travel on every request, and you can rotate the secret without touching the key.
It is an alternative to the API key, not an extra permission: the token acts as its key (same account, scopes, limits, monthly quota, IP and origin allow-lists, and test mode). There are no refresh tokens: request a new one when it expires.
In the examples, $BASE is your platform's address.
Create an OAuth client
There is no screen for this today: it is managed through the administration API, with an administrator session that holds the Manage OAuth2 clients permission (Pages.Administration.ApiKeys.OAuthClients) and an existing API key of your account.
| Call | What it does |
|---|---|
POST /api/services/app/ApiOAuthClient/Create | Creates the client. Body: apiKeyId, name (up to 256), description (optional) |
GET /api/services/app/ApiOAuthClient/GetAll | Lists the clients (no secrets) |
POST /api/services/app/ApiOAuthClient/RotateSecret | Body: id, revokeIssuedTokens. Returns a new secret |
POST /api/services/app/ApiOAuthClient/Revoke | Body: id. Cancels the client |
Create returns clientId (starts with oc_), clientSecret (starts with ocs_) and tokenEndpoint.
The client_secret is shown only once, when the client is created or rotated. Store it in a secrets manager, never in code.
If the key has admin scopes (Admin scopes), creating or rotating its client also requires the Pages.Administration.ApiKeys.AdminScopes permission.
Request a token
curl -s -X POST "$BASE/connect/token" \
-d grant_type=client_credentials \
-d client_id=oc_xxxxxxxx \
-d client_secret=ocs_xxxxxxxx \
-d "scope=read programs:read"
# {"access_token":"eyJ...","token_type":"Bearer","expires_in":3599,"scope":"read programs:read"}
- Format
application/x-www-form-urlencoded. Instead ofclient_idandclient_secretin the body you can use HTTP Basic (Authorization: Basic base64(client_id:client_secret)). scopeis optional and space-separated. Without it, the token carries all of the key's scopes; with it, only the ones you ask for, which must be a subset of the key's.- The token lasts 60 minutes by default (
expires_inis the remaining seconds). Reuse it until it expires.
Use it like a key:
curl -s "$BASE/api/v1/programs" -H "Authorization: Bearer eyJ..."
Errors
Those of the token endpoint follow RFC 6749 §5.2:
| Status | error | When |
|---|---|---|
401 | invalid_client | Wrong client_id or client_secret, or revoked client |
400 | invalid_scope | You ask for a scope the key does not have |
400 | unauthorized_client | The key is inactive or expired, the IP or origin is not allowed, the account is inactive or its plan does not include API access |
An expired or tampered token, or one from a revoked or rotated client, gets 401 with the header WWW-Authenticate: Bearer error="invalid_token": request a new one.
Rotate and revoke
- Rotate (
RotateSecret): the previous secret stops working at once. Tokens already issued stay valid until they expire, so you can switch secrets without downtime. If the secret leaked, sendrevokeIssuedTokens: trueand those tokens are refused too. - Revoke (
Revoke): the client, its secret and all its tokens stop working. Revoking or deleting the API key has the same effect; regenerating it keeps its OAuth clients. - Removing a scope from the key also removes it from tokens already issued.