Skip to main content

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.

When to use it

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.

CallWhat it does
POST /api/services/app/ApiOAuthClient/CreateCreates the client. Body: apiKeyId, name (up to 256), description (optional)
GET /api/services/app/ApiOAuthClient/GetAllLists the clients (no secrets)
POST /api/services/app/ApiOAuthClient/RotateSecretBody: id, revokeIssuedTokens. Returns a new secret
POST /api/services/app/ApiOAuthClient/RevokeBody: id. Cancels the client

Create returns clientId (starts with oc_), clientSecret (starts with ocs_) and tokenEndpoint.

Copy the secret now

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 of client_id and client_secret in the body you can use HTTP Basic (Authorization: Basic base64(client_id:client_secret)).
  • scope is 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_in is 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:

StatuserrorWhen
401invalid_clientWrong client_id or client_secret, or revoked client
400invalid_scopeYou ask for a scope the key does not have
400unauthorized_clientThe 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, send revokeIssuedTokens: true and 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.

Next steps​