Content API
The base URL, the JSON:API endpoint shape, the headers, and the shared error codes are identical on every Source CMS site. The endpoint list is not: it is generated per site, and served from the site itself (see below).
These shared endpoints are also published as a machine-readable OpenAPI 3.1 spec at a stable URL, for code generators and API collections. To explore endpoints interactively, use your site’s own OpenAPI documentation (API > OpenAPI documentation): it renders the endpoints your site actually exposes, against your real content model, and calls them live.
Your site’s generated API documentation
Section titled “Your site’s generated API documentation”The authoritative, always-current list of endpoints for a specific site lives on that site:
| Tool | Where | What it’s for |
|---|---|---|
| OpenAPI documentation | API > OpenAPI documentation in your site’s admin UI |
The source of truth for your site’s live, generated endpoint list. Browse every available endpoint and test calls interactively. |
Because every enabled content type gets its endpoints generated automatically, any static copy of the list goes stale. Your site’s OpenAPI documentation answers “what endpoints does my site have”; the shared rules below answer “what is true of every site”.
Used in: query quickstart, querying guide.
Base URL and versioning
Section titled “Base URL and versioning”$DRUPAL_SITE_URL/api- The base URL is your site URL (the canonical
DRUPAL_SITE_URLenvironment variable, no trailing slash) plus/api. - There is no version segment in the path. The surface follows the JSON:API 1.1 specification; every response’s
jsonapi.versionmember reports1.1. GET $DRUPAL_SITE_URL/api(the API root) returns the index of resource endpoints available on that site, keyed by resource type (for examplenode--article).- All requests and responses use the JSON:API media type
application/vnd.api+json(see Headers).
Used in: auth quickstart, first fetch.
Endpoint shape
Section titled “Endpoint shape”Every resource endpoint follows the same pattern, built from an entity type machine name and a bundle machine name (the mapping is in the content model reference):
| Path | Returns | Used in |
|---|---|---|
/api/{entity_type}/{bundle} |
Collection of entries of that bundle, e.g. /api/node/article |
query quickstart |
/api/{entity_type}/{bundle}/{uuid} |
One entry, addressed by its UUID (the id in every response) |
querying guide |
/api/{entity_type}/{bundle}/{uuid}/{field} |
The related entries a relationship field points at | querying guide |
/api/{entity_type}/{bundle}/{uuid}/relationships/{field} |
The relationship itself (type + id identifiers only, not full entries) | querying guide |
Collections accept the query parameters documented exhaustively in the query parameter reference (filter, fields, include, page, sort). Every endpoint returns one JSON:API document; the response document reference annotates each envelope member (data, attributes, relationships, links, meta, included, errors) on a live response.
Each shape, captured
Section titled “Each shape, captured”One request/response pair per path shape. All four use the same article, so the shapes are directly comparable. $TOKEN is an access token from step 3 of the auth quickstart.
Collection: data is an array:
curl -s "$DRUPAL_SITE_URL/api/node/article" \ -H "Accept: application/vnd.api+json" \ -H "Authorization: Bearer $TOKEN"{ "jsonapi": { "version": "1.1", "meta": { … } }, "data": [ { "type": "node--article", "id": "7f7dd525-fa49-465e-976d-a50ab5425c49", "attributes": { "title": "Spring launch recap", … }, "relationships": { … } }, … ], "meta": { "count": 4 }, "links": { "self": { … } }}Individual entry: same shape, but data is one object, and there is no meta.count:
curl -s "$DRUPAL_SITE_URL/api/node/article/7f7dd525-fa49-465e-976d-a50ab5425c49" \ -H "Accept: application/vnd.api+json" \ -H "Authorization: Bearer $TOKEN"{ "jsonapi": { "version": "1.1", "meta": { … } }, "data": { "type": "node--article", "id": "7f7dd525-fa49-465e-976d-a50ab5425c49", "attributes": { "title": "Spring launch recap", … }, "relationships": { … } }, "links": { "self": { … } }}Related subpath (/{field}): the referenced entries themselves, full attributes:
curl -s "$DRUPAL_SITE_URL/api/node/article/7f7dd525-fa49-465e-976d-a50ab5425c49/image" \ -H "Accept: application/vnd.api+json" \ -H "Authorization: Bearer $TOKEN"{ "jsonapi": { "version": "1.1", "meta": { … } }, "data": { "type": "media--image", "id": "5f2c5cde-9f49-48a9-8fb3-fbf328a710a2", "attributes": { "name": "Portal verification hero", … }, "relationships": { … } }, "links": { "self": { … } }}Relationship subpath (/relationships/{field}): the linkage only: type + id identifiers, no attributes. Use it to read or rewrite what an entry points at without fetching the targets:
curl -s "$DRUPAL_SITE_URL/api/node/article/7f7dd525-fa49-465e-976d-a50ab5425c49/relationships/tags" \ -H "Accept: application/vnd.api+json" \ -H "Authorization: Bearer $TOKEN"{ "jsonapi": { "version": "1.1", "meta": { … } }, "data": [ { "type": "taxonomy_term--tags", "id": "dbc476ff-1ff2-4bec-a1fe-cb3981dd385a", "meta": { … } }, { "type": "taxonomy_term--tags", "id": "19059886-e390-4520-b880-e372c2d1d784", "meta": { … } } ], "links": { "self": { … }, "related": { … } }}Methods
Section titled “Methods”| Method | Target | Requires |
|---|---|---|
GET |
Collection or individual entry | Always enabled |
POST |
Collection (create an entry) | The site’s allowed-operations setting at API > JSON:API set to Read and write, and a token from an API client granted the content:administer scope; the full scope list is in the authentication reference |
PATCH |
Individual entry (update) | Same as POST |
DELETE |
Individual entry (remove) | Same as POST |
Writes are opt-in per site; the writing content guide covers enabling and using them.
Used in: query quickstart, writing content guide.
Headers
Section titled “Headers”| Header | Value | When | Used in |
|---|---|---|---|
Accept |
application/vnd.api+json |
Every request | query quickstart, direct-fetch guide |
Content-Type |
application/vnd.api+json |
Every request with a body (POST, PATCH) |
writing content guide |
Authorization |
Bearer <access token> |
Every request, unless the site has enabled public API access at API > JSON:API |
auth quickstart, auth guide |
Tokens come from the OAuth 2.0 endpoints documented in the authentication reference; the API client that issued the token determines which scopes it carries.
Omitted resources
Section titled “Omitted resources”When access control hides entries from a collection response (most commonly unpublished entries), the response stays 200 and signals the omission instead of failing (an example is in the response document reference):
meta.countis the collection total including entries the request cannot see, sodatacan be shorter thanmeta.count, or empty.- A
meta.omittedblock appears, with"detail": "Some resources have been omitted because of insufficient authorization.". Each omitted entry is a link undermeta.omitted.linkswhose ownmeta.detailnames the reason. For unpublished entries:"The current user is not allowed to GET the selected resource. The 'view any unpublished content' permission is required." - Pagination links are computed before access filtering: a page can be
"data": []and still carrylinks.next/links.last. An empty page is not the end of a collection; followlinks.nextor checkmeta.omitted.
Used in: querying guide, writing content guide.
Error codes
Section titled “Error codes”The status codes below mean the same thing on every endpoint. Auth-specific codes (401/403) are detailed in the authentication reference and diagnosed step-by-step in the auth guide’s troubleshooting.
| Status | Meaning | Typical cause | Where to look |
|---|---|---|---|
400 Bad Request |
The query itself is malformed | Bad filter syntax, unknown field path in filter/sort/include, invalid page value |
Query parameter reference → 400 responses |
401 Unauthorized |
No valid token | Missing/malformed Authorization header, expired token, wrong client ID or secret at /oauth/token |
Authentication reference, auth guide troubleshooting |
403 Forbidden |
Token is valid but not allowed to do this | The API client the token came from doesn’t grant the needed scope: content writes need content:administer (full scope list in the authentication reference) |
Authentication reference, writing content guide |
404 Not Found |
The resource doesn’t exist | Wrong entity type or bundle machine name in the path, or a UUID that doesn’t exist; check your site’s OpenAPI documentation for the real endpoint list | Content model reference |
405 Method Not Allowed |
Write sent while the site allows only reads | The Allowed operations setting at API > JSON:API is Read-only (the default); the response’s detail names the settings page |
Writing content guide |
429 Too Many Requests |
Rate limited | Too many requests in a short window. Acquia does not publish Content API rate limits; if you receive a 429, back off and honor a Retry-After header when present |
None |
Used in
Section titled “Used in”- Authentication quickstart: headers and base URL
- Authentication & API credentials guide:
Authorizationheader, 401/403 diagnosis - Content API quickstart: endpoint shape,
Acceptheader - Querying guide: endpoint shape, subpaths, error codes, omitted resources
- First fetch and direct-fetch guide: base URL and headers from frontend code
- Creating & updating content via the API: methods,
Content-Type, 403/405 causes, omitted resources
Was this page helpful?
What went wrong?
Still stuck? Contact Acquia Support (opens in a new tab)