Authentication
Every CM Box API request is authenticated with a JSON Web Token (JWT) sent as a bearer token. Machine-to-machine integrations obtain that token by authenticating a service account through the OAuth2 client credentials grant. This page explains the token flow, how requests carry the token, and how a service account’s permissions are determined.
How it works
Section titled “How it works”Service account credentials │ ▼POST /oauth2/token (client_credentials grant) │ ▼JWT access token (valid 1 hour) │ ▼Authorization: Bearer <token> on every API request │ ▼Middleware verifies the JWT → route runs → permission checks applyAuthentication happens in middleware before your request reaches any route.
The middleware reads the Authorization: Bearer header (or, for browser
sessions, a token cookie), verifies the JWT’s signature and expiry, and
attaches the authenticated identity to the request. Requests without a valid
token never reach the API route. After authentication, each endpoint enforces
its own required permissions — see the
permissions reference.
Service accounts
Section titled “Service accounts”A service account is a non-human identity for API clients. An administrator
creates one in the security admin area or via
POST /api/security/serviceAccounts. Two values matter to you as an
integrator:
- ID — the account’s identifier, used as the OAuth2
client_id. - Secret — used as the OAuth2
client_secret. The plaintext secret is returned only once, at creation time; CM Box stores only a hash of it, so a lost secret cannot be retrieved later.
Service accounts hold no permissions directly. They are assigned roles (directly or through group membership), and roles carry the actual grants — permission bundles, scoped permission lists, and overrides. See how permissions attach below.
Obtaining a token
Section titled “Obtaining a token”Exchange the service account credentials for a token at POST /oauth2/token.
The request body is form-encoded (application/x-www-form-urlencoded), and
client_credentials is the only supported grant type:
| Field | Value |
|---|---|
client_id |
Service account ID |
client_secret |
Service account secret |
grant_type |
client_credentials (only supported value) |
curl -X POST https://cmbox.example.com/oauth2/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "client_id=my-service-account" \ -d "client_secret=YOUR_SECRET" \ -d "grant_type=client_credentials"A successful response returns the JWT and its lifetime in seconds:
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 3600, "token_type": "Bearer"}Error responses use a single error field:
| Status | error |
Cause |
|---|---|---|
400 |
unsupported_grant_type, only client_credentials is supported |
grant_type was not client_credentials |
401 |
invalid_client |
Unknown service account ID or wrong secret |
Using the token
Section titled “Using the token”Send the token in the Authorization header on every request:
curl https://cmbox.example.com/api/security/me/permissions \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."GET /api/security/me/permissions returns the calling identity’s effective
permission set, which makes it a convenient first call to confirm both that
your token works and what it is allowed to do.
Token lifetime
Section titled “Token lifetime”Tokens expire one hour after issue (expires_in: 3600). The client
credentials grant does not issue refresh tokens — when a token expires,
request a new one from /oauth2/token. A long-running client should track
the expiry and re-authenticate before it lapses; a 401 or a redirect on a
previously working call typically means the token has expired.
How permissions attach to a service account
Section titled “How permissions attach to a service account”A service account’s effective permissions are resolved the same way as a user’s:
- The account is assigned roles, directly and through its groups.
- Each role contributes permission bundles (named permission sets such as
bundle.content_editor), explicit permission keys scoped globally, per repository, or per site, and grant/revoke overrides. - Bundle permissions apply globally — across all repositories and sites. To restrict a service account to one repository, use a role with repository-scoped permission keys instead of a bundle.
The full key catalog, the built-in bundles, and the resolution rules are in the permissions reference.
Anonymous access
Section titled “Anonymous access”A small set of endpoints serves public content without a token when the repository is configured as public: published-asset and published-item endpoints, the GraphQL endpoint (which applies its own per-repository rules), and media embeds. Everything else requires a valid bearer token.
Related
Section titled “Related”- Permissions reference — permission keys, scopes, and bundles
- Architecture overview — where authentication sits in the request flow
- REST API reference — see the REST API Reference section in the sidebar
for per-endpoint details, including
/oauth2/token