Authentication endpoints & headers
Lookup material for authenticating against a site’s API. For instruction, see the auth quickstart (first token in 10 minutes) and the auth guide (grant choice, scoping, storage, rotation). Both endpoints are also described in the machine-readable OpenAPI 3.1 spec under the Authentication tag, published at a stable URL for code generators and API collections.
All paths are relative to the site base URL (DRUPAL_SITE_URL, no trailing slash). Credentials are an API client’s ID and secret, created in your site’s admin UI at API > API clients. These endpoints exist on Source CMS sites. A headless site on Cloud Platform authenticates against your own Drupal application’s OAuth setup, which you configure and operate yourself.
Endpoints
Section titled “Endpoints”How the two endpoints cooperate in the authorization_code grant; the other two grants skip /oauth/authorize and call POST /oauth/token directly:
GET /oauth/authorize
Section titled “GET /oauth/authorize”Starts the authorization_code grant: the user’s browser is redirected here, the user logs in and grants permissions, and the site redirects back with a temporary code. Not used by the other grants.
Query parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
response_type |
string | yes | none | The literal value code |
client_id |
string | yes | none | The API client’s ID |
redirect_uri |
string | yes | none | Must match one of the client’s configured Redirect URIs |
scope |
string | no | every scope selected on the client | Space-separated subset of the client’s selected scopes |
Response: a redirect to redirect_uri carrying the temporary authorization code, which is then exchanged at POST /oauth/token with grant_type=authorization_code.
Used in: auth guide, step 2.
POST /oauth/token
Section titled “POST /oauth/token”Issues access tokens for all three grant types. Request body is form-encoded:
Content-Type: application/x-www-form-urlencodedRequest parameters per grant type:
grant_type=client_credentials
Section titled “grant_type=client_credentials”Server-to-server; access is based on the client’s permissions, not a user’s.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
grant_type |
string | yes | none | The literal value client_credentials |
client_id |
string | yes | none | The API client’s ID |
client_secret |
string | yes | none | The API client’s secret |
scope |
string | no | every scope selected on the client | Space-separated subset of the client’s selected scopes |
Example exchange (token redacted):
curl -s -X POST "$DRUPAL_SITE_URL/oauth/token" \ -d "grant_type=client_credentials" \ -d "client_id=$DRUPAL_CLIENT_ID" \ -d "client_secret=$DRUPAL_CLIENT_SECRET"{ "token_type": "Bearer", "expires_in": 300, "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUz…(redacted)"}Used in: auth quickstart, auth guide, step 2.
grant_type=authorization_code
Section titled “grant_type=authorization_code”Exchanges the code from GET /oauth/authorize for a token. All parameters are required strings with no defaults:
| Parameter | Description |
|---|---|
grant_type |
The literal value authorization_code |
code |
The authorization code from the redirect |
client_id |
The API client’s ID |
client_secret |
The API client’s secret |
redirect_uri |
The same redirect URI used at /oauth/authorize |
Used in: auth guide, step 2.
grant_type=refresh_token
Section titled “grant_type=refresh_token”Exchanges a refresh token for a new access token, without user interaction. All parameters are required strings with no defaults:
| Parameter | Description |
|---|---|
grant_type |
The literal value refresh_token |
refresh_token |
The refresh token |
client_id |
The API client’s ID |
client_secret |
The API client’s secret |
Used in: auth guide, step 2.
Response schema (all grant types)
Section titled “Response schema (all grant types)”Success is 200 with JSON:
{ "token_type": "Bearer", "expires_in": 300, "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI…(redacted)"}| Field | Type | Meaning |
|---|---|---|
token_type |
string | Always Bearer, use the header format below |
expires_in |
integer | Access token lifetime in seconds, counted from issuance |
access_token |
string | The token itself |
refresh_token |
string | Returned by the authorization_code and refresh_token grants. Never returned by client_credentials (absent from the captured response above, and confirmed in the OAuth server implementation): server-to-server clients re-request tokens with their credentials instead |
Failure is 401 with a JSON error body; see Error codes.
Request header
Section titled “Request header”Every authenticated API request carries the access token in this exact header:
Authorization: Bearer <token>One space between Bearer and the token. Canonical example against the JSON:API root:
curl -s "$DRUPAL_SITE_URL/api" \ -H "Accept: application/vnd.api+json" \ -H "Authorization: Bearer $TOKEN"Used in: auth quickstart, step 3, every request in the Content API reference.
Scopes
Section titled “Scopes”Scopes are selected as checkboxes on the API client (Allowed Scopes) and are the token’s defaults:
- A token request without a
scopeparameter carries every selected scope. - A
scopeparameter may narrow to a subset of them. - Naming an unknown or unselected scope returns the
invalid_scopeerror.
Scopes gate writes and admin surfaces, not published-content reads: a GET for published content succeeds with any valid token regardless of scope, and with no token at all when the site’s Public access (API > JSON:API) is Yes. Reading unpublished content requires content:administer.
The full vocabulary:
| Scope | Governs | Used in |
|---|---|---|
content:administer |
Content CRUD over the JSON:API: create, update, and delete entries. There is no read-only content scope. | Auth quickstart, writing content guide |
content_type:administer |
Content types (bundles) | N/A |
content_type:administer_fields |
Fields on content types | N/A |
page_template:administer |
Page templates | N/A |
media:administer |
Media entries | N/A |
media_type:administer |
Media types | N/A |
taxonomy:administer |
Taxonomy vocabularies and terms | N/A |
taxonomy:administer_fields |
Fields on taxonomy terms | N/A |
menu:administer |
Menus | N/A |
site_settings:administer |
Site settings | N/A |
canvas:page:read |
Read Canvas pages | N/A |
canvas:page:create |
Create Canvas pages | N/A |
canvas:page:edit |
Edit Canvas pages | N/A |
canvas:page:delete |
Delete Canvas pages | N/A |
canvas:page_region |
Canvas page regions | N/A |
canvas:content_template |
Canvas content templates | N/A |
canvas:asset_library |
Drupal Canvas CLI (@drupal-canvas/cli): asset library access for downloading and uploading components |
Canvas components |
canvas:js_component |
Drupal Canvas CLI (@drupal-canvas/cli): JS component access for downloading and uploading components |
Canvas components |
canvas:brand_kit |
Canvas brand kits | N/A |
canvas:media:view |
View media from Canvas | N/A |
canvas:media:image:create |
Create images from Canvas | N/A |
member |
Basic member access | N/A |
Error codes
Section titled “Error codes”This table matches the auth guide’s troubleshooting, which adds context per entry.
| Error | Cause | Fix |
|---|---|---|
{"error":"invalid_client","error_description":"Client authentication failed"}, 401 from /oauth/token |
The client ID or secret sent to /oauth/token is wrong. |
Re-copy both from API > API clients; if the secret is lost, create a new client. The secret is shown only once. |
401 Unauthorized, on an API request that worked before; the explanation is in the WWW-Authenticate response header (error="access_denied", error_description="Access token could not be verified"), not the body |
The access token outlived its expires_in lifetime: 300 seconds (5 minutes). |
Request a fresh token from /oauth/token and retry; have your code re-request tokens automatically rather than caching one forever. |
401 Unauthorized, on an API request that has never worked. The body is HTML, not JSON; the diagnostic is only in the WWW-Authenticate response header, e.g. (captured) Bearer realm="OAuth", error="access_denied", error_description="The JWT string must have two dots" for a malformed token |
The Authorization header is missing, malformed, or carries a token this site did not issue. |
Send exactly Authorization: Bearer <token> (one space after Bearer) with a token issued by the same site you’re calling; read the header with curl -i. |
{"error":"invalid_scope","error_description":"The requested scope is invalid, unknown, or malformed","hint":"Check the `content:read` scope"}, 400 from /oauth/token; the hint names the offending scope |
The token request’s scope parameter names a scope that doesn’t exist or isn’t selected on the client. |
Omit the scope parameter (the token then carries every scope selected on the client), or name only selected scopes from the scope table. |
403 Forbidden, JSON:API error document whose detail names the missing permission (e.g. The 'administer nodes' permission is required.) |
The token is valid but doesn’t carry the scope the request needs, for example, writing content with a token from a client that doesn’t have content:administer selected. |
Select the scope on the API client at API > API clients, then request a new token. Existing tokens don’t gain scopes retroactively. |
…blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource., browser console |
The site’s allowed origins have been restricted at API > CORS configuration and the browser origin making the request isn’t one of them (CORS is open by default). |
Add the origin under Allowed origins at API > CORS configuration, or move the call server-side, where CORS doesn’t apply. |
Token lifetimes
Section titled “Token lifetimes”| Token | Lifetime |
|---|---|
| Access token | 300 seconds (5 minutes), reported per token as expires_in. |
| Authorization code | 300 seconds: the code from GET /oauth/authorize must be exchanged within 5 minutes (product configuration, confirmed 2026-07-03). |
| Refresh token | 1209600 seconds (14 days; product configuration, confirmed 2026-07-03). |
Used in
Section titled “Used in”- Auth quickstart: obtains a
client_credentialstoken and makes the first authenticated call. - Auth guide: chooses between these grant types and covers scoping, storage, and rotation.
- Content API reference: the endpoints these tokens authorize.
Was this page helpful?
What went wrong?
Still stuck? Contact Acquia Support (opens in a new tab)