
Contentful Api
- 171 installs
- 37 repo stars
- Updated August 4, 2026
- contentful/skills
Make direct HTTP/curl calls to Contentful's CMA, CDA, Preview, Images, and GraphQL APIs with correct auth, querying, and localization.
About
Provides a language-agnostic HTTP/curl reference for Contentful's Management, Delivery, Preview, Images, and GraphQL APIs. A developer uses it when making direct REST or GraphQL calls to Contentful.
- Language-agnostic curl/HTTP guide to CMA, CDA, Preview, Images, GraphQL
- Covers auth, versioning, pagination, includes, and localization
Contentful Api by the numbers
- 171 all-time installs (skills.sh)
- Ranked #2,257 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/contentful/skills --skill contentful-apiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 171 |
|---|---|
| repo stars | ★ 37 |
| Last updated | August 4, 2026 |
| Repository | contentful/skills ↗ |
What it does
Make direct HTTP/curl calls to Contentful's CMA, CDA, Preview, Images, and GraphQL APIs with correct auth, querying, and localization.
Files
Contentful REST API Guide
Language-agnostic guide for Contentful APIs using HTTP/curl.
Shared References
- [Authentication](references/authentication.md) — Token types, auth headers, API base URLs (US/EU)
- [HTTP Conventions](references/http-conventions.md) — Version locking, rate limits, pagination, errors, locale structure
Content Management API (CMA)
Read/write API for managing content, content types, assets, and environments.
Start here: references/content-management/overview.md
- **entries.md** — CRUD, publish/unpublish, versioning, query parameters
- **content-types.md** — Define/update content models, field types, validations
- **assets.md** — Upload, process, publish media files
- **environments.md** — Create, clone, manage environments and aliases
Content Delivery API (CDA)
Read-only API for fetching published content.
Start here: references/content-delivery/overview.md
- **querying.md** — Filters, search operators, pagination, ordering
- **includes-links.md** — Include parameter, link resolution
- **localization.md** — Locale parameter, fallback chains
- **sync.md** — Incremental content synchronization
Content Preview API
Draft + published content via same CDA endpoints, different host/token.
Reference: references/content-preview/overview.md
Images API
On-the-fly image transformations via URL parameters. No authentication needed.
Reference: references/images/overview.md
GraphQL API
Query content via GraphQL with CDA tokens.
Reference: references/graphql/overview.md
Quick Reference
# CMA: Create a draft entry
curl -X POST https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Content-Type: blogPost" \
-d '{"fields":{"title":{"en-US":"Hello"}}}'
# Then publish: PUT .../entries/{id}/published with X-Contentful-Version header
# CDA: Fetch entries
curl "https://cdn.contentful.com/spaces/{space_id}/environments/{env_id}/entries?content_type=blogPost" \
-H "Authorization: Bearer {cda_token}"{
"name": "@contentful/skill-contentful-api",
"version": "2.1.3",
"description": "Comprehensive Contentful REST API guide covering CMA, CDA, Preview, Images, and GraphQL APIs",
"license": "MIT",
"files": [
"SKILL.md",
"references/**"
]
}
Authentication
All Contentful APIs require authentication except the Images API.
Table of Contents
- Token Types
- Passing Tokens
- API Base URLs
- CDA Token
- Preview Token
- CMA Token
- OAuth Tokens
- Token Scopes
- Security
Token Types
| Token | Use With | Permissions | Where to Create |
|---|---|---|---|
| Content Delivery API (CDA) token | CDA, GraphQL, Images (optional) | Read published content | Settings → API keys |
| Content Preview API token | Preview API | Read draft + published content | Settings → API keys |
| Content Management API (CMA) token | CMA | Read/write all content and settings | Settings → CMA tokens |
| OAuth token | CMA | Scoped by OAuth app permissions | OAuth flow |
Passing Tokens
Authorization Header (Recommended)
curl https://cdn.contentful.com/spaces/{space_id}/environments/{environment_id}/entries \
-H "Authorization: Bearer {access_token}"Query Parameter
curl "https://cdn.contentful.com/spaces/{space_id}/environments/{environment_id}/entries?access_token={access_token}"Header method is preferred — query parameters may appear in logs.
API Base URLs
| API | US (Default) | EU |
|---|---|---|
| Content Delivery | cdn.contentful.com | cdn.eu.contentful.com |
| Content Preview | preview.contentful.com | preview.eu.contentful.com |
| Content Management | api.contentful.com | api.eu.contentful.com |
| Images | images.ctfassets.net | images.eu.ctfassets.net |
| Upload | upload.contentful.com | upload.eu.contentful.com |
| GraphQL | graphql.contentful.com | graphql.eu.contentful.com |
CDA Token
Read-only access to published content. Safe to use in client-side code.
# Fetch published entries
curl https://cdn.contentful.com/spaces/{space_id}/environments/master/entries \
-H "Authorization: Bearer YOUR_CDA_TOKEN"Preview Token
Same endpoints as CDA but returns draft and changed content. Never expose in client-side code.
# Fetch draft + published entries
curl https://preview.contentful.com/spaces/{space_id}/environments/master/entries \
-H "Authorization: Bearer YOUR_PREVIEW_TOKEN"CMA Token (Personal Access Token)
Full read/write access to the space. Never expose in client-side code.
# Create an entry
curl -X POST https://api.contentful.com/spaces/{space_id}/environments/master/entries \
-H "Authorization: Bearer YOUR_CMA_TOKEN" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Content-Type: blogPost" \
-d '{"fields":{"title":{"en-US":"Hello World"}}}'Create CMA tokens at: Settings → CMA tokens → Generate personal token
OAuth Tokens
For apps acting on behalf of users. Use the OAuth 2.0 flow:
1. Register app at Contentful App Definition 2. Redirect user to: https://be.contentful.com/oauth/authorize?response_type=token&client_id={client_id}&redirect_uri={redirect_uri}&scope=content_management_manage 3. User authorizes, token returned in redirect URL fragment
# Use OAuth token same as CMA token
curl https://api.contentful.com/spaces/{space_id}/environments/master/entries \
-H "Authorization: Bearer YOUR_OAUTH_TOKEN"Token Scopes
- CDA token: Read published content only. Cannot read drafts, cannot write.
- Preview token: Read all content (published + draft). Cannot write.
- CMA token: Full read/write. Can manage content, content types, environments, etc.
- OAuth token: Scoped by app permissions. Typically
content_management_manage.
Security
- Never commit tokens to version control
- Use environment variables:
CONTENTFUL_DELIVERY_TOKEN,CONTENTFUL_MANAGEMENT_TOKEN - CDA tokens are safe for client-side use (read-only, published content)
- CMA and Preview tokens must be kept server-side only
- Rotate CMA tokens periodically via Settings → CMA tokens
Includes & Links
How the CDA handles references between entries and assets.
Table of Contents
Link Structure
References in Contentful use a standard link object:
{
"sys": {
"type": "Link",
"linkType": "Entry",
"id": "referenced-entry-id"
}
}linkType is either Entry or Asset.
Include Parameter
The include parameter controls how many levels of linked entries/assets the API resolves and returns in the includes object.
# No link resolution — linked entries returned as link objects only
curl "...?content_type=blogPost&include=0" -H "Authorization: Bearer {cda_token}"
# Default (1 level) — first-level linked entries/assets included
curl "...?content_type=blogPost&include=1" -H "Authorization: Bearer {cda_token}"
# Deep resolution (max 10)
curl "...?content_type=blogPost&include=5" -H "Authorization: Bearer {cda_token}"| Value | Behavior |
|---|---|
0 | No includes. Links are returned as { "sys": { "type": "Link", ... } } objects |
1 (default) | Direct references resolved into includes |
2-10 | Deeper references resolved (e.g., entry → author → company) |
Maximum include depth is 10.
Response Structure
When include >= 1, the response contains an includes object with resolved entities:
{
"sys": { "type": "Array" },
"total": 10,
"items": [
{
"sys": { "id": "post-1", "type": "Entry", ... },
"fields": {
"title": "My Post",
"author": {
"sys": { "type": "Link", "linkType": "Entry", "id": "author-1" }
},
"heroImage": {
"sys": { "type": "Link", "linkType": "Asset", "id": "image-1" }
}
}
}
],
"includes": {
"Entry": [
{
"sys": { "id": "author-1", "type": "Entry", ... },
"fields": { "name": "Jane Doe", "email": "jane@example.com" }
}
],
"Asset": [
{
"sys": { "id": "image-1", "type": "Asset", ... },
"fields": {
"title": "Hero Image",
"file": {
"url": "//images.ctfassets.net/space_id/image-1/token/hero.jpg",
"contentType": "image/jpeg",
"details": { "size": 102400, "image": { "width": 1920, "height": 1080 } }
}
}
}
]
}
}Key points:
itemscontains the queried entries with link stubs in fieldsincludes.Entrycontains all resolved linked entries (flattened)includes.Assetcontains all resolved linked assets (flattened)- An entity appears in
includesonly once even if referenced multiple times
Resolving Links
To resolve a link, match the link's sys.id against entries in the includes object:
1. Entry field has: { "sys": { "type": "Link", "linkType": "Entry", "id": "author-1" } }
2. Find in includes.Entry: the object where sys.id === "author-1"
3. That object contains the full entry with fieldsFor arrays of links, resolve each link individually:
{
"relatedPosts": [
{ "sys": { "type": "Link", "linkType": "Entry", "id": "post-2" } },
{ "sys": { "type": "Link", "linkType": "Entry", "id": "post-3" } }
]
}Look up each ID in includes.Entry.
Nested Resolution
With include=2, the includes object contains both direct and second-level references. For example, if a post links to an author who links to a company:
include=1: includes contains author
include=2: includes contains author AND companyAll resolved entities are flat in the includes arrays regardless of depth.
Unresolvable Links
A link may be unresolvable if:
- The linked entry/asset was deleted
- The linked entry is not published (CDA only shows published content)
- Insufficient permissions
Unresolvable links remain as link stubs in the response — they won't appear in includes:
{
"fields": {
"author": {
"sys": { "type": "Link", "linkType": "Entry", "id": "deleted-author" }
}
}
}If "deleted-author" is not found in includes.Entry, the link is unresolvable.
Detecting Unresolvable Links
Check if a field value is a link stub (has sys.type === "Link") versus a resolved entry (has sys.type === "Entry" and fields):
If field.sys.type === "Link" → unresolved (look up in includes or mark as missing)
If field.sys.type === "Entry" → resolved (has fields)Note: The raw CDA response always returns link stubs in items and resolved entities in includes. Some SDKs auto-resolve links inline, but the raw REST response keeps them separate.
Performance Tips
1. Use `include=0` for list views where you only need titles/slugs 2. Use `include=1` (default) for detail views with direct references 3. Avoid high include values — include=10 can return very large responses 4. Use `select` to limit which fields are returned 5. Resolve links client-side — build a lookup map from includes for efficient resolution:
Map entries from includes.Entry by sys.id → O(1) lookup per link
Map assets from includes.Asset by sys.id → O(1) lookup per linkCommon Patterns
List view (no links needed)
curl "...?content_type=blogPost&select=fields.title,fields.slug,sys.id&include=0" \
-H "Authorization: Bearer {cda_token}"Detail view (resolve author and images)
curl "...entries/{entry_id}?include=2" \
-H "Authorization: Bearer {cda_token}"Find entries referencing a specific entry
curl "...?links_to_entry={entry_id}" \
-H "Authorization: Bearer {cda_token}"Find entries referencing a specific asset
curl "...?links_to_asset={asset_id}" \
-H "Authorization: Bearer {cda_token}"Localization
How to fetch content in specific locales via the Content Delivery API.
Table of Contents
Locale Parameter
Default Locale
Without the locale parameter, the API returns content in the space's default locale:
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id}" \
-H "Authorization: Bearer {cda_token}"Response fields contain resolved values directly:
{
"fields": {
"title": "Hello World",
"body": "Post content"
}
}Specific Locale
Request content in a specific locale:
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id}?locale=de-DE" \
-H "Authorization: Bearer {cda_token}"{
"fields": {
"title": "Hallo Welt",
"body": "Beitragsinhalt"
}
}Query with Locale
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries?content_type=blogPost&locale=fr-FR" \
-H "Authorization: Bearer {cda_token}"All Locales
Use locale=* to get all locales in a single response:
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id}?locale=*" \
-H "Authorization: Bearer {cda_token}"Response fields become locale-keyed (same structure as CMA):
{
"fields": {
"title": {
"en-US": "Hello World",
"de-DE": "Hallo Welt",
"fr-FR": "Bonjour le monde"
},
"body": {
"en-US": "English content",
"de-DE": "German content"
}
}
}Non-localized fields only appear under the default locale.
Fallback Chains
Each locale can have a fallback locale configured in space settings. If content doesn't exist in the requested locale, Contentful returns the fallback locale's value instead.
Example fallback chain:
fr-FR → en-US → null
de-DE → en-US → null
ja-JP → en-US → nullIf a German translation doesn't exist for a field, the English value is returned.
Checking for Fallbacks
Use locale=* to see which locales actually have content:
curl "...?locale=*" -H "Authorization: Bearer {cda_token}"If fields.title only has {"en-US": "Hello"} and no de-DE key, the German locale is using the English fallback.
Querying by Locale
Filter by localized field value
# Search German titles
curl "...?content_type=blogPost&locale=de-DE&fields.title[match]=Hallo" \
-H "Authorization: Bearer {cda_token}"Full-text search
Full-text query searches all locales by default:
curl "...?content_type=blogPost&query=contentful" \
-H "Authorization: Bearer {cda_token}"Combine with locale to search within a specific locale:
curl "...?content_type=blogPost&locale=fr-FR&fields.title[match]=bonjour" \
-H "Authorization: Bearer {cda_token}"Available Locales
List Locales
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/locales" \
-H "Authorization: Bearer {cda_token}"Response:
{
"sys": { "type": "Array" },
"items": [
{
"code": "en-US",
"name": "English (United States)",
"default": true,
"fallbackCode": null
},
{
"code": "de-DE",
"name": "German (Germany)",
"default": false,
"fallbackCode": "en-US"
},
{
"code": "fr-FR",
"name": "French (France)",
"default": false,
"fallbackCode": "en-US"
}
]
}Localized Assets
Assets can have different files per locale:
curl "...?locale=*" -H "Authorization: Bearer {cda_token}"{
"fields": {
"title": { "en-US": "User Guide", "de-DE": "Benutzerhandbuch" },
"file": {
"en-US": { "url": "//images.ctfassets.net/.../guide-en.pdf", "fileName": "guide-en.pdf" },
"de-DE": { "url": "//images.ctfassets.net/.../guide-de.pdf", "fileName": "guide-de.pdf" }
}
}
}Best Practices
1. Omit `locale` for default — don't specify locale for the default language 2. *Use `locale= sparingly** — significantly increases response size 3. **Cache by locale** — implement separate caches per locale 4. **Check fallback chain** — understand your space's locale fallback configuration 5. **Use /locales` endpoint** — fetch available locales dynamically rather than hardcoding
Content Delivery API Overview
The CDA is a read-only API for delivering published content to apps and websites.
Base URL
- US:
https://cdn.contentful.com - EU:
https://cdn.eu.contentful.com
Most endpoints follow: https://cdn.contentful.com/spaces/{space_id}/environments/{environment_id}/.... The space info endpoint uses /spaces/{space_id} without an environment segment.
Authentication
Use a CDA access token via Authorization header or query parameter:
curl https://cdn.contentful.com/spaces/{space_id}/environments/master/entries \
-H "Authorization: Bearer {cda_token}"See authentication.md for token types and creation.
Available Endpoints
| Endpoint | Description |
|---|---|
GET .../entries | List/query entries |
GET .../entries/{id} | Get single entry |
GET .../assets | List/query assets |
GET .../assets/{id} | Get single asset |
GET .../content_types | List content types |
GET .../content_types/{id} | Get single content type |
GET .../locales | List available locales |
GET .../tags | List content tags |
GET .../sync | Sync API for incremental updates |
GET /spaces/{space_id} | Get space info |
Quick Start
Get entries
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries?content_type=blogPost&limit=10" \
-H "Authorization: Bearer {cda_token}"Response:
{
"sys": { "type": "Array" },
"total": 42,
"skip": 0,
"limit": 10,
"items": [
{
"sys": { "id": "entry-id", "type": "Entry", "contentType": { "sys": { "id": "blogPost" } }, ... },
"fields": { "title": "Hello World", "slug": "hello-world" }
}
],
"includes": {
"Entry": [ ... ],
"Asset": [ ... ]
}
}Get single entry
curl https://cdn.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id} \
-H "Authorization: Bearer {cda_token}"Get assets
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/assets?limit=10" \
-H "Authorization: Bearer {cda_token}"CDA vs CMA Response Differences
- CDA fields return the resolved locale value directly:
"title": "Hello" - CMA fields are always locale-keyed:
"title": { "en-US": "Hello" } - CDA resolves linked entries/assets into an
includesobject - CDA only returns published content (use Preview API for drafts)
Topics
- [Querying](querying.md) — Filters, search, pagination, ordering
- [Includes & Links](includes-links.md) — Include parameter, link resolution
- [Localization](localization.md) — Locale parameter, fallback chains
- [Sync](sync.md) — Incremental content synchronization
Reference
- CDA API Reference
- Authentication
- HTTP Conventions
Querying
Comprehensive guide to querying entries and assets via the Content Delivery API.
Table of Contents
- Basic Query
- Pagination
- Ordering
- Select Fields
- Equality and Inequality
- Inclusion Operators
- Existence Check
- Comparison Operators
- Date Ranges
- Full-Text Search
- Array Fields
- Location Queries
- Link Queries
- System Field Queries
- Asset Queries
- Complex Examples
Basic Query
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries?content_type=blogPost" \
-H "Authorization: Bearer {cda_token}"Always specify content_type when querying by field — it's required for field-based filters and improves performance.
Pagination
# Page 1
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries?content_type=blogPost&limit=100&skip=0" \
-H "Authorization: Bearer {cda_token}"
# Page 2
curl "...?content_type=blogPost&limit=100&skip=100" \
-H "Authorization: Bearer {cda_token}"| Parameter | Default | Max | Description |
|---|---|---|---|
limit | 100 | 1000 | Items per page |
skip | 0 | — | Items to skip |
Continue until items.length < limit or skip >= total.
Ordering
# Ascending
...?order=fields.publishDate
# Descending (prefix with -)
...?order=-fields.publishDate
# Multiple fields
...?order=-fields.publishDate,fields.title
# System fields
...?order=sys.createdAt
...?order=-sys.updatedAtOrdering by field requires content_type parameter.
Select Fields
Return only specific fields to reduce payload:
# Select specific fields (sys.id is always included)
...?content_type=blogPost&select=fields.title,fields.slug,sys.id
# Only sys metadata
...?content_type=blogPost&select=sysEquality and Inequality
# Exact match
...?content_type=blogPost&fields.slug=hello-world
# Not equal
...?content_type=blogPost&fields.slug[ne]=hello-worldInclusion Operators
# In list (OR)
...?content_type=blogPost&fields.slug[in]=post-1,post-2,post-3
# Not in list
...?content_type=blogPost&fields.slug[nin]=post-1,post-2Existence Check
# Field exists (has value)
...?content_type=blogPost&fields.author[exists]=true
# Field doesn't exist (is empty)
...?content_type=blogPost&fields.author[exists]=falseComparison Operators
# Greater than
...?content_type=blogPost&fields.viewCount[gt]=1000
# Greater than or equal
...?content_type=blogPost&fields.viewCount[gte]=1000
# Less than
...?content_type=blogPost&fields.viewCount[lt]=1000
# Less than or equal
...?content_type=blogPost&fields.viewCount[lte]=1000Date Ranges
# After date
...?content_type=blogPost&fields.publishDate[gte]=2024-01-01
# Before date
...?content_type=blogPost&fields.publishDate[lte]=2024-12-31
# Date range
...?content_type=blogPost&fields.publishDate[gte]=2024-01-01&fields.publishDate[lte]=2024-12-31
# System date fields (use ISO 8601)
...?sys.createdAt[gte]=2024-01-01T00:00:00ZFull-Text Search
Across all text fields
...?content_type=blogPost&query=contentful+cmsMultiple terms are AND-ed (both must match).
On a specific field
...?content_type=blogPost&fields.title[match]=contentful[match] performs a full-text search on the field value using Contentful's text search semantics and is not limited to prefix matches.
Array Fields
Contains all values
...?content_type=blogPost&fields.tags[all]=tech,javascriptContains any value
...?content_type=blogPost&fields.tags[in]=tech,design,businessLocation Queries
Near a point
Results sorted by distance from the point:
...?content_type=venue&fields.location[near]=40.7128,-74.0060Within a bounding box
# lat1,lon1 = bottom-left, lat2,lon2 = top-right
...?content_type=venue&fields.location[within]=40.7,-74.1,40.8,-73.9Link Queries
By linked entry ID
# Entries where author field links to specific entry
...?content_type=blogPost&fields.author.sys.id=author-idFind all entries linking to a specific entry
...?links_to_entry=entry-idFind all entries linking to a specific asset
...?links_to_asset=asset-idMaximum reference depth for queries: 2 levels.
System Field Queries
# By entry ID
...?sys.id=entry-id
...?sys.id[in]=id1,id2,id3
# By content type
...?sys.contentType.sys.id=blogPost
# By creation date
...?sys.createdAt[gte]=2024-01-01T00:00:00Z
# By update date
...?sys.updatedAt[gte]=2024-01-01T00:00:00Z
# By revision
...?sys.revision[gte]=2Asset Queries
# By MIME type
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/assets?fields.file.contentType=image/png" \
-H "Authorization: Bearer {cda_token}"
# Images only
...?fields.file.contentType[in]=image/jpeg,image/png,image/gif,image/webp
# By file size (bytes)
...?fields.file.details.size[lt]=1048576
# By image dimensions
...?fields.file.details.image.width[gte]=1920
...?fields.file.details.image.height[gte]=1080
# By title
...?fields.title[match]=heroComplex Examples
Latest featured blog posts
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries?content_type=blogPost&fields.featured=true&fields.publishDate[gte]=2024-01-01&order=-fields.publishDate&limit=10" \
-H "Authorization: Bearer {cda_token}"Posts by author
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries?content_type=blogPost&fields.author.sys.id=author-id&order=-fields.publishDate" \
-H "Authorization: Bearer {cda_token}"Search with pagination
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries?content_type=blogPost&query=contentful&limit=20&skip=0" \
-H "Authorization: Bearer {cda_token}"Recent images
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/assets?fields.file.contentType[in]=image/jpeg,image/png&order=-sys.createdAt&limit=20" \
-H "Authorization: Bearer {cda_token}"Query Limits
- Maximum
limitper request: 1000 - Maximum
includedepth: 10 - Maximum reference query depth: 2 levels
- Default
limit: 100 - Multiple filters are AND-ed together
Best Practices
1. Always specify `content_type` for field-based queries 2. Use `select` to reduce payload size 3. Use `limit` and `skip` for pagination 4. Prefer equality over `[match]` for better performance 5. Use `sys` field queries for filtering by metadata 6. Combine filters — all query parameters are AND-ed
Sync API
The Sync API enables incremental content synchronization. Instead of fetching all content repeatedly, sync once initially, then fetch only changes since the last sync.
Table of Contents
Initial Sync
Fetch all content for the first time:
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/sync?initial=true" \
-H "Authorization: Bearer {cda_token}"Response:
{
"sys": { "type": "Array" },
"items": [
{
"sys": { "type": "Entry", "id": "entry-1", "contentType": { "sys": { "id": "blogPost" } }, ... },
"fields": { "title": "Hello World" }
},
{
"sys": { "type": "Asset", "id": "asset-1", ... },
"fields": { "title": "Hero Image", "file": { ... } }
},
{
"sys": { "type": "DeletedEntry", "id": "entry-2", ... }
},
{
"sys": { "type": "DeletedAsset", "id": "asset-2", ... }
}
],
"nextSyncUrl": "https://cdn.contentful.com/spaces/{space_id}/environments/master/sync?sync_token=next-token-here"
}Store the nextSyncUrl (or extract sync_token from it) for subsequent syncs.
Subsequent Sync
Fetch only changes since the last sync:
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/sync?sync_token={token}" \
-H "Authorization: Bearer {cda_token}"Response contains only entries/assets that were created, updated, or deleted since the last sync.
{
"sys": { "type": "Array" },
"items": [
{
"sys": { "type": "Entry", "id": "entry-3", ... },
"fields": { "title": "New Post" }
},
{
"sys": { "type": "DeletedEntry", "id": "entry-1", ... }
}
],
"nextSyncUrl": "https://cdn.contentful.com/spaces/{space_id}/environments/master/sync?sync_token=newer-token"
}Paginated Sync
If there are too many results for a single response, the response includes nextPageUrl instead of (or in addition to) nextSyncUrl:
{
"items": [ ... ],
"nextPageUrl": "https://cdn.contentful.com/spaces/{space_id}/environments/master/sync?sync_token=page-2-token"
}Follow nextPageUrl to get the next page. When nextPageUrl is absent and nextSyncUrl is present, you've reached the last page. Store nextSyncUrl for the next sync cycle.
Loop:
1. Call sync URL
2. Process items
3. If nextPageUrl exists → go to step 1 with nextPageUrl
4. If nextSyncUrl exists → done, store nextSyncUrl for next syncSync Types
Items in the sync response have these sys.type values:
| Type | Description |
|---|---|
Entry | New or updated entry (full entry with fields) |
Asset | New or updated asset (full asset with fields) |
DeletedEntry | Entry was deleted (only sys metadata, no fields) |
DeletedAsset | Asset was deleted (only sys metadata, no fields) |
Filtered Sync
Limit the initial sync to specific content:
By type
# Only entries
...?initial=true&type=Entry
# Only assets
...?initial=true&type=Asset
# Only deletions (both entries and assets)
...?initial=true&type=DeletionBy content type
# Only entries of a specific content type
...?initial=true&type=Entry&content_type=blogPostNote: Filtered sync tokens are separate — a token from a filtered sync can only be used for subsequent syncs with the same filter.
Important Notes
- Sync always returns all locales (equivalent to
locale=*) - Sync does not support
include— links are not resolved. You get link stubs and must resolve manually - Sync tokens expire after ~90 days. If expired, start a new initial sync
- Sync returns entries with all fields, ignoring
select - Entries in sync responses contain the full entry, not a diff
Best Practices
1. Store sync tokens — persist nextSyncUrl or sync_token between runs 2. Handle all item types — process Entry, Asset, DeletedEntry, DeletedAsset 3. Follow pagination — always follow nextPageUrl before storing nextSyncUrl 4. Resolve links separately — sync doesn't include resolved references 5. Use filtered sync for large spaces — sync only the content types you need 6. Handle token expiry — fall back to initial sync if token is expired (API returns error)
Assets
Assets are media files (images, videos, documents). They require a three-step workflow: create → process → publish.
Table of Contents
- Create Asset from URL
- Upload Binary File
- Process Asset
- Publish Asset
- Complete Workflow
- Update Asset
- Get / List Assets
- Delete Asset
- Localized Assets
Create Asset from URL
Create an asset referencing an external file URL:
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-d '{
"fields": {
"title": { "en-US": "Hero Image" },
"description": { "en-US": "Main hero image for homepage" },
"file": {
"en-US": {
"contentType": "image/png",
"fileName": "hero.png",
"upload": "https://example.com/hero.png"
}
}
}
}'Upload Binary File
For direct file uploads, first upload to the Upload API, then create the asset referencing the upload:
# 1. Upload binary file
curl -X POST https://upload.contentful.com/spaces/{space_id}/uploads \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/octet-stream" \
--data-binary @/path/to/file.png
# Response: { "sys": { "type": "Upload", "id": "upload-id", ... } }
# 2. Create asset referencing the upload
curl -X POST https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-d '{
"fields": {
"title": { "en-US": "Uploaded File" },
"file": {
"en-US": {
"contentType": "image/png",
"fileName": "file.png",
"uploadFrom": {
"sys": { "type": "Link", "linkType": "Upload", "id": "upload-id" }
}
}
}
}
}'Process Asset
CRITICAL: After creating, process the asset to generate CDN URLs and metadata.
Process for a specific locale
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id}/files/en-US/process \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 1"Poll for processing completion
The process endpoint returns 204 No Content. Poll the asset until fields.file.{locale}.url is present:
# Poll until url field appears
curl https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id} \
-H "Authorization: Bearer {cma_token}"
# Check: response.fields.file["en-US"].url exists → processing completePublish Asset
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id}/published \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 2"Complete Workflow
# 1. Create asset from URL
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/master/assets/hero-image \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-d '{
"fields": {
"title": { "en-US": "Hero Image" },
"file": {
"en-US": {
"contentType": "image/jpeg",
"fileName": "hero.jpg",
"upload": "https://example.com/hero.jpg"
}
}
}
}'
# 2. Process
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/master/assets/hero-image/files/en-US/process \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 1"
# 3. Wait — poll GET until fields.file["en-US"].url is present
# 4. Publish
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/master/assets/hero-image/published \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 2"Update Asset
# 1. Get current version
curl https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id} \
-H "Authorization: Bearer {cma_token}"
# 2. Update metadata (include ALL fields)
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Version: 3" \
-d '{
"fields": {
"title": { "en-US": "Updated Title" },
"description": { "en-US": "Updated description" },
"file": {
"en-US": {
"contentType": "image/jpeg",
"fileName": "hero.jpg",
"url": "//images.ctfassets.net/space_id/asset_id/token/hero.jpg"
}
}
}
}'
# 3. Republish
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id}/published \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 4"To replace the file itself, update fields.file with a new upload URL or uploadFrom reference, then re-process.
Get / List Assets
# Get single asset
curl https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id} \
-H "Authorization: Bearer {cma_token}"
# List assets
curl "https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets?limit=100&order=-sys.createdAt" \
-H "Authorization: Bearer {cma_token}"
# Query by title
curl "https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets?fields.title[match]=hero" \
-H "Authorization: Bearer {cma_token}"Asset Response Structure
{
"sys": { "id": "asset-id", "version": 3, ... },
"fields": {
"title": { "en-US": "Hero Image" },
"description": { "en-US": "Description" },
"file": {
"en-US": {
"url": "//images.ctfassets.net/{space_id}/{asset_id}/{token}/hero.jpg",
"fileName": "hero.jpg",
"contentType": "image/jpeg",
"details": {
"size": 102400,
"image": { "width": 1920, "height": 1080 }
}
}
}
}
}Delete Asset
Must unpublish before deleting:
# 1. Unpublish
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id}/published \
-H "Authorization: Bearer {cma_token}"
# 2. Delete
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id} \
-H "Authorization: Bearer {cma_token}"Archive / Unarchive
# Archive
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id}/archived \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 3"
# Unarchive
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/{env_id}/assets/{asset_id}/archived \
-H "Authorization: Bearer {cma_token}"Localized Assets
Different files per locale:
{
"fields": {
"title": {
"en-US": "User Guide",
"de-DE": "Benutzerhandbuch"
},
"file": {
"en-US": {
"contentType": "application/pdf",
"fileName": "guide-en.pdf",
"upload": "https://example.com/guide-en.pdf"
},
"de-DE": {
"contentType": "application/pdf",
"fileName": "guide-de.pdf",
"upload": "https://example.com/guide-de.pdf"
}
}
}
}Process each locale separately:
curl -X PUT .../assets/{asset_id}/files/en-US/process -H "Authorization: Bearer {cma_token}" -H "X-Contentful-Version: 1"
curl -X PUT .../assets/{asset_id}/files/de-DE/process -H "Authorization: Bearer {cma_token}" -H "X-Contentful-Version: 2"Common MIME Types
| Type | Content-Type |
|---|---|
| JPEG | image/jpeg |
| PNG | image/png |
| WebP | image/webp |
| GIF | image/gif |
| SVG | image/svg+xml |
| MP4 | video/mp4 |
application/pdf | |
| JSON | application/json |
Best Practices
1. Follow three-step workflow — create, process, publish 2. Always process — assets won't have CDN URLs without processing 3. Poll for completion — wait for url field before publishing 4. Include all fields on update — omitted fields are removed 5. Use image transformations — transform via URL parameters instead of uploading variants (see images/overview.md)
Bulk Actions
The Bulk Actions API lets you publish, unpublish, or validate multiple entities in a single request.
Table of Contents
Bulk Publish
Publish multiple entries and/or assets in one request:
curl -X POST https://api.contentful.com/spaces/{space_id}/environments/{env_id}/bulk_actions/publish \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-d '{
"entities": {
"items": [
{
"sys": {
"type": "Link",
"linkType": "Entry",
"id": "entry-1",
"version": 5
}
},
{
"sys": {
"type": "Link",
"linkType": "Entry",
"id": "entry-2",
"version": 3
}
},
{
"sys": {
"type": "Link",
"linkType": "Asset",
"id": "asset-1",
"version": 2
}
}
]
}
}'Response returns a bulk action object with status:
{
"sys": {
"type": "BulkAction",
"id": "bulk-action-id",
"status": "created",
"createdAt": "2024-01-15T10:00:00Z"
}
}Bulk Unpublish
curl -X POST https://api.contentful.com/spaces/{space_id}/environments/{env_id}/bulk_actions/unpublish \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-d '{
"entities": {
"items": [
{ "sys": { "type": "Link", "linkType": "Entry", "id": "entry-1" } },
{ "sys": { "type": "Link", "linkType": "Entry", "id": "entry-2" } }
]
}
}'Note: Unpublish does not require version numbers.
Bulk Validate
Validate multiple entries without publishing:
curl -X POST https://api.contentful.com/spaces/{space_id}/environments/{env_id}/bulk_actions/validate \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-d '{
"entities": {
"items": [
{ "sys": { "type": "Link", "linkType": "Entry", "id": "entry-1" } },
{ "sys": { "type": "Link", "linkType": "Entry", "id": "entry-2" } }
]
}
}'Check Bulk Action Status
Bulk actions run asynchronously. Poll the status endpoint:
curl https://api.contentful.com/spaces/{space_id}/environments/{env_id}/bulk_actions/actions/{bulk_action_id} \
-H "Authorization: Bearer {cma_token}"Response:
{
"sys": {
"type": "BulkAction",
"id": "bulk-action-id",
"status": "succeeded",
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-15T10:00:05Z"
}
}Status values: created, inProgress, succeeded, failed.
On failure, the response includes error details per entity.
Request Format
Entity Reference (for publish)
Publish requires the entity version for optimistic locking:
{
"sys": {
"type": "Link",
"linkType": "Entry",
"id": "entry-id",
"version": 5
}
}Entity Reference (for unpublish/validate)
Unpublish and validate don't require version:
{
"sys": {
"type": "Link",
"linkType": "Entry",
"id": "entry-id"
}
}Mixed Entities
A single bulk action can include both entries and assets:
{
"entities": {
"items": [
{ "sys": { "type": "Link", "linkType": "Entry", "id": "entry-1", "version": 3 } },
{ "sys": { "type": "Link", "linkType": "Asset", "id": "asset-1", "version": 2 } }
]
}
}Limits
- Maximum entities per bulk action varies by plan (typically 200)
- Bulk actions are processed asynchronously
- Rate limits still apply per entity within the bulk action
Best Practices
1. Include version numbers for bulk publish — prevents overwriting concurrent changes 2. Poll for completion — bulk actions are async, don't assume immediate success 3. Handle partial failures — some entities may fail while others succeed 4. Batch large sets — split > 200 entities into multiple bulk action requests 5. Use bulk validate first — validate before publishing to catch errors early
Content Types
Content types define the structure of your content. They must be published before entries can be created.
Table of Contents
- Get Content Type
- List Content Types
- Create Content Type
- Update Content Type
- Publish / Unpublish
- Delete Content Type
- Field Types
- Validations
Get Content Type
curl https://api.contentful.com/spaces/{space_id}/environments/{env_id}/content_types/{content_type_id} \
-H "Authorization: Bearer {cma_token}"List Content Types
curl "https://api.contentful.com/spaces/{space_id}/environments/{env_id}/content_types?limit=100" \
-H "Authorization: Bearer {cma_token}"Create Content Type
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/content_types/{content_type_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-d '{
"name": "Blog Post",
"displayField": "title",
"fields": [
{
"id": "title",
"name": "Title",
"type": "Symbol",
"required": true,
"localized": false
},
{
"id": "body",
"name": "Body",
"type": "Text",
"required": false,
"localized": true
},
{
"id": "slug",
"name": "Slug",
"type": "Symbol",
"required": true,
"validations": [
{ "unique": true },
{ "regexp": { "pattern": "^[a-z0-9-]+$" } }
]
}
]
}'CRITICAL: Publish after creation to make it usable:
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/content_types/{content_type_id}/published \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 1"Update Content Type
# 1. Get current version
curl https://api.contentful.com/spaces/{space_id}/environments/{env_id}/content_types/{content_type_id} \
-H "Authorization: Bearer {cma_token}"
# 2. Update — include ALL fields (existing + new)
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/content_types/{content_type_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Version: 2" \
-d '{
"name": "Blog Post",
"displayField": "title",
"fields": [
{ "id": "title", "name": "Title", "type": "Symbol", "required": true },
{ "id": "body", "name": "Body", "type": "Text" },
{ "id": "slug", "name": "Slug", "type": "Symbol", "required": true },
{ "id": "summary", "name": "Summary", "type": "Symbol" }
]
}'
# 3. Publish updated content type
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/content_types/{content_type_id}/published \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 3"Publish / Unpublish
Publish
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/content_types/{content_type_id}/published \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: {version}"Unpublish
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/{env_id}/content_types/{content_type_id}/published \
-H "Authorization: Bearer {cma_token}"Cannot unpublish if entries exist for this content type.
Delete Content Type
Must unpublish first. Cannot delete if entries exist.
# 1. Unpublish
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/{env_id}/content_types/{content_type_id}/published \
-H "Authorization: Bearer {cma_token}"
# 2. Delete
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/{env_id}/content_types/{content_type_id} \
-H "Authorization: Bearer {cma_token}"Field Types
| Type | Description | Example Value |
|---|---|---|
Symbol | Short text (max 256 chars) | "Hello World" |
Text | Long text (max 50,000 chars) | "Long content..." |
Integer | Whole number | 42 |
Number | Decimal number | 3.14 |
Boolean | True/false | true |
Date | ISO 8601 date | "2024-01-15T10:30:00Z" |
Location | Lat/lon coordinates | {"lat": 40.71, "lon": -74.00} |
Object | Arbitrary JSON | {"key": "value"} |
RichText | Structured rich text | Rich text document |
Link | Reference to entry or asset | {"sys": {"type": "Link", ...}} |
Array | Array of symbols or links | ["tag1", "tag2"] |
Link Field
{
"id": "author",
"name": "Author",
"type": "Link",
"linkType": "Entry",
"validations": [
{ "linkContentType": ["author", "contributor"] }
]
}Asset Link Field
{
"id": "image",
"name": "Image",
"type": "Link",
"linkType": "Asset"
}Array of Links
{
"id": "relatedPosts",
"name": "Related Posts",
"type": "Array",
"items": {
"type": "Link",
"linkType": "Entry",
"validations": [
{ "linkContentType": ["blogPost"] }
]
}
}Array of Symbols
{
"id": "tags",
"name": "Tags",
"type": "Array",
"items": { "type": "Symbol" }
}Rich Text Field
{
"id": "richBody",
"name": "Rich Body",
"type": "RichText",
"validations": [
{
"nodes": {
"embedded-entry-block": [
{ "linkContentType": ["quote", "callout"] }
]
}
}
]
}Validations
Symbol/Text
{ "unique": true }
{ "regexp": { "pattern": "^[a-z0-9-]+$" } }
{ "size": { "min": 3, "max": 100 } }
{ "in": ["tech", "design", "business"] }Number
{ "range": { "min": 1, "max": 5 } }
{ "in": [1, 5, 10, 25, 50, 100] }Array
{ "size": { "min": 1, "max": 10 } }Link
{ "linkContentType": ["author", "contributor"] }
{ "assetFileSize": { "min": 1024, "max": 10485760 } }
{ "assetImageDimensions": { "width": { "min": 100, "max": 2000 }, "height": { "min": 100, "max": 2000 } } }Best Practices
1. Always publish — content types must be published to create entries 2. Use descriptive field IDs — field IDs are permanent, choose carefully 3. Add validations early — harder to add constraints with existing content 4. Use `linkContentType` — restrict references to specific content types 5. Set `localized: true` — only for fields that need translation 6. Set `displayField` — identifies content in the Contentful web app 7. Test in staging — create content types in a non-production environment first
Entries
Entries are instances of content types. They follow the locale structure and require explicit publishing.
Table of Contents
- Get Entry
- List Entries
- Create Entry
- Create Entry with ID
- Update Entry
- Publish / Unpublish
- Archive / Unarchive
- Delete Entry
- Query Parameters
- Links and References
- Complete Workflow
Get Entry
curl https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}"Response includes sys.version needed for updates.
List Entries
curl "https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries?content_type=blogPost&limit=100&skip=0" \
-H "Authorization: Bearer {cma_token}"Returns { "sys": { "type": "Array" }, "total": N, "skip": 0, "limit": 100, "items": [...] }.
Create Entry
Auto-generated ID. Requires X-Contentful-Content-Type header:
curl -X POST https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Content-Type: blogPost" \
-d '{
"fields": {
"title": { "en-US": "My First Post" },
"body": { "en-US": "Post content here." }
}
}'Create Entry with ID
Use PUT with the desired ID for idempotent creation:
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Content-Type: blogPost" \
-d '{
"fields": {
"title": { "en-US": "My First Post" }
}
}'Update Entry
CRITICAL: Include X-Contentful-Version header with current sys.version. Omitting fields removes them.
# 1. Get current entry to obtain version
curl https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}"
# Response: { "sys": { "version": 5, ... }, "fields": { ... } }
# 2. Update with version header — include ALL fields you want to keep
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Version: 5" \
-d '{
"fields": {
"title": { "en-US": "Updated Title" },
"body": { "en-US": "Keep existing or update." }
}
}'Returns updated entry with sys.version: 6.
Publish / Unpublish
Publish
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries/{entry_id}/published \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 6"Unpublish
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries/{entry_id}/published \
-H "Authorization: Bearer {cma_token}"Archive / Unarchive
Archive
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries/{entry_id}/archived \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 7"Unarchive
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries/{entry_id}/archived \
-H "Authorization: Bearer {cma_token}"Delete Entry
Must unpublish before deleting:
# 1. Unpublish
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries/{entry_id}/published \
-H "Authorization: Bearer {cma_token}"
# 2. Delete
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}"Query Parameters
Use as URL query parameters when listing entries:
Filtering
content_type=blogPost # filter by content type
fields.title=Exact+Title # exact match (requires content_type)
fields.title[match]=search+term # full-text match
fields.title[ne]=Not+This # not equal
fields.count[gt]=10 # greater than
fields.count[gte]=10 # greater than or equal
fields.count[lt]=100 # less than
fields.count[lte]=100 # less than or equal
fields.slug[in]=post-1,post-2,post-3 # in list
fields.slug[nin]=post-1,post-2 # not in list
fields.author[exists]=true # field existsSystem Fields
sys.id=entry-id
sys.id[in]=id1,id2,id3
sys.createdAt[gte]=2024-01-01
sys.updatedAt[gte]=2024-01-01T00:00:00ZFull-Text Search
query=search+across+all+fieldsPagination and Ordering
limit=100 # max 1000
skip=0 # offset
order=sys.createdAt # ascending
order=-sys.createdAt # descending
order=fields.title # by field (requires content_type)
select=fields.title,sys.id # return only these fieldsComplete Example
curl "https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries?content_type=blogPost&fields.title[match]=hello&order=-sys.createdAt&limit=10&skip=0" \
-H "Authorization: Bearer {cma_token}"Links and References
Entry Reference
{
"fields": {
"author": {
"en-US": {
"sys": { "type": "Link", "linkType": "Entry", "id": "author-entry-id" }
}
}
}
}Asset Reference
{
"fields": {
"image": {
"en-US": {
"sys": { "type": "Link", "linkType": "Asset", "id": "asset-id" }
}
}
}
}Array of References
{
"fields": {
"relatedPosts": {
"en-US": [
{ "sys": { "type": "Link", "linkType": "Entry", "id": "post-1" } },
{ "sys": { "type": "Link", "linkType": "Entry", "id": "post-2" } }
]
}
}
}Multiple Locales
{
"fields": {
"title": {
"en-US": "English Title",
"de-DE": "German Title",
"fr-FR": "French Title"
}
}
}Complete Workflow
# 1. Create entry
curl -X POST https://api.contentful.com/spaces/{space_id}/environments/master/entries \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Content-Type: blogPost" \
-d '{"fields":{"title":{"en-US":"New Post"},"slug":{"en-US":"new-post"}}}'
# Note the sys.id and sys.version from response
# 2. Publish
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id}/published \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 1"
# 3. Update
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Version: 2" \
-d '{"fields":{"title":{"en-US":"Updated Post"},"slug":{"en-US":"new-post"}}}'
# 4. Republish
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id}/published \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 3"
# 5. Unpublish
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id}/published \
-H "Authorization: Bearer {cma_token}"
# 6. Delete
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}"Best Practices
1. Always include all fields when updating — omitted fields are removed 2. Use `X-Contentful-Version` for every update — prevents conflicts 3. Publish explicitly — entries are drafts until published 4. Unpublish before delete — published entries cannot be deleted directly 5. Use `content_type` in queries — improves performance 6. Handle 409 conflicts — refetch entry, get new version, retry 7. Use PUT with ID for idempotent creates (migrations, imports)
Environments
Environments are isolated content workspaces within a space. Use them for staging, testing, and feature branches.
Table of Contents
- Get Environment
- List Environments
- Create Environment
- Clone Environment
- Wait for Ready
- Update Environment
- Delete Environment
- Environment Aliases
- Deployment Patterns
Get Environment
curl https://api.contentful.com/spaces/{space_id}/environments/{env_id} \
-H "Authorization: Bearer {cma_token}"Response includes sys.status.sys.id — one of: queued, creating, ready, failed.
List Environments
curl https://api.contentful.com/spaces/{space_id}/environments \
-H "Authorization: Bearer {cma_token}"Create Environment
Creates an empty environment:
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-d '{ "name": "Staging" }'Clone Environment
Clone from a source environment by providing the source in the request body:
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{new_env_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-d '{
"name": "Feature Branch",
"sourceEnvironmentId": "master"
}'This copies all content types and content from the source environment.
Wait for Ready
Environments are created asynchronously. Poll until status is ready:
# Poll GET until sys.status.sys.id === "ready"
curl https://api.contentful.com/spaces/{space_id}/environments/{env_id} \
-H "Authorization: Bearer {cma_token}"
# Check: response.sys.status.sys.id
# Possible values: "queued", "creating", "ready", "failed"Update Environment
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Version: 1" \
-d '{ "name": "Updated Name" }'Delete Environment
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/{env_id} \
-H "Authorization: Bearer {cma_token}"Warning: This permanently deletes all content in the environment.
Environment Aliases
Aliases provide stable identifiers that point to different environments. Useful for zero-downtime deployments.
Get Alias
curl https://api.contentful.com/spaces/{space_id}/environment_aliases/{alias_id} \
-H "Authorization: Bearer {cma_token}"Response:
{
"sys": { "id": "master", "type": "EnvironmentAlias", "version": 1, ... },
"environment": {
"sys": { "type": "Link", "linkType": "Environment", "id": "production-v2" }
}
}List Aliases
curl https://api.contentful.com/spaces/{space_id}/environment_aliases \
-H "Authorization: Bearer {cma_token}"Update Alias (Switch Environment)
Point an alias to a different environment:
curl -X PUT https://api.contentful.com/spaces/{space_id}/environment_aliases/{alias_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Version: 1" \
-d '{
"environment": {
"sys": { "type": "Link", "linkType": "Environment", "id": "production-v3" }
}
}'Deployment Patterns
Blue-Green Deployment
# 1. Create new environment from current production
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/production-blue \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-d '{"name":"Production Blue","sourceEnvironmentId":"master"}'
# 2. Wait for ready (poll until sys.status.sys.id === "ready")
# 3. Make changes in new environment
# 4. Switch alias to new environment
curl -X PUT https://api.contentful.com/spaces/{space_id}/environment_aliases/master \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Version: 1" \
-d '{"environment":{"sys":{"type":"Link","linkType":"Environment","id":"production-blue"}}}'
# 5. Delete old environment after verification
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/production-green \
-H "Authorization: Bearer {cma_token}"Feature Branch Workflow
# 1. Create feature branch from master
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/feature-new-layout \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-d '{"name":"New Layout Feature","sourceEnvironmentId":"master"}'
# 2. Wait for ready, make changes, test
# 3. Clean up when done
curl -X DELETE https://api.contentful.com/spaces/{space_id}/environments/feature-new-layout \
-H "Authorization: Bearer {cma_token}"Best Practices
1. Use environment aliases — avoid hardcoding environment IDs 2. Always poll after creation — environments take time to clone 3. Clone from appropriate source — staging from master, production from staging 4. Clean up old environments — most plans have environment limits 5. Use feature branches — create temporary environments for isolated changes 6. Monitor status — check sys.status.sys.id before using a new environment
Content Management API Overview
The CMA is a read/write API for managing content, content types, assets, and environments.
Base URL
- US:
https://api.contentful.com - EU:
https://api.eu.contentful.com
Most endpoints follow: https://api.contentful.com/spaces/{space_id}/environments/{environment_id}/.... Some are space-scoped and omit the environment segment (e.g., /spaces/{space_id}/environment_aliases/...).
Required Headers
Authorization: Bearer {cma_token}
Content-Type: application/vnd.contentful.management.v1+json # for write requests
X-Contentful-Version: {version} # for updates (optimistic locking)
X-Contentful-Content-Type: {content_type_id} # when creating entriesQuick Start
Get an entry
curl https://api.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}"Create an entry
curl -X POST https://api.contentful.com/spaces/{space_id}/environments/master/entries \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Content-Type: blogPost" \
-d '{
"fields": {
"title": { "en-US": "Hello World" },
"body": { "en-US": "First post content." }
}
}'Update an entry
# First GET the entry to obtain sys.version, then:
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Version: 5" \
-d '{
"fields": {
"title": { "en-US": "Updated Title" },
"body": { "en-US": "Updated content." }
}
}'Publish an entry
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id}/published \
-H "Authorization: Bearer {cma_token}" \
-H "X-Contentful-Version: 6"Critical Patterns
1. Version locking — every update requires X-Contentful-Version header matching current sys.version. See http-conventions.md. 2. Locale structure — all field values are keyed by locale: {"en-US": "value"}. See http-conventions.md. 3. Publish workflow — entities are drafts until explicitly published via PUT .../published.
Topics
- [Entries](entries.md) — CRUD, publish/unpublish, versioning, query parameters
- [Content Types](content-types.md) — Define and update content models with field types and validations
- [Assets](assets.md) — Upload, process, and publish media files
- [Environments](environments.md) — Create, clone, manage environments and aliases
- [Bulk Actions](bulk-actions.md) — Bulk publish, unpublish, and validate via API
Reference
- CMA API Reference
- Authentication
- HTTP Conventions
Content Preview API
The Preview API returns draft and changed content before it's published. Same endpoints and query parameters as the CDA, but with a different base URL and token.
Base URL
- US:
https://preview.contentful.com - EU:
https://preview.eu.contentful.com
Authentication
Use a Preview access token (not the CDA token):
curl https://preview.contentful.com/spaces/{space_id}/environments/master/entries \
-H "Authorization: Bearer {preview_token}"Create Preview tokens at: Settings → API keys → Content Preview API - access token
How It Differs from CDA
CDA (cdn.contentful.com) | Preview (preview.contentful.com) | |
|---|---|---|
| Token | CDA access token | Preview access token |
| Content | Published only | Published + draft + changed |
| Caching | CDN-cached, fast | No caching, slower |
| Use case | Production apps | Content preview, editorial tools |
Usage
All CDA endpoints, query parameters, and features work identically:
# Get entries
curl "https://preview.contentful.com/spaces/{space_id}/environments/master/entries?content_type=blogPost" \
-H "Authorization: Bearer {preview_token}"
# Get single entry (including unpublished drafts)
curl "https://preview.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id}" \
-H "Authorization: Bearer {preview_token}"
# Query with filters
curl "https://preview.contentful.com/spaces/{space_id}/environments/master/entries?content_type=blogPost&fields.slug=draft-post&include=2" \
-H "Authorization: Bearer {preview_token}"
# Get assets (including unprocessed/unpublished)
curl "https://preview.contentful.com/spaces/{space_id}/environments/master/assets" \
-H "Authorization: Bearer {preview_token}"
# Sync API works too
curl "https://preview.contentful.com/spaces/{space_id}/environments/master/sync?initial=true" \
-H "Authorization: Bearer {preview_token}"Common Use Cases
- Content preview in editorial workflows (show authors what content will look like)
- Draft content review before publishing
- Staging environments where you need to see unpublished changes
- Headless CMS previews integrated into frontend frameworks
Security
- Never expose Preview tokens in client-side code — they reveal unpublished content
- Use Preview API only in server-side code or authenticated admin interfaces
- Preview tokens have read-only access (same as CDA tokens, but for all content states)
Reference
- All CDA query parameters and features apply — see content-delivery/
- Authentication for token types
GraphQL Content API
Contentful provides a GraphQL endpoint for querying published content. It uses CDA tokens and supports the same content as the Content Delivery API.
Table of Contents
Endpoint
POST https://graphql.contentful.com/content/v1/spaces/{space_id}/environments/{environment_id}- US:
graphql.contentful.com - EU:
graphql.eu.contentful.com
Authentication
Use a CDA access token:
curl -X POST https://graphql.contentful.com/content/v1/spaces/{space_id}/environments/master \
-H "Authorization: Bearer {cda_token}" \
-H "Content-Type: application/json" \
-d '{"query": "{ blogPostCollection { items { sys { id } } } }"}'Basic Query
Single Entry
curl -X POST https://graphql.contentful.com/content/v1/spaces/{space_id}/environments/master \
-H "Authorization: Bearer {cda_token}" \
-H "Content-Type: application/json" \
-d '{
"query": "query { blogPost(id: \"entry-id\") { title slug body { json } author { name } } }"
}'Collection Query
curl -X POST https://graphql.contentful.com/content/v1/spaces/{space_id}/environments/master \
-H "Authorization: Bearer {cda_token}" \
-H "Content-Type: application/json" \
-d '{
"query": "query { blogPostCollection(limit: 10, order: publishDate_DESC) { total items { sys { id } title slug publishDate } } }"
}'Query Patterns
Schema Convention
Contentful auto-generates the GraphQL schema from your content types:
| Content Type ID | Single entry | Collection |
|---|---|---|
blogPost | blogPost(id: "...") | blogPostCollection(...) |
author | author(id: "...") | authorCollection(...) |
category | category(id: "...") | categoryCollection(...) |
Field names match content type field IDs.
Nested References
References are resolved inline — no separate includes object:
query {
blogPostCollection(limit: 5) {
items {
title
author {
name
photo {
url
width
height
}
}
heroImage {
url
width
height
}
}
}
}Rich Text
Rich text fields return a json property containing the document:
query {
blogPost(id: "entry-id") {
body {
json
links {
entries {
block { sys { id } __typename }
inline { sys { id } __typename }
}
assets {
block { sys { id } url title }
}
}
}
}
}Filtering
Where Clause
query {
blogPostCollection(where: { slug: "hello-world" }) {
items { title }
}
}Operators
# Equality
where: { slug: "hello-world" }
# Not equal
where: { slug_not: "hello-world" }
# In list
where: { slug_in: ["post-1", "post-2"] }
# Not in list
where: { slug_not_in: ["post-1"] }
# Contains (text search)
where: { title_contains: "contentful" }
# Exists
where: { author_exists: true }
# Greater/less than (numbers, dates)
where: { publishDate_gt: "2024-01-01" }
where: { publishDate_gte: "2024-01-01" }
where: { publishDate_lt: "2024-12-31" }
where: { publishDate_lte: "2024-12-31" }Combined Filters (AND)
query {
blogPostCollection(
where: {
featured: true,
publishDate_gte: "2024-01-01"
}
) {
items { title }
}
}OR Queries
query {
blogPostCollection(
where: {
OR: [
{ slug: "post-1" },
{ slug: "post-2" }
]
}
) {
items { title }
}
}Pagination
query {
blogPostCollection(limit: 10, skip: 0) {
total
skip
limit
items { title }
}
}Maximum limit: 1000.
Preview Content
Use a Preview token and add preview: true to queries:
curl -X POST https://graphql.contentful.com/content/v1/spaces/{space_id}/environments/master \
-H "Authorization: Bearer {preview_token}" \
-H "Content-Type: application/json" \
-d '{
"query": "query { blogPostCollection(preview: true) { items { title sys { publishedAt } } } }"
}'This returns draft and changed content (equivalent to using the Preview API host with REST).
Complexity Limits
GraphQL queries are subject to complexity limits:
- Max complexity: 11,000 points
- Each field costs points based on type and depth
- Collection queries cost more than single-entry queries
- Deeply nested reference queries increase complexity quickly
If a query exceeds the limit, the API returns an error with the calculated complexity.
Reducing Complexity
1. Limit collection sizes with limit 2. Avoid deeply nested reference chains 3. Use sys { id } instead of fetching full referenced entries when you only need IDs 4. Split large queries into multiple smaller queries
Rate Limits
Same as CDA: ~78 requests/second per space (varies by plan). Each GraphQL request counts as one request regardless of query complexity.
Best Practices
1. Use variables for dynamic values instead of string interpolation 2. Limit collection sizes — always set limit 3. Watch complexity — monitor query complexity scores 4. Use fragments for reusable field selections 5. Prefer REST for simple queries — GraphQL overhead isn't worth it for single entry fetches 6. Use `preview: true` with preview token for draft content
HTTP Conventions
Common patterns across all Contentful REST APIs.
Table of Contents
Headers
CMA Requests (Write)
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/{env_id}/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Version: {version}"Required headers for CMA write operations:
Authorization: Bearer {cma_token}— authenticationContent-Type: application/vnd.contentful.management.v1+json— request body formatX-Contentful-Version: {version}— optimistic locking (for updates)X-Contentful-Content-Type: {content_type_id}— required when creating entries
CDA/Preview Requests (Read)
curl https://cdn.contentful.com/spaces/{space_id}/environments/{env_id}/entries \
-H "Authorization: Bearer {cda_token}"Only Authorization header required. Responses are always application/json.
Version Locking
The CMA uses optimistic concurrency control. Every entity has a version number in sys.version.
To update an entity, pass its current version in the `X-Contentful-Version` header. If the version doesn't match (someone else changed it), the API returns 409 Conflict.
# 1. Get entry (note the version in sys.version)
curl https://api.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}"
# Response: { "sys": { "version": 5, ... }, "fields": { ... } }
# 2. Update with version header — include ALL fields you want to keep
curl -X PUT https://api.contentful.com/spaces/{space_id}/environments/master/entries/{entry_id} \
-H "Authorization: Bearer {cma_token}" \
-H "Content-Type: application/vnd.contentful.management.v1+json" \
-H "X-Contentful-Version: 5" \
-d '{"fields":{"title":{"en-US":"Updated Title"},"body":{"en-US":"Existing body"}}}'
# NOTE: CMA PUT replaces the entire `fields` object. Omitted fields are removed.On success, the response contains sys.version: 6. Use that version for subsequent updates.
Rate Limiting
CMA Limits
- Default: 10 requests/second per space
- Response headers track usage:
| Header | Description |
|---|---|
X-Contentful-RateLimit-Second-Limit | Max requests per second |
X-Contentful-RateLimit-Second-Remaining | Remaining requests this second |
X-Contentful-RateLimit-Reset | Seconds until rate limit resets |
CDA Limits
- Default: 78 requests/second per space (varies by plan)
- Same rate limit headers as CMA
Handling 429 Too Many Requests
# When rate limited, check Retry-After or X-Contentful-RateLimit-Reset header
# HTTP/1.1 429 Too Many Requests
# X-Contentful-RateLimit-Reset: 1Retry after the number of seconds indicated. Use exponential backoff for repeated 429s.
Pagination
All collection endpoints return paginated results:
{
"sys": { "type": "Array" },
"total": 250,
"skip": 0,
"limit": 100,
"items": [ ... ]
}Parameters
| Parameter | Default | Max | Description |
|---|---|---|---|
limit | 100 | 1000 | Items per page |
skip | 0 | — | Items to skip |
Paginating Through All Results
# Page 1
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries?limit=100&skip=0" \
-H "Authorization: Bearer {cda_token}"
# Page 2
curl "https://cdn.contentful.com/spaces/{space_id}/environments/master/entries?limit=100&skip=100" \
-H "Authorization: Bearer {cda_token}"
# Continue until items.length < limit or skip >= totalError Responses
All errors follow a consistent format:
{
"sys": {
"type": "Error",
"id": "NotFound"
},
"message": "The resource could not be found.",
"details": { ... },
"requestId": "abc123"
}Common Error Codes
| Status | Error ID | Description | Action |
|---|---|---|---|
| 400 | BadRequest | Invalid request body or parameters | Fix request format |
| 401 | AccessTokenInvalid | Invalid or expired token | Check/refresh token |
| 403 | AccessDenied | Insufficient permissions | Check token scope |
| 404 | NotFound | Resource doesn't exist | Check IDs |
| 409 | VersionMismatch | Version conflict on update | Refetch, get new version, retry |
| 422 | ValidationFailed | Content validation failed | Check details.errors array |
| 429 | RateLimitExceeded | Too many requests | Wait and retry with backoff |
| 500 | ServerError | Internal server error | Retry with backoff |
| 502 | ServiceUnavailable | Temporary service issue | Retry with backoff |
409 Version Conflict Pattern
# 1. GET the entry to get current version
# 2. Update with the correct X-Contentful-Version
# 3. If 409 again, repeat from step 1422 Validation Error Details
{
"sys": { "type": "Error", "id": "ValidationFailed" },
"message": "Validation error",
"details": {
"errors": [
{
"name": "required",
"path": ["fields", "title", "en-US"],
"details": "The property \"title\" is required."
}
]
}
}Locale Structure
In the CMA, all field values are keyed by locale:
{
"fields": {
"title": {
"en-US": "English Title",
"de-DE": "German Title"
},
"body": {
"en-US": "English content"
}
}
}In the CDA (without locale=*), fields return the resolved locale value directly:
{
"fields": {
"title": "English Title",
"body": "English content"
}
}With locale=* on the CDA, fields use the same locale-keyed structure as the CMA.
Link Format
References between entries/assets use a standard link object:
{
"sys": {
"type": "Link",
"linkType": "Entry",
"id": "referenced-entry-id"
}
}Link types: Entry, Asset, Environment, Upload, ContentType, Space.
In CMA request bodies, set references as:
{
"fields": {
"author": {
"en-US": {
"sys": { "type": "Link", "linkType": "Entry", "id": "author-id" }
}
},
"gallery": {
"en-US": [
{ "sys": { "type": "Link", "linkType": "Asset", "id": "image-1" } },
{ "sys": { "type": "Link", "linkType": "Asset", "id": "image-2" } }
]
}
}
}Images API
The Images API transforms images on-the-fly via URL parameters. No authentication required.
Table of Contents
- Base URL
- URL Format
- Resize
- Fit Modes
- Focus Area
- Format Conversion
- Quality
- Other Parameters
- Combining Parameters
Base URL
- US:
https://images.ctfassets.net - EU:
https://images.eu.ctfassets.net
No authentication is needed. The URL from an asset's file.url field already points to this host.
URL Format
https://images.ctfassets.net/{space_id}/{asset_id}/{unique_token}/{filename}?{parameters}The full URL is returned in asset responses as fields.file.url (prefixed with //).
Resize
# Set width (height auto-scales)
...?w=300
# Set height (width auto-scales)
...?h=200
# Set both (behavior depends on fit mode)
...?w=300&h=200Maximum dimension: 4000px.
Fit Modes
Control how the image fits within the specified dimensions:
| Mode | Description |
|---|---|
pad | Resize to fit within dimensions, pad remaining space with background color |
fill | Resize to fill dimensions, crop overflow |
scale | Resize to fit within dimensions, no cropping or padding |
crop | Crop to exact dimensions from center (or focus area) |
thumb | Thumbnail — smart crop using focus area detection |
# Pad with white background
...?w=300&h=200&fit=pad&bg=rgb:ffffff
# Fill (crop overflow)
...?w=300&h=200&fit=fill
# Scale to fit
...?w=300&fit=scale
# Crop from center
...?w=300&h=200&fit=crop
# Smart thumbnail
...?w=150&h=150&fit=thumb&f=faceFocus Area
Control the focal point for fit=crop, fit=fill, and fit=thumb:
| Value | Description |
|---|---|
center | Center of image (default) |
top | Top edge |
right | Right edge |
bottom | Bottom edge |
left | Left edge |
top_right | Top-right corner |
top_left | Top-left corner |
bottom_right | Bottom-right corner |
bottom_left | Bottom-left corner |
face | Detected face (auto) |
faces | All detected faces (auto) |
# Crop focusing on faces
...?w=300&h=300&fit=thumb&f=faces
# Crop from top
...?w=800&h=400&fit=crop&f=topFormat Conversion
Convert between image formats:
# Convert to WebP
...?fm=webp
# Convert to AVIF
...?fm=avif
# Convert to PNG
...?fm=png
# Convert to JPEG
...?fm=jpg
# Convert to progressive JPEG
...?fm=jpg&fl=progressive
# Convert to 8-bit PNG
...?fm=png&fl=png8Supported formats: jpg, png, webp, gif, avif.
Quality
Control JPEG/WebP/AVIF compression quality (1-100):
# 80% quality (good balance of size and visual quality)
...?q=80
# Lower quality, smaller file
...?q=50
# High quality
...?q=95Default quality varies by format. Only applies to jpg, webp, and avif.
Other Parameters
Border radius
# Round corners (max: half of smallest dimension for circle)
...?r=20
# Full circle (use with equal w and h)
...?w=200&h=200&fit=fill&r=maxBackground color (for fit=pad)
# RGB hex
...?fit=pad&w=300&h=200&bg=rgb:ff0000
# With transparency (for PNG)
...?fit=pad&w=300&h=200&bg=rgb:00000000Combining Parameters
Chain multiple transformations:
# Responsive hero image: 800px wide, WebP, 80% quality
https://images.ctfassets.net/{space}/{id}/{token}/hero.jpg?w=800&fm=webp&q=80
# Thumbnail: 150x150, smart crop on faces, WebP
https://images.ctfassets.net/{space}/{id}/{token}/photo.jpg?w=150&h=150&fit=thumb&f=faces&fm=webp&q=80
# Padded product image: 400x400, white background, PNG
https://images.ctfassets.net/{space}/{id}/{token}/product.png?w=400&h=400&fit=pad&bg=rgb:ffffff
# Avatar circle: 100x100
https://images.ctfassets.net/{space}/{id}/{token}/avatar.jpg?w=100&h=100&fit=fill&f=face&r=max&fm=webpBest Practices
1. Use WebP/AVIF — significantly smaller files than JPEG/PNG 2. Set appropriate quality — q=80 is usually sufficient 3. Resize on delivery — don't upload pre-sized variants 4. Use `fit=fill` for fixed-size containers 5. Use `f=faces` for profile photos and portraits 6. Serve responsive images — use different w values per breakpoint