Content API (REST)
Entries
Listing, fetching and creating content entries.
Entries are your content. The REST API lists and fetches them by content type and slug, lists them across every type, and creates new drafts. Updating, publishing and deleting entries is done over GraphQL.
Every endpoint here accepts ?environment=staging and ?locale=.
List entries of a type
GET /v1/content/:typeThe endpoint most sites use. :type is the content type's slug.
curl "https://api.graphcms.io/v1/content/blog-posts?limit=10&offset=0" \ -H "Authorization: Bearer $GRAPH_API_KEY"By default it returns published entries in en-US, newest first. Pass status or language to change that; see Querying.
{ "success": true, "data": [ { "id": "k3x9m2p7q1a4", "title": "Hello, world", "slug": "hello-world", "status": "published", "stage": null, "language": "en-US", "locale": "en-US", "publishedAt": "2026-09-30T14:02:11.000Z", "createdAt": "2026-09-30T13:55:40.000Z", "updatedAt": "2026-09-30T14:02:11.000Z", "fields": { "excerpt": "Our first post.", "body": "<p>Welcome to the blog.</p>" } } ], "pagination": { "limit": 10, "offset": 0, "total": 1 }}| Parameter | Default | Description |
|---|---|---|
limit | 10 | Entries to return, 1 to 100. |
offset | 0 | Entries to skip. |
status | published | published, draft or archived. |
language | en-US | Only entries written in this language. |
locale | en-US | Return translated text fields in this locale. |
environment | production | production or staging. |
Needs content.view. Returns 404 with Content type not found for an unknown slug.
Get one entry
GET /v1/content/:type/:slugcurl https://api.graphcms.io/v1/content/blog-posts/hello-world \ -H "Authorization: Bearer $GRAPH_API_KEY"{ "success": true, "data": { "id": "k3x9m2p7q1a4", "title": "Hello, world", "slug": "hello-world", "status": "published", "stage": null, "language": "en-US", "locale": "en-US", "publishedAt": "2026-09-30T14:02:11.000Z", "createdAt": "2026-09-30T13:55:40.000Z", "updatedAt": "2026-09-30T14:02:11.000Z", "contentType": { "id": "p8d2k4m9x0s1", "name": "Blog posts", "slug": "blog-posts" }, "fields": { "excerpt": "Our first post.", "body": "<p>Welcome to the blog.</p>", "coverImage": null } }}stage is the workflow stage the entry is at, as { "id", "name" }, or null when its content type has no workflow (or the entry is archived).
fields has every field of the content type, in its order, so every entry of a type has the same shape: a field with nothing saved is null (or [] for a repeater or a multiple reference). Section fields only lay out the editor and aren't included.
| Parameter | Default | Description |
|---|---|---|
language | en-US | Which language version of the entry to return. |
locale | en-US | Return translated text fields in this locale. |
environment | production | production or staging. |
Needs content.view. Returns 404 with Content type not found or Content entry not found.
This endpoint returns the entry whatever its status, drafts included. If your site should only show published content, check
data.status === 'published'before rendering, or use the list endpoint, which filters to published by default.
List entries across types
GET /v1/contentEvery entry in the environment, newest first, each with the content type it belongs to. Useful for sitemaps, search indexes and sync jobs.
curl "https://api.graphcms.io/v1/content?type=blog-posts&limit=50" \ -H "Authorization: Bearer $GRAPH_API_KEY"{ "data": [ { "id": "k3x9m2p7q1a4", "title": "Hello, world", "slug": "hello-world", "status": "published", "stage": null, "contentType": { "id": "p8d2k4m9x0s1", "name": "Blog posts", "slug": "blog-posts" }, "locale": "en-US", "fields": { "excerpt": "Our first post." }, "createdAt": "2026-09-30T13:55:40.000Z", "updatedAt": "2026-09-30T14:02:11.000Z", "publishedAt": "2026-09-30T14:02:11.000Z" } ], "pagination": { "limit": 50, "offset": 0, "total": 1 }}| Parameter | Default | Description |
|---|---|---|
type | none | Only entries of this content type slug. |
limit | 50 | Entries to return, 1 to 100. |
offset | 0 | Entries to skip. |
locale | en-US | Return translated text fields in this locale. |
environment | production | production or staging. |
Unlike the per-type list, this returns entries of every status; filter on status yourself. Needs content.view. This endpoint doesn't send CORS headers, so call it from a server.
Create an entry
POST /v1/contentCreates a draft entry. Needs content.edit.
curl https://api.graphcms.io/v1/content \ -X POST \ -H "Authorization: Bearer $GRAPH_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "contentTypeSlug": "blog-posts", "title": "Release notes for October", "slug": "release-notes-october", "fields": { "excerpt": "What shipped this month.", "body": "<p>Lots.</p>" } }'| Body property | Required | Description |
|---|---|---|
contentTypeSlug | yes | Slug of the content type. |
title | yes | The entry's title. |
slug | no | Defaults to the title, lowercased with non-alphanumerics turned into hyphens. |
fields | no | Object keyed by field slug. Unknown slugs are ignored. Reference fields take entry IDs; see Reference fields. |
{ "data": { "id": "z0q8w1e2r3t4", "title": "Release notes for October", "slug": "release-notes-october", "status": "draft", "contentType": { "id": "p8d2k4m9x0s1", "name": "Blog posts", "slug": "blog-posts" }, "fields": { "excerpt": "What shipped this month.", "body": "<p>Lots.</p>" }, "createdAt": "2026-10-09T09:12:00.000Z", "updatedAt": "2026-10-09T09:12:00.000Z" }, "message": "Content entry created successfully"}Errors are 400 for a body that isn't JSON or is missing contentTypeSlug or title, a non-string slug, a fields value that isn't an object, or a reference to an entry that doesn't exist or isn't allowed, and 404 for an unknown content type.
To create an entry that is published straight away, set its language, or update and delete entries, use createContentEntry, updateContentEntry and deleteContentEntry over GraphQL.
Import and export
In the dashboard, Import / export on the entries page exports the entries your current filters show (up to 5,000) as CSV or Excel (.xlsx). It also imports a CSV or .xlsx file into one content type. Other file types aren't accepted, and a file can have up to 2,000 rows.
An export has the columns ID, Title, Slug, Status, Content type, Created, Updated and Published, then one column per field, named after the field. An export can be imported back as it is.
Importing takes three steps in one dialog:
- Upload. Drop a file on the dialog, or browse for one.
- Map columns. Choose the content type to import into. Each column in the file is mapped to
Title,Slug,Statusor a field, by name. You can change each mapping or set a column to not be imported.Titlemust be mapped, and so must every required field. - Preview. Every row is checked before anything is saved. Each row shows whether it will be new, update an entry, be skipped, or has errors. Cells with a problem are highlighted, and the reason is shown next to the row. Go back to fix the mapping, or import only the rows that are ready.
Each row becomes a draft in the base language, unless its Status is draft, published or archived. An empty slug is made from the title. A row whose slug already exists is skipped, or updates that entry if you turn on Update entries whose slug already exists. A later row with the same slug as an earlier one is treated the same way.
| Field type | Cell value |
|---|---|
| Number | A number; thousands separators are ignored. |
| Boolean | true/false, yes/no, 1/0 or on/off. |
| Date & Time | A date, e.g. 2026-10-12 or 2026-10-12T09:30:00Z. A range is from / to. |
| Select | One of the field's option values. |
| Reference | The slugs (or IDs) of the entries it points at, separated by commas. |
| Media | The URL or ID of a file in the media library. |
| JSON, Repeater | JSON text. |
A blank cell leaves the field empty, and clears it when updating an entry.