> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://contentful.com/developers/docs/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://contentful.com/developers/docs/_mcp/server.

# GraphQL Content API

The GraphQL Content API provides a GraphQL API interface to the content from Contentful.
Each Contentful space comes with a GraphQL schema based on its content model.
This GraphQL schema is generated at request time and is always up-to-date with the current status of the space.

You can use this API to consume both published and non-published content. Read more about this in the [previewing content section](#previewing-content).

> **Info**
>
> **Note:** For EU data residency customers, the Base URL is **[https://graphql.eu.contentful.com](https://graphql.eu.contentful.com)**.

## Basic API Information

API Base URL `https://graphql.contentful.com`
*This is a read-only API*

### Basic API Information

The Contentful GraphQL Content API is available at:

```
https://graphql.contentful.com/content/v1/spaces/{SPACE}
```

It is also available for specific [environments](/concepts/multiple-environments) at:

```
https://graphql.contentful.com/content/v1/spaces/{SPACE}/environments/{ENVIRONMENT}
```

> **Info**
>
> **Disclaimer:** GraphQL Content API is available on all spaces for customers on [current pricing plans](https://www.contentful.com/pricing/). If you are on a legacy plan, contact [Customer Support](https://www.contentful.com/support/) to upgrade.

## HTTP Methods

The GraphQL Content API supports both GET and POST methods.

This is the query used in both examples below:

```graphql
query($preview: Boolean){
  blogCollection(preview: $preview){
    items{
      title
    }
  }
}
```

### POST

The HTTPS POST method is more flexible and recommended. The query should be sent as a property in a JSON payload in the body of the POST request with the property name `query`. Any required variables are added as an additional JSON property to the payload with the property name `variables`.

```bash
curl -g \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {TOKEN}" \
-d  '{"query":"query($preview:Boolean){blogCollection(preview:$preview){items{title}}}","variables":{"preview":true}}' \
https://graphql.contentful.com/content/v1/spaces/{SPACE}/environments/{ENVIRONMENT}
```

Supported values for the `Content-Type` header:

* `application/json`
* `application/json; charset=UTF-8`
* `application/x-www-form-urlencoded`
* `application/x-www-form-urlencoded; charset=UTF-8`

Using `application/json` is encouraged.

### GET

The HTTPS GET method requires that the query is included in the URL string as a parameter. You can also send any required variables in an additional `variables` parameter in JSON format.

```bash
curl -g \
-X GET \
-H "Content-Type: application/json" \
-H "Authorization: Bearer {TOKEN}" \
'https://graphql.contentful.com/content/v1/spaces/{SPACE}/environments/{ENVIRONMENT}?query=query($preview:Boolean){blogCollection(preview:$preview){items{title}}}&variables={"preview":true}'
```

## Authentication

Any client using the API needs to provide an access token in either:

* The `Authorization` header, specifically `Authorization: Bearer MY_TOKEN`.
* The `access_token` URL query parameter.

The token must have access to the space and environment you're targeting. For example, if you create an access token that only has access to the `master` environment of your space, you cannot use that token to access content from any other environment or space.

To learn more about authentication in Contentful and how to create your own access tokens, see the [Authentication reference documentation](/references/authentication).

## API rate limits

API rate limits specify the number of requests a client can make to Contentful APIs in a specific time frame.
Every request counts against a per-second rate limit.

There are no limits enforced on requests that hit our CDN cache, i.e. the request doesn't count towards your rate limit
and you can make an unlimited amount of cache hits. For requests that do hit the GraphQL Content API, a rate limit of **55** requests per second is enforced. Higher rate limits may apply depending on your current plan.

When a client gets rate limited, the API responds with the **429 Too Many Requests** HTTP status code
and sets the `X-Contentful-RateLimit-Reset` header that tells the client when it can make its next single request.
The value of this header is an integer specifying the time before the limit resets and another request will be accepted.
As the client is rate-limited per second, the header will return 1, which means the next second.

**Example**

The current rate limit for a client is the default 55 per second.
Client: 85 uncached requests in 1 second

```
HTTP/1.1 429
X-Contentful-RateLimit-Reset: 1
```

Meaning: wait 1 second before making more requests.

The following table lists all headers returned in every response by the GraphQL API which give a client information on rate limiting:

| Header                                | Description                                                   |
| ------------------------------------- | ------------------------------------------------------------- |
| `X-Contentful-RateLimit-Second-Limit` | The maximum amount of requests which can be made in a second. |
| `X-Contentful-RateLimit-Reset`        | The number of seconds until the next request can be made.     |

## Query complexity limits

> **Info**
>
> **Disclaimer:** The default complexity limit for collections is 100. The query complexity is calculated as the maximum number of entries and assets a query can potentially return.

Query complexity limits specify the amount of data a client can request in one request. You can currently request up to 11000 entities in one request.

**Example 1**

```graphql
query {
  lessonCollection(limit: 20) {
    items {
      title
    }
  }
}
```

The query above can return up to 20 `Lessons`. The query complexity is 20.

**Example 2**

```graphql
query {
  lessonCollection(limit: 20) {
    items {
      title
      imageCollection(limit: 10) {
        title
        url
      }
    }
  }
}
```

The query above can return up to 20 `Lessons` and up to 200 `Assets` (up to 10 for each of 20 `Lessons`). The query complexity is 220.

**Example 3**

```graphql
query {
  lessonCollection(limit: 20) {
    items {
      title
      participantsCollection (limit: 10) {
        ... on Person {
          name
          imageCollection(limit: 3) {
            title
            url
          }
        }
        ... on Pet {
          name
          imageCollection(limit: 5) {
            title
            url
          }
        }
      }
    }
  }
}
```

The query above can return up to 20 `Lessons`, up to 200 `Participants` (up to 10 for each of 20 `Lessons`) and up to 1000 `Assets` (up to 5 for each of 200 `Participants`). The query complexity is 1220.

Parameters such as `locale`, `useFallbackLocale`, `preview`, sorting, filtering or image transformations do not change query complexity.

The API sets the `X-Contentful-Graphql-Query-Cost` response header to the calculated query complexity value.

When a client gets query complexity limited, the API responds with a `TOO_COMPLEX_QUERY` error.

## Query size limits

Query size limits specify the maximum size of the query parameter for GET requests and the total payload size for POST requests. This limit is **8kb**.

This limit includes whitespace and newline characters. Removing semantically unnecessary whitespaces and newline characters before sending a request can lower the query size. You can reduce the query size without manual editing using GraphQL minifiers, such as [GQLMin](https://github.com/drwpow/gqlmin).

When the query size of a request exceeds the limit, the API returns a `QUERY_TOO_BIG` error.

> **Info**
>
> **Note:** You can use [automatic persisted queries](/references/graphql/automatic-persisted-queries) to bypass this limit if you are a customer on our Premium plan and above.

## Rich Text

Rich Text fields work similarly when calculating complexity but have some special behaviour. The complexity of the `links` property in a `RichText` field is equal to the sum of the maximum number of allowed linked entries and assets in the validation settings for the field.

In the following example the `richText` field is configured with a maximum limit of 5 embedded inline entries and a maximum of 0 of all other types of embedded entries or assets:

```graphql
query{
  articleCollection(limit: 100) {
    items{
      title
      bodyRichText {
        json
        links {
          entries {
            inline {
              sys {
                id
              }
            }
          }
        }
      }
    }
  }
}
```

The query above can return up to 100 `article` entries and up to 500 `links` (up to 5 for each of 100 `article`) due to the field validation settings. The query complexity is 500.

By default a Rich Text field has a total limit of 1000 linked entities of all supported types. This means that by default the `links` field in each Rich Text entry has a complexity of 1000.

## Tags

The [ContentfulMetadata](/references/graphql/schema-generation#contentfulmetadata-field) tags field calculates its complexity in a special way. The complexity of the tags property in the `ContentfulMetadata` field is 1 for every entry or asset being queried for. This complexity cost remains the same regardless of the number of tags returned.

```graphql
query {
  articleCollection(limit: 100) {
    items{
      title
      contentfulMetadata {
        tags {
            id
            name
        }
      }
    }
  }
}
```

The query above can return up to 100 `Articles` and up to 100 tags (up to 1 for each of 100 `Articles`). The query complexity is 200.

## Previewing content

Accessing non-published content can be useful when you want to preview how a new article will look before publishing it. The GraphQL API gives you the control to choose whether you want to access published or non-published content in a very granular fashion.

To control whether you get published or non-published content you have to use the `preview` argument, available to both [single resource fields](/references/graphql/single-resource-fields) and [collection fields](/references/graphql/collection-fields). This argument cascades, meaning that all the references resolved from a resource with `preview: true` are also showing preview content, unless explicitly overridden:

```graphql
query {
  houseCollection (preview: true) {
    items {
      houseNumber
      numberOfRooms
      owner {
        # content for "owner" will also be non-published
      }
      architect (preview: false) {
        # content for "architect" will be published
      }
    }
  }
}
```

Any query that accesses non-published content requires a preview access token. This includes queries that mix preview and published content. Follow the [authentication](#authentication) section to learn how to generate a token and how to use it to authenticate the requests. Fields in queries that require access to non-published content but fail to provide a valid preview access token will be resolved with an `ACCESS_TOKEN_INVALID` error.

## Cursor pagination

Cursor pagination is an approach to paginating datasets in Contentful APIs. Unlike traditional offset-based pagination, which uses `skip` and `limit` parameters, cursor-based pagination uses opaque cursor tokens and dedicated next/prev links to mark the position in the dataset. This can dramatically improve performance, especially for datasets with large numbers of items.

Use cursor pagination whenever you need to traverse a whole collection, for example, exporting a space, running analytics over all entries, or processing every asset.

Use offset pagination (`skip`/`limit`) only for bounded, random-access cases, such as showing a specific page of results in a UI.

### How cursor pagination works

* **Initial request:**

  Collections of entries and assets which support cursor based pagination are exposed as cursor collection types. They support the same arguments and types as [collection types](/references/graphql/collection-fields#arguments), with the exception of `skip` and the addition of `pageNext`, `pagePrev` and `pages`.

  ```graphql
  type FriendlyUserCursorCollection {
    # ... additional types and args
    pageNext: String
    pagePrev: String
    pages: CursorPages!
    items: [FriendlyUser]!
  }

  type AssetCursorCollection {
    # ... additional types and args
    pageNext: String
    pagePrev: String
    pages: CursorPages!
    items: [Asset]!
  }

  type CursorPages {
    next: String
    prev: String
  }

  query {
    friendlyUserCursorCollection {
      items {
        name
      }
      pages {
        next
        prev
      }
    }
    assetCursorCollection {
      items {
        title
      }
      pages {
        next
        prev
      }
    }
  }
  ```

  The response contains:

  * `items`: The current page of resources.
  * `pages`: Contains `next` (and optionally `prev`) URLs for paginating forward and backward.

* **Paginate forward:**
  Use the `pages.next` URL from the previous response for the next page request:

  ```graphql
  query {
    friendlyUserCursorCollection(pageNext: {cursor_token}) {
      items {
        name
      }
      pages {
        next
        prev
      }
    }
    assetCursorCollection(pageNext: {cursor_token}) {
      items {
        title
      }
      pages {
        next
        prev
      }
    }
  }
  ```

  Continue this process until the `next` link is omitted, which means you've reached the end of the dataset.

* **Paginate backward:**
  If the response contains a `pages.prev` link, you can fetch previous pages:

  ```graphql
  query {
    friendlyUserCursorCollection(pagePrev: {cursor_token}) {
      items {
        name
      }
      pages {
        next
        prev
      }
    }
    assetCursorCollection(pagePrev: {cursor_token}) {
      items {
        title
      }
      pages {
        next
        prev
      }
    }
  }
  ```

* **Consistency:**
  All query parameters used in the initial request apart from `limit` are locked and encoded in the cursor token. They cannot be changed between pages.

  The `limit` parameter can be updated, and the new value will be persisted for subsequent pages until it's updated again.

  ```graphql
  query {
    friendlyUserCursorCollection(pageNext: {cursor_token}, limit: {limit}) {
      items {
        name
      }
      pages {
        next
        prev
      }
    }
    assetCursorCollection(pageNext: {cursor_token}, limit: {limit}) {
      items {
        title
      }
      pages {
        next
        prev
      }
    }
  }
  ```

  This can be useful when you need to adjust the page size, for example, if a page size (in bytes) exceeds the response limit.

* **Total count:**
  The response does not include a total count or a `skip` property. This is a key feature for performance, as the API avoids costly full counts on large datasets.

### Example response

```json
{
  "data": {
    "friendlyUserCursorCollection": {
      "items": ["..."],
      "pages": {
        "next": "0IWECT7w....",
        "prev": null
      }
    },
    "assetCursorCollection": {
      "items": ["..."],
      "pages": {
        "next": "3sibGlta....",
        "prev": null
      }
    }
  }
}
```

### Advantages

* Considerable performance improvements, particularly for large datasets.

If your environment contains tens of thousands of entries or assets, consider switching to cursor pagination — it can make requests to fetch lists of entries or assets much faster.

If you don't paginate at all and don't need the `total` property from the response, we highly recommend using cursor collection types.

### Limitations

* **The `total` property is not included in API responses.** Make sure you don't rely on it in your workflow before making the switch.

### Enablement and access

* The feature is currently available in the CMA, CPA, CDA and Graph API.