ContentGrid Applications expose a REST API that can be used to programmatically access the metadata and content stored in your ContententGrid application.
The API documentation gives examples using curl, but various tools or libraries can be used for making calls to the API.
An access token is required to access a ContentGrid Application.
Access tokens are always obtained through OpenID Connect (OIDC). The authentication token contains user identity information and the IAM attributes assigned to the user.
OIDC clients
To identify the application that performs the authentication, an OIDC client needs to be registered in the Organization’s IAM Realm.
An Organization can have multiple IAM Realms; ensure you are using the same Realm as the one used by the Application.
An OIDC client can be configured in the “Clients” tab of the IAM Realm.
Setting up an OIDC connection requires 3 configuration parameters that can be obtained from the OIDC client page:
Issuer URI
Client ID
Client Secret
OIDC configuration can be discovered from the Issuer URI using OIDC Discovery.
If OIDC discovery is not supported by your software, you can obtain the authorization and token endpoints manually from the discovery document, but this is not recommended.
Service account authentication
When using service account authentication, the access token is issued with its own privileges instead of with the privileges of a user.
By default, no IAM attributes are assigned to a service account. These need to be set up if the applicable permission policies require it to provide access.
When using user authentication, the access token is issued with the privileges of the user.
To use user authentication, you need to use the Authorization code grant.
This flow will necessary include a pass through the user’s web browser as part of the flow to obtain an access token.
ContentGrid applications expose a REST API using JSON as a response format.
All JSON responses use HAL as a hypertext format, linking to related resources.
Actions that can be taken on resources are specified using the HAL-FORMS format.
This documentation covers the basics of using the Application REST API, advanced usage with HAL is described in HAL Usage.
Because the specific API is generated from the configured data model, the examples will use an example datamodel.
OpenAPI spec
An OpenAPI spec for your application’s datamodel is available from the ContentGrid Console. Every Release has its own associated OpenAPI spec, which you can view or download.
For deployed applications, a copy of the OpenAPI spec is also available on /openapi.yaml (after authentication).
Resource types
A ContentGrid application can be configured with kinds of entities that have attributes and relations.
A single instance of such an entity is generically referred to as an entity-item. Multiple entity-items of the same kind together form an entity-collection.
These two form the main resource types in a ContentGrid application.
To support these resource types, a couple of additional resource types exist. Their relation to the main resource types is explained in the Resource Types reference.
Basic operations
This section explains all the basic operations (create, read, update, delete) for the resource types.
The operations are explained in their minimal form, without any request modifiers applied.
Tip
You can follow along with the examples by creating an application with the Example datamodel set up.
Define the following variables to make the examples easily runnable.
REGION="<Region where the application is deployed>"APP_ID="<ID of the application>"REALM="<Internal reference of your IAM Realm>"CLIENT_ID="<IAM Service account Client ID>"CLIENT_SECRET="<IAM Service account Client Secret>"
You will need to obtain an access token. This can be done easily with an IAM service account.
The Authentication documentation contains more details on this.
An entity-item resource is exposed on the path /<entity-name-plural>/{id} (e.g. /invoices/{id} for the invoice entity type).
{id} is a placeholder for the actual ID of the entity-item.
The following operations are available:
Method
Content-Type
Description
GET
application/hal+json
Read the entity-item with the specified id
PUT
application/json
Replace the entity-item data with the data supplied in the request body
PATCH
application/json
Update the entity-item data, modifying only the fields supplied in the request body
DELETE
N/A
Remove the entity-item
Note
The difference between PUT and PATCH is how attributes that are not present in the request body are handled:
PUT: It is written to the entity-item as null, removing the stored value.
PATCH: It is skipped, the stored value is unchanged.
This rule also applies to attributes of type content. Omitting the field will remove the content.
Update entity-item using PUT/PATCH
To show the difference between updating an entity-item with PUT or PATCH, we first show the existing state
An entity-collection has 3 query parameters that control the collection itself:
Query Parameter
Type
Description
_size
integer, 1-1000 or absent
Page size, the number of entity-item resources that will be returned on a single page
_cursor
string or absent
Cursor for the page to retrieve. Absent to retrieve the first page
_sort
string or absent
Sort the collection by an attribute. Parameter can be repeated multiple times to sort on multiple attributes
Pagination
By default, every page contains 20 entity-items. This can be changed with the _size query parameter.
When there are multiple pages, the meta information will contain prev_cursor and/or next_cursor fields.
A previous or next page can be retrieved by setting the _cursor query parameter to the value in the prev_cursor or next_cursor field.
Note
A cursor is not a fancy page number.
Cursors are opaque. A client should not parse or attempt to modify a cursor; they are only meaningful to the server.
Cursors are unique for every entity-collection. They can not be reused in a different context or used with different filter parameters.
Cursors are ephemeral. They should not be stored permanently to use at a later point in time, and they do not permanently identify a certain page
GET /invoices?_sort=received,asc&_sort=total_amount,desc HTTP/1.1Authorization:Bearer $TOKEN
Filtering
Search filters can be applied to the entity-collection. By default no filters are applied.
All filters are application-specific, and are mapped directly to query parameters. The OpenAPI spec lists all the valid filters as query parameters.
Note
Using a query parameter that is not a known search filter or one of the special query parameters will be ignored and have no effect.
Multiple search filters can be combined.
Different search filters are AND’ed together; repeating the same search filter multiple times will perform an OR on the different values of that filter.
Example of collection filtering with multiple search filters
GET /invoices?total_amount=15.95&total_amount=123.4&pay_before=2024-08-14 HTTP/1.1Authorization:Bearer $TOKEN
relation operations
An entity can have relations to a different entity. These are exposed on the path /<entity-name-plural>/{id}/<relation-name>. (e.g. /invoices/{id}/supplier for the invoice entity type and a relation supplier)
Depending on the relation type, the available operations and responses are different.
to-one relation operations
These operations are applicable to one-to-one and many-to-one relations.
Method
Content-Type
Description
GET
N/A
Read the relation link. Redirects to the entity-item that the relation refers to
PUT /invoices/$INVOICE_ID/supplier HTTP/1.1Authorization:Bearer $TOKENContent-Type:text/uri-listhttps://$APP_ID.$REGION.contentgrid.cloud/suppliers/$SUPPLIER_ID
HTTP/1.1204No Content
Reading the relation afterwards will result in a redirect to the specific item that was linked.
POST /suppliers/$SUPPLIER_ID/invoices HTTP/1.1Authorization:Bearer $TOKENContent-Type:text/uri-listhttps://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID
https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE2_ID
HTTP/1.1204No Content
Reading the relation will result in a redirect to a collection containing all items that are linked.
Note that the query parameter used in the redirect is subject to change and is not part of the public API.
A to-many relation also has a way to reference an individual item inside the relation; the relation-item resource.
These are exposed on the path /<entity-name-plural>/{id}/<relation-name>/{itemId} (e.g. /suppliers/{id}/invoices/{itemId} for the supplier entity type and a relation invoices).
Method
Description
GET
Read the relation collection item. Redirects to the entity-item if it is part of the relation collection
DELETE
Remove the relation collection item from the relation collection. No entity-items are deleted, only the link between the two is severed
Removing an item from a to-many relation
This will remove a single entity-item from the relation
An entity-content resource is exposed on the path /<entity-name-plural>/{id}/<attribute-name> (e.g. /invoices/{id}/document for the invoice entity type and a content attribute document). {id} is a placeholder for the actual ID of the entity-item.
The following operations are available:
Method
Content-Type
Description
GET
any
Retrieve stored file. If no file is currently stored, responds with HTTP 404 Not Found
PUT
any
Overwrite file with contents of request body
PUT
multipart/form-data
Overwrite file with contents of the file form-field
DELETE
N/A
Remove stored file
For the GET and PUT with arbitrary Content-Type; the filename is provided in the Content-Disposition header.
If no filename is provided during content upload, the filename field of the content attribute will be set to null
PUT /invoices/$INVOICE_ID/document HTTP/1.1Authorization:Bearer $TOKENContent-Disposition:attachment;filename="example-invoice.pdf"Content-Type:application/pdf....Omitted contents of file example-invoice.pdf....
HTTP/1.1204No Content
Overwrite content of an invoice using multipart upload
PUT /invoices/$INVOICE_ID/document HTTP/1.1Authorization:Bearer $TOKENContent-Type:multipart/form-data;boundary="delimiter123"--delimiter123
Content-Disposition: form-data; name="file"; filename="example-invoice.pdf"
....Omitted contents of file example-invoice.pdf....
--delimiter123--
HTTP/1.1204No Content
Request modifiers
The basic operations explained above can be extended with additional functionality.
Additional functionality is layered on top of the basic operations using HTTP headers.
Conditional requests
Conditional requests allow to check a precondition before applying the request to the target resource.
They are an implementation of RFC9110 Conditional Requests.
The primary usage of conditional requests is to prevent the “lost update” problem, where one system overwrites the changes of another system that made a write between the read and write of the first system.
Comparing without/with conditional requests
Without conditional request
With conditional request
sequenceDiagram
autonumber
participant a as System A
participant s as ContentGrid API
participant b as System B
a ->>+ s: GET item
s -->>- a: 200 OK<br>ETag: "abc"<br>original data
b ->>+ s: PUT item<br>new data
s -->>- b: 204 No Content<br>ETag: "def"
a ->>+ s: PUT item<br>other data
s -->>- a: 204 No Content<br>ETag: "mno"
note over a, s: "new data" was accidentally<br>overwritten by "other data"<br>without System A having seen it
sequenceDiagram
autonumber
participant a as System A
participant s as ContentGrid API
participant b as System B
a ->>+ s: GET item
s -->>- a: 200 OK<br>ETag: "abc"<br>original data
b ->>+ s: PUT item<br>new data
s -->>- b: 204 No Content<br>ETag: "def"
a ->>+ s: PUT item<br>If-Match: "abc"<br>other data
rect red
s -->>- a: 419 Precondition Failed
note over a, s : request was rejected<br>System A did not accidentally<br>overwrite the "new data"
end
Conditional requests can be used on the resources that have an ETag response header, which are these:
Name
URL
entity-item
/<entity-name-plural>/{id}
to-one relation
/<entity-name-plural>/{id}/<relation-name>
entity-content
/<entity-name-plural>/{id}/<attribute-name>
To perform conditional requests, a previously obtained ETag value has to be placed in the If-Match or If-None-Match headers. Note that the quotes are part of the ETag value.
The If-Modified-Since and If-Unmodified-Since headers are not supported, because no Last-Modified response header is present, and they are less precise.
Using a conditional request for updating
In this example, we’re going to move the pay_before date one day earlier.
curl -i -X GET https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID \
-H "Authorization: Bearer $TOKEN"
GET /invoices/$INVOICE_ID HTTP/1.1Authorization:Bearer $TOKEN
After retrieving the invoice response, we take note of the ETag header, and place if in the If-Match header for our next request.
Then we perform a PATCH request to update only the pay_before attribute.
Between our requests…
To execute this example, you will need to change the If-Match header to the correct value yourself.
HTTP/1.1412Precondition FailedContent-Type:application/problem+json{
"type": "https://contentgrid.cloud/problems/unsatisfied-version",
"title": "Object has changed",
"detail": "Requested version constraint 'is any of [exactly '1e0k4j9']' can not be satisfied (actual version exactly '1r061qt')",
"status": 412,
"actual_version": "1r061qt"}
Range requests allow retrieving a partial representation of the entity-content resource. They are an implementation of RFC9110 Range Requests.
Range requests are only supported on the entity-content resource (/<entity-name-plural>/{id}/<attribute-name>), as indicated by the Accept-Ranges header.
You usually don’t use range requests directly, but some tools that work with potentially large files (like PDF viewers or download managers) can make use of them.
Warning
When composing a file together from multiple parts, it is critical to check that the ETags of all parts are identical.
Otherwise, a concurrent change of the content will result in the composed file being corrupted.
Using a range request to retrieve a part of a resource
In this example, the first 4 bytes are fetched separately.
ContentGrid applications expose a REST API using JSON as a response format.
All JSON responses use HAL as a hypertext format, linking to related resources.
Actions that can be taken on resources are specified using the HAL-FORMS format.
Links connect resources together. Instead of constructing URLs yourself, you use the provided link to go to the related resource.
Link Relation Types
Links express a relation between the resource they appear on (the link context), and the link target.
The link relation type (RFC8288 Sec 3.3) determines the kind of relation between the link context and the link target.
CURIEs (Compact URIs) are a shorthand notation for extension link relation types.
They are used as defined in the HAL specification. Before comparing a link relation, a CURIE must be expanded into the full URI.
Instead of linking to a related resource, it is also possible to directly embed it in the main resource.
This is of only possible when the resource to be embedded is also a HAL resource.
In the ContentGrid application, this is mostly used for the entity-collection resource, where the page is the main resource,
and all items on the page are embedded resources.
Link Relation Types work the same for embedded resources as they do for links.
An embedded resource should have a self link.
The link attributes on the self-link of the embedded resource can be considered as
the link attributes that would be present on a normal link to the embedded resource.
Where HAL links describe related resources, HAL-FORMS templates describe the operations available on the resource.
HAL-FORMS are described in their own specification. ContentGrid adds additional functionality to the base specification, described in HAL-FORMS extensions.
The keys in _templates tell you what forms are available.
Search within the collection of the type described by the profile
HAL-FORMS Extensions
ContentGrid extends the HAL-FORMS specification with support for nested JSON objects, additional body encodings (multipart/form-data and text/uri-list), and extended options element behavior.
The entity-collection resource provides HAL links that can be used pagination.
Pagination uses the standard IANA-registered link relation types for their purpose:
first: First page of results (may be absent if you’re already on the first page)
prev: Previous page of results (absent when on the first page)
next: Next page of results (absent when on the last page)
self: Current page
To navigate through pages, follow the next link until it’s absent. The cursors in these links are opaque values managed by the server - don’t try to parse or construct them yourself.
Discovering the API Structure
Start at the entities-root resource to discover the entire API.
The root resource contains cg:entity links to all entity collections in your data model. Use these to discover what entities are available without hardcoding entity names.
Alternatively, follow the profile link to arrive at the profile-root, and follow the cg:entity links there to arrive at the entity-profiles for every entity in your data model.
Using Entity Profiles
Entity profiles provide machine-readable metadata about entity types in a ContentGrid application.
They describe the attributes, relations, constraints, and available operations for each entity type.
The entity-profile supports content negotiation between two formats:
The HAL-FORMS profile format is the richest representation. It describes the full model of an entity type, its attributes and relations, and provides search and create-form templates.
The ContentGrid REST API uses the standard HTTP status codes to signal errors.
RFC9457 Problem Details are used to provide additional information about the error condition.
HTTP Status Codes
The ContentGrid Application API uses standard HTTP status codes to indicate the success or failure of API requests.
These status codes provide high-level information about what happened with your request, and are generically described in RFC9110.
Successful Responses (2xx)
200 OK: The request succeeded. The response body contains the requested resource or data.
201 Created: A new resource was successfully created. The Location header contains the URL of the newly created resource.
204 No Content: The request succeeded but there is no response body. This is typically returned after successful updates or deletions.
Redirection Responses (3xx)
302 Found: The resource is temporarily available at a different URI. The Location header contains the URI where the resource can be found. This is commonly used when accessing relations.
Client Error Responses (4xx)
400 Bad Request: The request is malformed or contains invalid data. The response body may contain a problem detail explaining what went wrong if the request is sufficiently syntactically valid to be able to be processed.
401 Unauthorized: The request lacks valid authentication credentials.
403 Forbidden: The server understood the request but refuses to authorize it. This occurs when you don’t have permission to perform the requested operation.
404 Not Found: The requested resource does not exist. This could be an entity that doesn’t exist, an empty relation, or an unknown endpoint.
409 Conflict: The request conflicts with the current state of the resource. This occurs with unique constraint violations or when trying to delete an entity that is still referenced by required relations.
412 Precondition Failed: The condition specified in the request headers (If-Match or If-None-Match) was not met. This typically occurs when using optimistic locking and the entity has been modified by another request.
415 Unsupported Media Type: The Content-Type header specifies a media type that the server doesn’t support for this endpoint.
Server Error Responses (5xx)
500 Internal Server Error: An unexpected error occurred on the server. Contact support if this persists.
Problem Details (RFC 9457)
When an error occurs (4xx or 5xx status codes), the API returns a standardized error response using the RFC9457 Problem Details format. This provides a consistent, machine-readable way to communicate error information.
Problem Detail Structure
All error responses have Content-Type: application/problem+json and include these standard fields:
type (string): A URI identifying the problem type (e.g., https://contentgrid.cloud/problems/not-found/entity-item)
title (string): A short, human-readable summary of the problem type
detail (string): A human-readable explanation specific to this occurrence of the problem
status (number): The HTTP status code for this problem
Additional properties: Problem-specific fields that provide more context
HTTP/1.1412Precondition FailedContent-Type:application/problem+json{
"type": "https://contentgrid.cloud/problems/unsatisfied-version",
"title": "Object has changed",
"detail": "Requested version constraint 'is any of [exactly 'outdated-version']' can not be satisfied (actual version exactly '1r061qt')",
"status": 412,
"actual_version": "1r061qt"}
Problem Types Reference
For a complete catalog of all problem types returned by the ContentGrid API, see the Problem Types reference.