Content Cloud Content Delivery REST API
Introduction
The Content Cloud REST Delivery API is a read-only REST API that delivers content from your connected source sites and services, e.g. Drupal sites, to any application, website, or digital platform. Content (entries, content types, etc.) is returned as JSON data, while media assets (images, videos, files) are delivered as binary files. The API is served through a high-speed global content delivery network (CDN) to ensure low latency and high availability, so content is fetched from the server closest to the end user. This allows your front-end applications to load content quickly, benefiting mobile apps and global audiences.
Content Cloud APIs are mostly compatible with Contentful. You can read more about Contentful compatibility here.
Note: You will need an active Content Cloud account with at least one Space configured (and your source site/service content synchronized) to use this API. Ensure you have your Space ID and a valid API access token before proceeding.
Getting started: The recommended way of adding Content Cloud to your project is by using our auto-generated clients. This will provide the content model as a schema in your frontend and allow you to spend less time writing repetitive integration code.
Requirements
Before you can use the Delivery APIs, you first have to feed content into Content Cloud. You can do so by creating a Space in the Content Sync backend for your source site or services, e.g. Drupal. After connecting your source sites and services, you can push content to Content Sync. To serve the pushed content through the Delivery API, you have to create a second Content Cloud space and connect the two. Please refer to the general Onboarding instructions to learn more.
Basic API Information
API Base URLs: The base URL for the REST Delivery API depends on the environment of content you want to access. The following endpoints are available:
CDN Content API: e.g.,
https://{ENVIRONMENT_ID}.cdn.cloud.content-sync.io/{VERSION}/rest/, for published content via CDN.Live Content API: e.g.,
https://{ENVIRONMENT_ID}.live.cloud.content-sync.io/{VERSION}/rest/, for published content, uncached.Preview Content API: e.g.,
https://{ENVIRONMENT_ID}.preview.cloud.content-sync.io/{VERSION}/rest/, for previewing content including drafts/unpublished entries.
In the above, replace {ENVIRONMENT_ID} with your actual Space ID, and optionally include the Environment ID. The exact domain and for your Space’s API endpoints can be found in your Content Cloud space settings. The {VERSION} is used for API versioning (see below).
Read-Only Access: This API is read-only for content and used to retrieve content. It cannot be used to create or modify content (content management is done through Drupal or other management APIs). All endpoints follow REST conventions (HTTPS GET requests for retrieving resources).
API versioning
Content Cloud changes to the API are backwards-compatible by default. You can pass a version parameter for every request as part of the path (see {VERSION} above). This version can either be latest, meaning you will receive responses based on the latest spec, or a date in the format YYYY-MM-DD to use the API format of the spec from this date, 0:00:00 am UTC. For example:
https://space-environment.cdn.cloud.content-sync.io/latest/rest/to use the latest API spec.https://space-environment.cdn.cloud.content-sync.io/2025-09-19/rest/to use the API spec from September 18, 2025 at 23:59:59.999 (UTC).
It’s usually a good idea to use the date of your development start for the version parameter and update it routinely to use newer specs.
Content Cloud APIs are backwards-compatible for at least 1 year, and breaking changes from backwards-compatibility reaching their EOL are communicated in advance.
Auto-generated clients will always use the API version of the date the client code was generated.
Authentication
By default, any client (application or user agent) requesting content from the Content Cloud Delivery API must provide a valid access token with the request. The access token authenticates your request and determines which Space and Environment you can access. You can supply the token as:
HTTP Header: Provide the token via the
Authorizationheader as a Bearer token. For example:Authorization: Bearer MY_ACCESS_TOKEN
The access token you use must have access to the target space and environment. For instance, if a token is limited to the “live” environment of a space, it cannot be used to fetch content from the “preview” environment or a different space.
Access tokens are generated using client secrets. To generate a secret, open the project settings, navigate to Authorization (clients), create a client and then a secret. Copy-paste the result and save it to your project as an environment variable. Keep your tokens secure, i.e., treat them like passwords and do not expose them in client-side code.
Use the secret to generate access tokens on-demand or create a long-lived token that’s used in your applications. Access tokens must have a space, one or more environments, allowed APIs, and permissions assigned. You can read more about access here.
Tip: Use the auto-generated clients and their helper functions to generate tokens more easily. You can create different tokens, e.g., for live published content and for preview content.
API Rate Limits
To protect the service and ensure fair usage, the Content Cloud API enforces rate limits on API calls. Every uncached request counts toward a per-second rate limit for your client. Requests served entirely from the CDN cache do not count against this limit.
Rate Limit: By default, clients can make up to 55 requests per second to the Content Cloud Delivery API. (Higher limits may be available for enterprise plans). If you exceed this rate, the API will respond with HTTP 429 Too Many Requests and include a header
X-RateLimit-Resetindicating how many seconds to wait before making new requests. In practice, this header is usually "1", meaning you should pause for 1 second and then resume sending requests.Cached Requests: Requests that hit the CDN cache are not counted against the rate limit. In other words, if a piece of content has been recently requested and is cached at the edge, you can fetch it repeatedly without impacting your rate limit. This allows high-read scenarios to scale. Uncached requests (e.g., the first request after content changes, or requests including preview/draft content) do count toward the rate.
Example – Rate Limiting: If the limit is 55/sec and your client sends 85 uncached requests within one second, the API will return a 429 error on some requests:
This response means you should wait 1 second before retrying. Your client should implement retry logic when receiving 429 responses.
Common Resource Attributes (Sys Metadata)
All content objects returned by the API include a top-level sys property. This is a system-managed metadata object containing standard attributes for that resource. Most of the fields in sys are not editable by users and generated by the system. The exact sys fields vary by resource type, but generally you will see:
sys.type – The type of the resource (e.g. "Entry", "Asset", "ContentType", "Space").
sys.id – A unique identifier for the resource within the space. For example, an entry with sys.id = "abc123" can be retrieved directly via the API using that ID.
sys.space – A link object referencing the Space that the resource belongs to (contains its sys.id and sys.linkType = "Space"). Present on most resource types (entries, assets, content types).
sys.environment – A link object referencing the Environment (e.g., "live" or "dev") for this resource.
sys.contentType – (Entries only) A link referencing the Content Type that defines the structure of this entry.
sys.createdAt – Timestamp of when the resource was first created in the system (ISO 8601 format).
sys.updatedAt – Timestamp of the last update/publish of the resource.
sys.revision – Revision number (an incrementing version counter for published updates) for entries, assets, and content types.
sys.publishedVersion – If the resource is published, this indicates which revision was published (for concurrency control).
sys.locale – (Entries and assets) If this object is returned in a specific locale, sys.locale indicates which locale’s data you’re seeing.
Other fields that can be changed by users include:
sys.versionId – A unique version ID to identify the exact revision of the content. Can be used for caching snapshots of content that are immutable.
sys.uuid – The UUID of the content provided by the source site/system, e.g. Drupal. You can use this property to uniquely identify content across systems.
sys.customId – A custom, unique ID assigned by the source site/system, e.g. Drupal. You can use this property to uniquely identify content across systems.
sys.name – The name/title/label of the entry. This property may not be available for all content, but most content from source systems like Drupal will provide one.
sys.slug – The SEO-friendly path identifying a piece of content. This is not enforced to be unique as some use cases require multiple content items to be available with the same slug, e.g. when serving different variants of content depending on the source market. You can query for content by the slug using the entry API.
For example, a Space object’s sys might look like:
And an Entry’s sys might include its contentType and environment, e.g.:
Important: In Content Cloud, content is managed in Drupal, so there is no separate "Content Management API" (CMA) to edit these fields. The createdAt and updatedAt reflect when content was added to the Content Cloud from Drupal. The firstPublishedAt and publishedAt fields (found on published content) correspond to the first and latest publish times respectively.
The sys.revision increments on each publish, and sys.publishedVersion reflects the revision number that is currently live. As Content Cloud versions localized and non-localized properties separately, the revision property consists of a combination of both the untranslatated property revision and translated property revision.
Date and Time Format
All dates and times in API responses are formatted according to the ISO 8601 standard. For example: "2025-08-06T09:45:27Z" represents August 6, 2025, at 09:45:27 UTC. If no timezone offset is specified, UTC is assumed by default.
Supported formats for date-time fields include:
Date only: "YYYY-MM-DD" (e.g.,
"2025-09-15").Date and time (UTC): "YYYY-MM-DDThh:mm:ssZ" (e.g.,
"2025-09-15T09:45:27Z").Date and time with offset: "YYYY-MM-DDThh:mm:ss±hh:mm" (e.g.,
"2025-09-15T09:45:27+00:00"or"2025-09-15T09:45:27-08:00").
Ensure to include the timezone (offset or Z for UTC) when specifying times; otherwise, UTC is assumed. All timestamps returned by the API are in UTC by default.
Collection Resources and Pagination
When you request a collection of items (for example, retrieving all entries in a space), the response is wrapped in an object that includes metadata for pagination. A typical collection response looks like:
Here:
itemscontains an array of the resources (entries, assets, etc.) returned.totalis the total number of items available (across all pages) for that query.skipis the offset of items skipped (not included) from the start; in this example 0 means we’re on the first page.limitis the maximum number of items returned in this page (100 in this example).
By default, the API returns up to 100 items per page. You can request a smaller or larger page size using the limit query parameter (maximum limit is 1000). To get the next page of results, use the skip parameter to offset the next request by the number of items already retrieved. For example, to get the second page (items 101–200), set skip=100 in the query. Continue increasing skip (in multiples of the limit) to page through all results.
Ordering and consistency: When paging through results, it’s recommended to specify an explicit sort order so that items remain in a consistent order across pages. For example, use order=sys.createdAt (or another field) to sort entries by creation time. This prevents items from appearing or disappearing between pages if new content is published during pagination.
Reference
Now that we’ve covered the basics, the following sections describe each type of resource you can access through the Content Cloud REST Delivery APIs, along with available endpoints and query options.
Spaces
All content in Content Cloud is organized into Spaces. A Space is a container for content (entries, assets, content types, etc.), analogous to a site or project. You might use separate Spaces for different projects or environments (e.g., one Space per content repository from your Drupal projects).
As Spaces are logically separated, content can only be requested from one Space at a time. It’s important to consider how many Spaces are needed and how to divide content, depending on the use cases you want to implement.
Space
A Space has metadata such as a name and a set of locales (languages) configured. You will typically have at least one Space connected to your Drupal site’s content. Source sites/services like Drupal will sit in a separate space and are connected by links to sync content from e.g. Drupal to Content Cloud.
Get a Space: To retrieve basic information about a Space, use the following endpoint:
Parameters: None (besides the required Space ID in the URL and authentication token). The response will include the Space’s sys metadata and attributes like name and locales.
Example Request:
Example Response:
In this example, the Space is named "My Website Content Space" and has two locales (English as the default, and French which falls back to English if content is not translated).
Locales are automatically created based on your connected source Spaces. E.g., if you connect 1 Drupal site with the locale codes “en” and “fr” and another one with only “de”, the Content Cloud Space will provide the three locales “en”, “fr” and “de”.
The default locale is set when a Content Cloud Space is first created and cannot be changed later. Most organizations use “en” as their default locale.
Content Types
A Content Type in Content Cloud defines the structure of a content item (entry). It is analogous to, e.g. a Drupal content type or entity bundle. It specifies which fields an entry has (text, media, references, etc.) and their data types. Defining content types is a fundamental step in modeling your content for headless use.
Each Space has a set of content types (its content model). Content types themselves have metadata (name, description, fields array, etc.) and are identified by an ID (usually an alphanumeric string).
Both Content Types and fields also have a mandatory machine name as a developer-friendly alternative when querying content. Most endpoints asking for Content Type references will accept both the machine name and the auto-generated ID. You can reuse the same machine name between Spaces and Environments and query for, e.g., Article content across Spaces and Environments using the same machine name.
Content Model (All Content Types)
Get the content model of a Space: You can fetch the collection of all content types in a Space:
Replace {ENVIRONMENT_ID} with the environment you want (e.g., live or dev). This returns an array of all content type definitions in that environment.
Parameters: Supports pagination (limit, skip as with other collections) and optional locale (though content type names are not localized).
The response’s items will be content type objects, each containing fields definitions.
Example: To get all content types in the live environment of a space, you might call:
This would return a structure similar to:
Each field in a content type includes attributes like id, machineName, name (human-friendly), type (data type), whether it’s localized, required, etc. Link fields have a linkType (Entry or Asset) and may include validations restricting which content types can be linked.
Content Type (Single)
Get a single content type: To fetch a specific content type by ID:
Provide the Content Type’s machine name (for example, BlogPost in the above example). The response will be a content type object with the same structure as one item from the collection.
Use this to retrieve details of a specific content model if you already know its ID or machine name, or to verify its field structure in your application logic.
Entries
Entries represent actual content entries (instances of a content type). For example, if you have a “Blog Post” content type, each blog article is an entry of that type. Entries contain the fields defined by their content type, with values for each field.
Entries in the JSON response have the following top-level structure:
metadata: Contains user-defined metadata like tags or taxonomy concepts applied to the entry (if any).For example, metadata.tags will list any tags on the entry.
sys: System metadata as discussed (type, id, contentType link, etc.).fields: An object containing the content fields and their values. The keys in fields correspond to the field IDs defined in the content type.
By default, when retrieving entries, the field values are returned for the default locale (and any fallback logic is already applied by the server). If you request a specific locale, the fields object will contain only that locale’s values.
For example, a Blog Post entry might look like:
Entries Collection (List all entries)
To fetch all entries (or a subset of entries matching a query) in a space/environment, use:
By default, this returns entries of all content types in that environment. You can filter by content type or other criteria using query parameters (see Search Parameters). The response will be a collection (array) of entry objects, with the standard sys/skip/limit/total/items wrapper as described earlier.
Includes: If any entries contain links to other entries or assets, those linked items may be included in a special includes section of the response to avoid additional API calls. The Delivery API resolves links up to a certain depth (default 1 level) unless otherwise specified with the include parameter. Links that cannot be resolved (for example, a link to a deleted or unpublished entry when using the Delivery API) will simply remain as link objects without a matching item in
includes.
Example – Get all entries:
Parameters: You can use various parameters to filter the entries or shape the response:
content_type: limit results to entries of a specific content type ID.Query operators like
fields.fieldName[eq]=valueto filter by field values (discussed in Search Parameters).include: to specify how many levels of linked entries/assets to include (default 1, max 10).limitandskipfor pagination, etc.
By default, the first 100 entries (sorted by creation date descending, i.e., newest first) are returned. If there are more, you will get a total count and can retrieve further pages by incrementing skip.
Entry (Single entry by ID)
To fetch a single entry by its ID (when you know the ID):
This returns the entry object directly (not wrapped in an array). If the entry has links to other entries/assets, those will not be automatically included via the includes array when using this single-entry endpoint. (The includes mechanism is only used for collection queries). So, a single entry request will show linked items only as Link objects containing the target IDs. You would need to fetch those linked entries separately by ID, or use the collection endpoint with include if you want resolved links in one response.
Example:
Returns the entry with ID "abcd1234" (if it exists and is published in the live environment).
Unresolved Entries and Errors
In some cases, when querying a collection of entries, you might encounter unresolvable links. For instance, an entry references another entry that has been deleted or is not accessible (e.g., a draft in Delivery API). The API will handle this gracefully by omitting the missing linked item in the includes and providing an errors array detailing which links couldn’t be resolved.
For example, if an entry in the results links to an entry that no longer exists, the response may include an errors section like:
Each error lists the id of the link that could not be resolved. This can also happen due to permission issues (e.g., trying to retrieve drafts with a delivery token). These errors are mainly informational; the rest of the response will still contain the entries that were resolvable. Your application should handle missing linked content (e.g., skip or indicate broken reference).
Assets
Assets are binary files (images, PDFs, videos, etc.) that are part of your content. In Content Cloud, assets are often referenced from entries (e.g., an Image field on an entry links to an Asset). Assets themselves have some metadata, like title and description, and one or more file objects (often one file per locale).
Just like entries, assets have:
metadata: for tags or taxonomy applied to the asset.sys: system info (type=Asset, id, space, environment, etc.).fields: which include file information and optional descriptive fields.
Asset fields typically include:
title (Text) – a short human-friendly title for the asset (often used as alt text for images).
description (Text) – a longer description or caption (optional).
file (File object) – the actual file data, including:
fileName – original filename.
contentType – MIME type of the file (e.g., "image/png", "application/pdf").
details – an object with file details (size in bytes, image dimensions if applicable, etc.).
url – the URL where the file can be downloaded.
If the asset is localized (i.e., you provide different files per locale), you can request the appropriate file by passing in a locale parameter as you do for the Entry API.
An example Asset (image) in a response:
Notice the url fields. These URLs point to the actual file on the Content Cloud CDN. By default, image assets’ URLs will use a domain optimized for images, and other files might use a different domain or path optimized for file delivery (e.g., a dedicated asset domain). These URLs can be used directly in your front-end <img> tags or downloads.
You can use the different URL variants for specific use-cases:
imageUrl: Allow image transformations (see below), default aturlfor image-type assets.embedUrl: Send an attachment header to treat the file as embedded, e.g., to ask browsers to display a PDF file inline rather than downloading it.downloadUrl: Send an attachment header to treat the file as an attachment, e.g., to ask browsers to download a PDF file rather than displaying it inline.
Other notes:
Large assets may use a special domain. Please reach out to us if you’d like to learn more.
Image Transformations: You can append query parameters to image asset URLs for resizing and other transformations (such as width, height, format conversion, cropping, etc.). For example, adding ?w=500&h=300&fit=thumb might return a 500x300 thumbnailed version of an image. See the Images API documentation for available transformations. (These transformations do not affect the original image; they are applied on-the-fly by the CDN.)
Video assets: If videos are stored, their URLs may use a different domain or have specific streaming support. Please reach out to us if you’d like to learn more.
Permissions: The URL fields are only populated if the user has the permission to access asset files. Otherwise, they will not be provided.
If the user has access to previews and to asset files, the URLs will also be provided for draft assets; otherwise, they will not be included in the response.
Assets Collection (List all assets)
To get a list of all assets in a space/environment:
This returns a collection of Asset objects. As with entries, you can paginate through assets with skip and limit. You can also filter assets by their fields using query parameters (for example, by file type).
Filtering assets by file type: Use the mimetype_group query parameter to filter assets by general MIME category. Valid values might include image, audio, video, richtext (for PDF, JSON, etc.), presentation (PPT, etc.), and spreadsheet. For example:
would return only image assets.
Asset (Single asset by ID)
To fetch one asset by ID:
This returns the asset object with its fields for each locale. As with single entries, the response is just the asset (no includes). If you need to get multiple assets, use the collection endpoint or multiple single calls.
Securing Assets of Embargoed Content
This is an advanced feature for securing assets. If you don’t need time-limited access control on assets, you can skip this section.
Embargoed Assets: In some cases, you may want to restrict access to certain asset files so that they are not publicly accessible via their URL. Content Cloud supports signed asset URLs for such scenarios. An “Asset Key” is used to sign URLs for embargoed assets.
Note: This feature may only be available on certain plans or for specific secure content. By default, all asset URLs are publicly accessible if you know the URL. Secure asset URLs require this signing process.
If a Space has embargoed assets enabled, asset URLs will be delivered on a special secure domain (e.g., space-environment.preview-assets.cloud.content-sync.io) and will not work unless a signature is provided. To access these, you must either use the provided signed URLs from the asset API (see above under Permissions). Signed URLs are only valid for 1 hour by default. To request an asset later, you have to request a new signed URL.
Locales
Locales define the languages (or regional variants) available in a Space for content localization. Each locale has: a code, name, and an optional fallback locale.
code: The locale code used in the API, e.g., "en-US" for U.S. English or "fr" for French.name: A human-readable name, e.g. "English (United States)".default: A boolean indicating which locale is the default for the space (the default locale is used when no specific locale is requested).fallbackCode: If set, indicates another locale code that serves as a fallback if content is not available in this locale. If no fallback is desired, this can be null. Fallback chains can’t be cyclic and typically end at the default locale.
Locale Collection
Get all locales of a space:
This returns an array of locale objects configured for that environment/space. Usually, locales are space-wide settings (and thus the same across all environments of a space). One locale will have "default": true; this is the default locale used when you fetch entries without specifying a locale.
Example Response:
In this example, en-US is the default locale and has no fallback (it’s the root), while French falls back to English if a field is not translated to French.
Tags
Tags are non-hierarchical labels you can attach to entries and assets for arbitrary grouping. They differ from taxonomy concepts in that tags are typically simpler keywords and are managed on an environment scope.
In Content Cloud:
Tags have an id and a human-readable name.
Tags have a visibility; only tags marked public are exposed via the Delivery and Preview APIs. All tags you create in Content Cloud for content categorization are likely public by default unless there’s a private distinction.
Tags are environment-specific: a tag created in the live environment is separate from one in dev environment, even if named the same. That is unless they share the same tag ID or UUID from the source system or site like Drupal.
Tags are automatically generated based on, e.g., taxonomy terms from Drupal. Tags usually have an owning content item that’s created based on the tag or term from the source site. E.g., if you have a vocabulary Industry in Drupal and push a term from this vocabulry named Pharmaceuticals to the Content Cloud, it will create an Entry Pharmaceuticals of type IndustryTerm and a tag Pharmaceuticals. This is to provide convenience features like filtering Entries by the assigned tags.
Each entry or asset’s metadata may contain a tags array listing tag links applied to it (see above).
Tag Collection
Get all tags:
Returns all tags in that environment that are visible via API (public tags). Each item has an id and name.
Example item:
Tag
Get a single tag:
Returns one tag by ID (if it exists and is public in that environment).
Tags on entries and assets
As mentioned, entries and assets include their tags in metadata.tags. You can filter content by tags using similar search parameters on metadata.tags.sys.id:
To require an item to have a specific set of tags (all of them): use
metadata.tags.sys.id[all]=tagId1,tagId2.To find items that have at least one of a set of tags: use
metadata.tags.sys.id[in]=tagId1,tagId2.To find items that have any tag (or no tag): you can check existence. For example,
metadata.tags[exists]=truefinds entries that have at least one tag.
These filters work similarly to the concept filters. Tags are a convenient way to mark content with arbitrary labels (like “Featured”, “ReviewPending”, etc.) and then retrieve those subsets.
Note: Only public tags are returned in Delivery API. While terms are public by default, double-check the visibility property if you want them to appear in responses and be filterable.
User Data Stores
In some cases, you may want to store additional user-specific data that’s related to content. Think of users wanting to bookmark content, store content customizations, or store the reading state. Content Cloud provides rich user data stores for this that can be used in all delivery APIs. Data is stored per space, so it is shared between environments but unique to each user. User data can be stored, retrieved, and used in filters. E.g., when a user opens an article, you can assign a reading timestamp; later, when you want to show the content the user hasn’t read yet, you can filter for content without a reading timestamp assigned.
After creating a user data store in the Content Sync backend, you can opt-in to receiving the user data associated with an entry. The user data is specific to the user requesting the content and only works when providing a user ID in the JWT.
Please note that requests leveraging user data stores may only go to the live and preview endpoints. Trying to load or update user data using the CDN endpoint will result in an error.
The user ID is taken from the sub_id JWT property. If no sub_id is found, the sub property is used instead. If you are using a third-party authorization service like Auth0, AWS Cognito, or Microsoft Entra, this will be the user ID in that system. If you control the user ID yourself through self-signed JWTs, we recommend assigning a namespaced user ID and avoiding the use of personal information like email addresses (emails may also change). Ideally, use a unique ID that’s namespaced to the source system, e.g., { "sub": "{SOURCE_SYSTEM_PREFIX}/user/{USER_ID}", ... }.
Retrieving user data
To fetch a single entry by its ID and include user data with it:
This returns the entry and, if the user has stored user data with it, the associated user data:
This works the same when retrieving lists of entries:
Adding and updating user data
To add or update user data:
Example request body:
Example response body:
Permissions
Retrieving user data requires the permission:user-data:read permission. Writing user data requires the permission:user-data:read and permission:user-data:write permissions.
Users can only retrieve data from the user data stores that are explicitly allowed through their JWT. E.g., to provide access to the ExampleContentUserData store, a user must have either the content-user-data:ExampleContentUserData scope or the allow-all scope content-user-data:*.
Limitations
User data stores don’t support nesting, so properties have to use flat types like strings, numbers, or JSON data.
The REST API will only return data for the first level of content and not for embedded, linked content. If you require user data for linked content, you have to retrieve it through a separate request to that resource.
Advanced
Shared IDs (group data)
For advanced use cases, you can also create a shared user ID, e.g., for all users in a specific group. JWTs with shared IDs should be as limited in scope and use as possible. JWTs with a shared ID allow you to synchronize settings across multiple users, e.g., to store progress within a project. You can control which user can read or write to the user data store by assigning the user data read and/or write permissions.
Settings
To store, e.g., user-specific settings that are independent of content, create a user data store for those settings, then create a static content item with a specific customId or UUID and use this content item to store user-specific settings that can be shared across devices. To store settings for different front-ends, you can either create different user data stores or use different content items for different settings; it is generally more efficient to use more content and less types.
Errors
No user data available
When the environment doesn’t have any user data store or the user doesn’t have access to them, Content Cloud will respond with a 400 Bad Request status code and an error message like "\"ExampleForMissingType\" is invalid for user_data_types: This environment doesn't provide user data.".
The requested user data type is not available
When the environment doesn’t have the requested user data store or the user doesn’t have access to it, Content Cloud will respond with a 400 Bad Request status code and an error message like "\"ExampleForMissingType\" is invalid for user_data_types: Invalid user data type ExampleForMissingType.".
No user provided
When no user ID is provided, Content Cloud will respond with a 400 Bad Request status code and an error like "\"ExampleForMissingType\" is invalid for user_data_types: User ID is required to access user data.".
Links and Includes
Content entries often reference other content entries or assets via Link fields. The Delivery API provides ways to retrieve these related items without requiring separate calls for each link, using the include query parameter.
Retrieval of Linked Items (Includes)
When you request a collection of entries, you can specify include=<n> to tell the API to resolve linked entries/assets up to <n> levels deep. By default, include=1, meaning it will include one level of linked resources. If not specified, the API typically assumes include=1 for entry collections.
Includes Array: The response will contain an
includesobject with potentially two arrays:EntryandAsset. For example,includes.Entrywill list entry objects that were linked from the initial items but not already present in the main items list. Similarly,includes.Assetlists asset objects for linked assets. This way, each linked item is returned once.Depth: You can increase the depth if you have multi-level relationships (e.g., an entry links to another entry which links to another).
include=2would resolve two levels deep, and so on. The maximum is 10. If you exceed the max, the API returns aBadRequesterror. If you setinclude=0, the response will contain only the primary entries and no linked includes (link fields will just be unresolved Link objects).Performance: Be mindful that higher include levels can significantly increase payload size and processing time, especially if content is highly interconnected. Only request the depth you need. As the number of items returned scales roughly exponentially based on the
includeandlimit, it’s usually a good idea to request fewer items with more includes or more items with fewer includes.
Example: Suppose you have an Article entry that has a reference to an Author entry (entry link) and an Image asset. A query for articles with include=1 will return the Article(s) in items, and in includes.Entry it will include the Author entry object, and in includes.Asset the Image asset object (assuming those were not already in items). Your application can then assemble the data without making separate requests for author or image.
Limitations: The include parameter only resolves links within the same space (and environment). It does not automatically pull content from other spaces.
Also, it only pertains to entry collection endpoints (/entries). On single entry fetches, includes are not applied. On assets collections, assets can include links (e.g., to a source File entry from the source system like Drupal) which would also use includes.
Links to a specific item (Reverse Links)
If you need to find all entries that link to a particular entry or asset, the API provides search parameters:
links_to_entry=<Entry id>links_to_asset=<Asset id>
Using these query parameters on the entries or assets endpoint will filter results to only those that have a link to the specified entry or asset somewhere in their fields.
Example: To find all entries that reference entry 12345 (perhaps find all articles written by a specific Author entry), you could query:
This will return any entries that have at least one field linking to the entry with ID 12345.
Similarly:
would return entries that include asset abcde in one of their fields (for example, all entries that use a particular image asset).
This is simpler than manually filtering by each link field.
Ensure to specify content_type filter if needed to narrow the search domain, but it’s not strictly required for links_to_* searches.
Search Parameters (Filtering and Querying)
The Content Cloud Delivery API provides powerful query parameters to filter and search for content in collection endpoints (entries, assets, content types, etc.). By appending these parameters to your GET requests, you can retrieve only the content that matches certain criteria, limiting payload and simplifying client-side filtering.
Below is a summary of the available query operators and how to use them:
content_type (Filter by Content Type)
Use content_type=<id or machine name> to restrict results to entries of a specific content type. This is especially important when filtering by fields, because the API needs to know which content type’s fields to filter on.
Example:
Returns only entries of the Blog Post content type.
Note: When using any field-based filters or ordering by a field, you must include a single content_type filter in the query. You can only query one content type at a time for those operations (except for global full-text search, which spans types).
select (Projection of Fields)
The select operator allows you to pick which fields (and/or sys properties) to return in the response, rather than getting the entire entry/asset object. This can reduce payload if you only need certain fields.
Usage:
select=sys.id,fields.title,fields.author
This would result in each entry object only containing the sys.id, the fields.title, and fields.author (and not other fields or sys properties).
You can also select entire blocks:
select=syswill include the whole sys object.select=fieldswill include all fields (equivalent to not using select for fields).You cannot nest deeper than 2 levels in select. For example,
fields.myField.fields.nestedFieldis not valid.
You can select up to 100 properties. If you request a property that doesn’t exist, you’ll get a 400 error listing the invalid path.
Example:
This returns each product entry with only its ID, name and SKU field.
Equality operator (field[eq])
You can filter for exact matches on field values using the equality operator. In query string, this is implicit by just assigning a value:
For non-text fields (Symbol, Number, Boolean, etc.), use
fields.fieldId=valueto find entries where that field exactly matches the value.For text fields (Long text), exact match is generally not supported because full-text search is used instead (see below). For case-sensitive exact matching on text, the API doesn’t provide direct
eq, but you can perhaps use[match]for full-text.
If you want to find an entry by ID via query (instead of using the direct ID endpoint), you can use sys.id=<ID> or the generic equality.
Example:
/entries?sys.id=0500asdasa3v5KdahS
would return the entry with that ID (if you want it included with includes resolution, for instance).
Or
to find blog posts with slug field equal to "/welcome".
Note: equality (and inequality) are not supported on Text area (multi-paragraph text) fields, and you must constrain by content_type if filtering by fields as mentioned.
Inequality operator (field[ne])
Use the [ne] operator to exclude items that match a value. For example:
fields.status[ne]=archived
would return entries whose status field is not "archived".
This works for the same types as equality (not for large text fields as a direct filter).
Example:
/entries?content_type=product&fields.inStock[ne]=true
to find all products that are not in stock.
Array equality/inequality (field[eq] / field[ne])
If a field is an array (list) of values, the behavior of = and [ne] is:
[eq](or implicit) will return an entry if any element of the array matches the value.[ne]will exclude an entry if any element matches the value (meaning the entry is excluded if it contains that value).
So for example, if fields.tags is an array of strings on an entry:
fields.tags=accessoriesreturns entries where "accessories" is one of the tags.fields.tags[ne]=accessoriesreturns entries where "accessories" is not in the tags (none of the tags is "accessories").
Remember to include content_type if filtering by a field.
Array with multiple values (field[all])
To require an array field to contain all of a set of values, use the [all] operator with a comma-separated list.
Example:
fields.tags[all]=red,large
This finds entries whose tags array contains both "red" and "large" (order doesn’t matter).
This is an AND condition across the values.
Inclusion (field[in])
Use [in] with a comma-separated list to find entries where the field value matches any one of the values in the list. This is like SQL IN clause or a logical OR among multiple values.
For non-array fields: fields.status[in]=draft,published would get entries whose status is either "draft" or "published".
For array fields: fields.tags[in]=sports,news returns entries that have "sports" or "news" in their tags (at least one match).
Exclusion (field[nin])
[nin] is the negation of in. It will include entries where the field’s value is none of the listed values.
For example, fields.category[nin]=announcement,release gives entries whose category is neither "announcement" nor "release".
On array fields, an entry’s array must have none of those values to be included.
(Remember content_type requirement when using these on fields.)
Existence (field[exists])
The [exists] operator checks whether a field has a value (is present) or not. It expects a boolean true or false.
fields.subtitle[exists]=truereturns entries that have a value for the "subtitle" field (even if that value might be an empty string or false – as long as the field is defined it counts as exists).fields.subtitle[exists]=falsereturns entries where "subtitle" is not set at all.
This is useful for optional fields or to find content that is missing certain data. For assets, you could find which assets have no description by fields.description[exists]=false.
Be careful with case sensitivity: use true/false exactly (lowercase).
Ranges (field[lt], field[lte], field[gt], field[gte])
For numeric or date fields, you can filter by ranges using:
[lt](less than, <)[lte](less than or equal to, ≤)[gt](greater than, >)[gte](greater than or equal to, ≥)
These are applied to field values that are numbers or date strings.
Example:
fields.price[gt]=100
returns entries where price > 100.
fields.eventDate[lte]=2025-12-31T23:59:59Z
returns events on or before the end of 2025.
When filtering by a field with ranges, you must specify content_type (to know which field schema).
Dates should be in ISO 8601 format. The comparison is done on the timestamp.
Full-text search (global)
To perform a full-text search across all text fields of all entries (within the specified content_type if given, otherwise across all types), use the query parameter with your search string.
Example:
/entries?query=holiday+sale
This will return entries where the words "holiday" and "sale" appear in any text or symbol field in the entry (in any order, and possibly in different fields).
Global full-text search is useful when you don’t know which field the text might be in, or you want to search across multiple fields at once. It operates on all text-based fields (including titles, body text, etc.) that are indexed for search.
It’s an OR search by default (content containing either term). To require both terms, you can include them together (it generally ranks results by relevance).
Note: This search is not locale-specific by default; it will search the default locale content. If you want to search within a specific locale’s text, you might need to add locale param.
Full-text search on a specific field
If you want to search within a particular text field, use the [match] operator on that field.
For example:
fields.description[match]=sale
would find entries whose description field contains the word "sale" (full-text match).
This is useful to narrow scope, e.g., search in title only:
fields.title[match]=Holiday
to get entries with "Holiday" in the title.
Partial and fuzzy matching are handled by the search engine – it’s not an exact substring but rather tokenized word search.
Important: [match] only works on Text fields (long text or symbol fields). It won’t work on fields like integers or keywords.
Links to entry/asset (links_to_entry / links_to_asset)
As mentioned earlier in Links to a specific item, you can filter entries by those that reference a given entry or asset ID:
links_to_entry=ENTRY_IDlinks_to_asset=ASSET_ID
These are very handy for reverse lookups (e.g., find all pages that include a reference to a certain media asset).
Example:
/entries?links_to_asset=asset123
finds all entries containing that asset.
User data filters
You can filter by user data using a special user_data.*.* query parameter, for example:
user_data.FlagsUserData.bookmarked=true: Only return content the user has bookmarked.user_data.FlagsUserData.readAt[exists]=false: Only return content the user has not read yet, so content that is new to the user.
Order
By default, results are ordered by the system default (which is usually descending by creation or publish date for entries). You can control sorting using the order parameter:
To sort by a field in ascending order, specify the field’s path (for example
fields.price).For descending order, prefix the field with a minus sign, e.g.
-fields.price.
You can also sort by sys properties like sys.createdAt or sys.updatedAt.
Examples:
order=fields.title– sort entries alphabetically by title (A→Z).order=-fields.title– sort entries by title Z→A.order=sys.createdAt– oldest first.order=-sys.createdAt– newest first.order=fields.releaseDate– earliest releaseDate first (assuming that’s a Date field).order=-fields.releaseDate– latest releaseDate first.
If you want to ensure deterministic order, you can specify multiple sort keys (see below).
Reverse order (shortcut)
E.g. order=-sys.updatedAt gives reverse chronological by last update (newest first).
Order with multiple parameters
You can sort by multiple keys by comma-separating them in one order parameter. The order of fields in the list is their priority.
For example:
order=fields.category,fields.title
would sort primarily by category, and within each category by title ascending.
Or:
order=fields.priority,-sys.createdAt
– sort by a custom priority field (lowest to highest), and for equal priority, sort by createdAt descending (newer first).
Note: When ordering by a field, if that field is missing on some entries (null), those entries appear first when ordering ascending and last when ordering descending.
limit and skip (Pagination)
We covered these under “Collection and Pagination,” but as a summary:
limit: how many items to return (max 1000, default 100).skip: how many items to skip (offset) from the beginning.
Example: ?limit=50&skip=50 to get the 2nd page of 50 items.
Filtering assets by MIME type
As mentioned, mimetype_group can be used on the assets endpoint to filter by general file type category:
mimetype_group=image– onlyimage/*MIME types.mimetype_group=audio– only audio files.mimetype_group=video– only videos.mimetype_group=richtext– PDFs, JSON, and other "rich" documents.mimetype_group=spreadsheetorpresentation, etc., if supported.
For example:
to get recently updated images.
Search on references (linked entries)
While you cannot directly filter by a linked entry’s sub-field in REST (that’s more a GraphQL thing), you can combine filters to achieve similar results. For instance, to find all blog posts where the author’s name is "Alice", you might: 1. Query the author entries for name "Alice" to get her entry ID. 2. Then use links_to_entry=thatAuthorId on blog posts.
There isn’t a direct one-step filter in REST to say fields.author.fields.name=Alice. Instead, use the above two-step approach or use GraphQL for such queries.
However, if the content model has validations restricting links to certain types, you can sometimes search by the linked entry’s ID directly if known (as shown with links_to_entry).
Localization (Working with Locales)
By default, when no locale parameter is specified, the Delivery API returns entries in the default locale of the space. Each entry’s fields will contain only that locale’s data (and any missing translations will fall back as configured in the space).
To work with multiple languages, you have a few options:
Specify a Locale: Add
locale=<locale code>to your query to retrieve content in a specific language. For example,?locale=frwill return each entry’s fields only in French (if available; if not available and a fallback is defined, you may get the fallback content with an indication of the fallback viasys.locale). If a field is not translated and fallback is defined, the field may be omitted and the entry’ssys.localemight reflect the fallback content’s locale.Fallback behavior: The API will automatically apply fallback locales if a translation is missing. If you request a specific locale and the entry doesn’t have that locale, the response’s
sys.localemay indicate which locale’s content you actually got. For example, if you requestlocale=frbut an entry isn’t translated to French, the API might return the entry with English content andsys.locale: "en-US"(the fallback) unless you disabled fallback (which can be configured via thefallbackCode).
Creating content in multiple locales: This is done through the management side (e.g., Drupal). The Delivery API just delivers whatever is there.
Retrieving localized entries example:
This gives all article entries in German (assuming de-DE is a locale in your space). If some articles don’t have German versions, they’ll come back with fallback content (perhaps English) and sys.locale reflecting that.
If you want to only get entries that have German specifically, you can handle it in the application (e.g., check if sys.locale == "de-DE" for each entry returned).