# Content API

The base URL, the [JSON:API](/start-here/glossary/#jsonapi) endpoint shape, the headers, and the shared error codes are identical on every Source CMS [site](/start-here/glossary/#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](/openapi/acquia-content-api.yaml) at a stable URL, for code generators and API collections. To explore endpoints interactively, use your site's own [OpenAPI documentation](/start-here/glossary/#openapi-documentation) (`API > OpenAPI documentation`): it renders the endpoints your site actually exposes, against your real content model, and calls them live.

<TryItAside />

## 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](/start-here/glossary/#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](/start-here/glossary/#content-type-bundle) 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](/source-cms/content-api/quickstart/), [querying guide](/source-cms/content-api/guide/).

## Base URL and versioning

```
$DRUPAL_SITE_URL/api
```

- The base URL is your site URL (the canonical `DRUPAL_SITE_URL` environment variable, no trailing slash) plus `/api`.
- There is no version segment in the path. The surface follows the [JSON:API 1.1 specification](https://jsonapi.org/); every response's `jsonapi.version` member reports `1.1`.
- `GET $DRUPAL_SITE_URL/api` (the API root) returns the index of resource endpoints available on that site, keyed by resource type (for example `node--article`).
- All requests and responses use the JSON:API media type `application/vnd.api+json` (see [Headers](#headers)).

Used in: [auth quickstart](/source-cms/authenticate/quickstart/), [first fetch](/source-cms/content-api/first-fetch/).

## Endpoint shape

Every resource endpoint follows the same pattern, built from an [entity type](/start-here/glossary/#entity-type) [machine name](/start-here/glossary/#machine-name) and a bundle machine name (the mapping is in the [content model reference](/source-cms/reference/content-model/)):

| Path | Returns | Used in |
|---|---|---|
| `/api/{entity_type}/{bundle}` | Collection of entries of that bundle, e.g. `/api/node/article` | [query quickstart](/source-cms/content-api/quickstart/) |
| `/api/{entity_type}/{bundle}/{uuid}` | One entry, addressed by its UUID (the `id` in every response) | [querying guide](/source-cms/content-api/guide/) |
| `/api/{entity_type}/{bundle}/{uuid}/{field}` | The related entries a relationship [field](/start-here/glossary/#field) points at | [querying guide](/source-cms/content-api/guide/) |
| `/api/{entity_type}/{bundle}/{uuid}/relationships/{field}` | The relationship itself (type + id identifiers only, not full entries) | [querying guide](/source-cms/content-api/guide/) |

Collections accept the query parameters documented exhaustively in the [query parameter reference](/source-cms/reference/query-parameters/) (`filter`, `fields`, `include`, `page`, `sort`). Every endpoint returns one JSON:API document; the [response document reference](/source-cms/reference/response-document/) annotates each envelope member (`data`, `attributes`, `relationships`, `links`, `meta`, `included`, `errors`) on a live response.

### 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](/source-cms/authenticate/quickstart/#make-one-authenticated-request).

**Collection**: `data` is an array:

```bash
curl -s "$DRUPAL_SITE_URL/api/node/article" \
  -H "Accept: application/vnd.api+json" \
  -H "Authorization: Bearer $TOKEN"
```

```jsonc
{
  "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`:

```bash
curl -s "$DRUPAL_SITE_URL/api/node/article/7f7dd525-fa49-465e-976d-a50ab5425c49" \
  -H "Accept: application/vnd.api+json" \
  -H "Authorization: Bearer $TOKEN"
```

```jsonc
{
  "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:

```bash
curl -s "$DRUPAL_SITE_URL/api/node/article/7f7dd525-fa49-465e-976d-a50ab5425c49/image" \
  -H "Accept: application/vnd.api+json" \
  -H "Authorization: Bearer $TOKEN"
```

```jsonc
{
  "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:

```bash
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"
```

```jsonc
{
  "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

| 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](/start-here/glossary/#api-client) granted the `content:administer` [scope](/start-here/glossary/#scope); the full scope list is in the [authentication reference](/source-cms/reference/authentication/) |
| `PATCH` | Individual entry (update) | Same as `POST` |
| `DELETE` | Individual entry (remove) | Same as `POST` |

Writes are opt-in per site; the [writing content guide](/source-cms/content-api/writing-content/) covers enabling and using them.

Used in: [query quickstart](/source-cms/content-api/quickstart/), [writing content guide](/source-cms/content-api/writing-content/).

## Headers

| Header | Value | When | Used in |
|---|---|---|---|
| `Accept` | `application/vnd.api+json` | Every request | [query quickstart](/source-cms/content-api/quickstart/), [direct-fetch guide](/source-cms/content-api/fetch-content/) |
| `Content-Type` | `application/vnd.api+json` | Every request with a body (`POST`, `PATCH`) | [writing content guide](/source-cms/content-api/writing-content/) |
| `Authorization` | `Bearer <access token>` | Every request, unless the site has enabled public API access at `API > JSON:API` | [auth quickstart](/source-cms/authenticate/quickstart/), [auth guide](/source-cms/authenticate/guide/) |

Tokens come from the OAuth 2.0 endpoints documented in the [authentication reference](/source-cms/reference/authentication/); the [API client](/start-here/glossary/#api-client) that issued the token determines which scopes it carries.

## 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](/source-cms/reference/response-document/#when-data-is-empty-but-the-collection-is-not)):

- **[`meta.count`](/source-cms/reference/response-document/#the-success-document) is the collection total *including* entries the request cannot see**, so `data` can be shorter than `meta.count`, or empty.
- A **`meta.omitted`** block appears, with `"detail": "Some resources have been omitted because of insufficient authorization."`. Each omitted entry is a link under `meta.omitted.links` whose own `meta.detail` names 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 carry [`links.next`](/source-cms/reference/response-document/#when-data-is-empty-but-the-collection-is-not)/`links.last`. An empty page is not the end of a collection; follow `links.next` or check `meta.omitted`.

Used in: [querying guide](/source-cms/content-api/guide/), [writing content guide](/source-cms/content-api/writing-content/).

## Error codes

The status codes below mean the same thing on every endpoint. Auth-specific codes (401/403) are detailed in the [authentication reference](/source-cms/reference/authentication/) and diagnosed step-by-step in the [auth guide's troubleshooting](/source-cms/authenticate/guide/#when-something-goes-wrong).

| 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](/source-cms/reference/query-parameters/#400-responses-for-malformed-queries) |
| `401 Unauthorized` | No valid token | Missing/malformed `Authorization` header, expired token, wrong client ID or secret at `/oauth/token` | [Authentication reference](/source-cms/reference/authentication/), [auth guide troubleshooting](/source-cms/authenticate/guide/#when-something-goes-wrong) |
| `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](/source-cms/reference/authentication/)) | [Authentication reference](/source-cms/reference/authentication/), [writing content guide](/source-cms/content-api/writing-content/) |
| `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](#your-sites-generated-api-documentation) for the real endpoint list | [Content model reference](/source-cms/reference/content-model/) |
| `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](/source-cms/content-api/writing-content/) |
| `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

- [Authentication quickstart](/source-cms/authenticate/quickstart/): headers and base URL
- [Authentication & API credentials guide](/source-cms/authenticate/guide/): `Authorization` header, 401/403 diagnosis
- [Content API quickstart](/source-cms/content-api/quickstart/): endpoint shape, `Accept` header
- [Querying guide](/source-cms/content-api/guide/): endpoint shape, subpaths, error codes, omitted resources
- [First fetch](/source-cms/content-api/first-fetch/) and [direct-fetch guide](/source-cms/content-api/fetch-content/): base URL and headers from frontend code
- [Creating & updating content via the API](/source-cms/content-api/writing-content/): methods, `Content-Type`, 403/405 causes, omitted resources
