# Response document

Every response the Content API returns is one [JSON:API](/start-here/glossary/#jsonapi) document with the same envelope. Each member is explained below, with the matching slice of a live response pinned alongside it. For the endpoints that produce these documents, see the [Content API reference](/source-cms/reference/content-api/); for the query parameters that shape them, see the [query parameter reference](/source-cms/reference/query-parameters/).

<TryItAside />

## The success document

The slices below come from one response to `GET /api/node/article?include=image,tags&page[limit]=1`.

<ApiLayout>

<ApiRow id="jsonapi">
<Fragment slot="code">
```jsonc
"jsonapi": {
  "version": "1.1",
  "meta": { … }
}
```
</Fragment>

**`jsonapi`**: always present. The spec version the document conforms to.

<Ref>[Content API reference → versioning](/source-cms/reference/content-api/#base-url-and-versioning)</Ref>
</ApiRow>

<ApiRow id="data">
<Fragment slot="code">
```jsonc
"data": [
  {
    // type + id identify any resource, everywhere.
    "type": "node--article",
    "id": "7f7dd525-fa49-465e-976d-a50ab5425c49",
    "links": { "self": { "href": "…/api/node/article/7f7dd525-…" } },
    "attributes":    { … },
    "relationships": { … }
  }
]
```
</Fragment>

**`data`**: the requested resource(s). Its shape follows the endpoint:

| Request | `data` is |
|---|---|
| A collection (`/api/node/article`) | an array of resource objects |
| A single resource (`/{uuid}`) | one resource object |
| A relationship pointing at nothing | `null` |

Each resource is identified by `type` + `id` together, the same pair used in `data`, in `included`, and inside relationship linkage. A document is a success or a failure, never both: `data` and `errors` never appear together.
</ApiRow>

<ApiRow id="attributes">
<Fragment slot="code">
```jsonc
"attributes": {
  "langcode": "en",
  "status": true,
  "title": "Spring launch recap",
  "created": "2026-07-02T14:51:56+00:00",
  "moderation_state": "published",
  "body": {
    "value": "The spring launch shipped on time…",
    "format": "filtered_html",
    "processed": "<p>The spring launch shipped on time…</p>"
  }
}
```
</Fragment>

**`data[].attributes`**: every non-reference [field](/start-here/glossary/#field) of the entry, keyed by [machine name](/start-here/glossary/#machine-name). The field-type table maps each type to its JSON shape.

<Ref>[Content model reference](/source-cms/reference/content-model/)</Ref>
</ApiRow>

<ApiRow id="relationships">
<Fragment slot="code">
```jsonc
"relationships": {
  // one-to-one: "data" is one identifier (or null)
  "image": {
    "data": { "type": "media--image", "id": "5f2c5cde-…" }
  },
  // one-to-many: "data" is an array of identifiers
  "tags": {
    "data": [
      { "type": "taxonomy_term--tags", "id": "dbc476ff-…" },
      { "type": "taxonomy_term--tags", "id": "19059886-…" }
    ]
  }
}
```
</Fragment>

**`data[].relationships`**: every reference field, keyed by machine name. Each holds resource *identifiers* (`type` + `id`), never the referenced entries themselves:

| Cardinality | `data` is |
|---|---|
| One-to-one | one identifier, or `null` |
| One-to-many | an array of identifiers |

The entries themselves arrive in top-level `included` when the request adds `?include=`.

<Ref>[Querying guide → relationships](/source-cms/content-api/guide/)</Ref>
</ApiRow>

<ApiRow id="included">
<Fragment slot="code">
```jsonc
"included": [
  {
    "type": "media--image",
    "id": "5f2c5cde-…",
    "attributes": { "name": "Portal verification hero", … }
  },
  {
    "type": "taxonomy_term--tags",
    "id": "dbc476ff-…",
    "attributes": { "name": "news", … }
  }
]
```
</Fragment>

**`included`**: the referenced entries themselves, present only when the request used `?include=` **and** at least one named relationship resolved to an entity; an `include` that matches nothing (every relationship empty) omits the key entirely rather than sending `[]`, so read it as `doc.included ?? []`. Each entry:

- has the same `type` / `id` / `attributes` / `relationships` shape as a `data` resource,
- is deduplicated: one entry per resource, however many relationships point to it,
- is matched back to a relationship by its `type` + `id`.

<Ref>[Query parameter reference → include](/source-cms/reference/query-parameters/#include-related-resources-in-one-request)</Ref>
</ApiRow>

<ApiRow id="meta">
<Fragment slot="code">
```jsonc
"meta": { "count": 4 }
```
</Fragment>

**`meta.count`**: collection responses only, the total number of matching entries, counted **before pagination** and **before access filtering**. It can therefore exceed what `data` actually contains (see [the empty-data case](#when-data-is-empty-but-the-collection-is-not) below).

<Ref>[Content API reference → omitted resources](/source-cms/reference/content-api/#omitted-resources)</Ref>
</ApiRow>

<ApiRow id="links">
<Fragment slot="code">
```jsonc
"links": {
  "self": { "href": "…/api/node/article?…" },
  "next": { "href": "…/api/node/article?page%5Boffset%5D=1&…" },
  "last": { "href": "…/api/node/article?page%5Boffset%5D=3&…" }
}
```
</Fragment>

**`links`**: where this document came from and how to page through it:

| Key | Appears |
|---|---|
| `self` | always |
| `next` / `prev` | when a next / previous page exists |
| `first` / `last` | on a paginated collection |

<Ref>[Query parameter reference → pagination](/source-cms/reference/query-parameters/#pagelimit-and-pageoffset-pagination)</Ref>
</ApiRow>

</ApiLayout>

## When `data` is empty but the collection is not

When access control hides entries (most commonly unpublished ones), the response stays `200` and signals the gap in `meta`. The slices below are a separate capture of the article collection (a different snapshot than the one above, hence the different `meta.count`), on a page whose two newest entries are unpublished.

<ApiLayout>

<ApiRow id="empty-data">
<Fragment slot="code">
```jsonc
"data": [],
"links": {
  "self": { … },
  "next": { … },
  "last": { … }
}
```
</Fragment>

**`data: []`**: nothing visible on *this* page, but an empty page is not the end of the collection: `links.next` still points onward, so keep following it.
</ApiRow>

<ApiRow id="omitted">
<Fragment slot="code">
```jsonc
"meta": {
  "count": 7,
  "omitted": {
    "detail": "Some resources have been omitted because of insufficient authorization.",
    "links": {
      "help": { "href": "https://www.drupal.org/docs/8/modules/json-api/filtering#filters-access-control" },
      "item--IYrGUC8": {
        "href": "…/api/node/article/c59ab903-…",
        "meta": {
          "rel": "item",
          "detail": "The current user is not allowed to GET the selected resource. The 'view any unpublished content' permission is required."
        }
      }
    }
  }
}
```
</Fragment>

**`meta.omitted`**: `meta.count` (7) disagrees with `data.length` on purpose: `count` is the whole collection, `data` is only what you may see. `omitted.links` names why each hidden resource is missing, one link per entry.

<Ref>[Content API reference → omitted resources](/source-cms/reference/content-api/#omitted-resources)</Ref>
</ApiRow>

</ApiLayout>

## The error document

On failure, `errors` replaces `data` entirely. Here, a filter on a field that doesn't exist returns `400 Bad Request`:

<ApiLayout>

<ApiRow id="errors">
<Fragment slot="code">
```jsonc
"errors": [
  {
    "title": "Bad Request",   // generic name of the status code
    "status": "400",          // the HTTP status, as a string
    // the member to read: names exactly what was wrong
    "detail": "Invalid nested filtering. The field `nonexistent_field`, given in the path `nonexistent_field`, does not exist.",
    "links": {
      "via":  { "href": "…/api/node/article?filter…" },
      "info": { "href": "https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1" }
    }
  }
]
```
</Fragment>

**`errors`**: replaces `data` on failure; one object per error, every member a string:

| Member | Holds |
|---|---|
| `detail` | the diagnostic: names exactly what was wrong (read this first) |
| `title` | the generic status name, e.g. `"Bad Request"` |
| `status` | the HTTP status as a string, e.g. `"400"` |
| `links` | `via` (the offending request) and `info` (the spec for that status) |

<Ref>[Content API reference → error codes](/source-cms/reference/content-api/#error-codes) for the shared table, [authentication reference](/source-cms/reference/authentication/) for 401/403, and [query parameter reference → 400 responses](/source-cms/reference/query-parameters/#400-responses-for-malformed-queries) for malformed queries</Ref>
</ApiRow>

</ApiLayout>

## Used in

- [Content API quickstart](/source-cms/content-api/quickstart/): first sight of `data`, `attributes`, `meta.count`
- [Querying guide](/source-cms/content-api/guide/): `relationships` vs. `included`, pagination links, omissions
- [First fetch](/source-cms/content-api/first-fetch/) and [direct-fetch guide](/source-cms/content-api/fetch-content/): reading `attributes` and `included` from frontend code
- [Creating & updating content via the API](/source-cms/content-api/writing-content/): the error document on rejected writes
