API Usage

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.

Before using the API, you need to authenticate and obtain an access token.

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.

TOKEN="$(curl -Ss https://auth.$REGION.contentgrid.cloud/realms/$REALM/protocol/openid-connect/token    \
 -u $CLIENT_ID:$CLIENT_SECRET       \
 -d grant_type=client_credentials | jq .access_token -r)"

An access token is valid for 5 minutes; you can obtain a new token by re-running the command above.

Next, set up some entities that will be used by the examples

INVOICE_ID="$(curl -Ss https://$APP_ID.$REGION.contentgrid.cloud/invoices   \
    -F "total_amount=15.95"   \
    -F "received=2024-07-15"   \
    -F "pay_before=2024-08-14"   \
    -F "document=dummy-invoice;filename=invoice.txt;type=text/plain"   \
    -H "Authorization: Bearer $TOKEN"   \
    | jq .id -r
)"
INVOICE2_ID="$(curl -Ss https://$APP_ID.$REGION.contentgrid.cloud/invoices   \
    -F "total_amount=123.4"   \
    -F "received=2020-01-01"   \
    -F "pay_before=2020-02-01"   \
    -H "Authorization: Bearer $TOKEN"   \
    | jq .id -r
)"
SUPPLIER_ID="$(curl -Ss https://$APP_ID.$REGION.contentgrid.cloud/suppliers   \
    -F "name=Test supplier"   \
    -F "telephone=test"   \
    -H "Authorization: Bearer $TOKEN"   \
    | jq .id -r
)"

entity-item operations

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

{
  "id": "3211be1d-1ed1-4850-8ea6-3fa3218031f6",
  "received": "2024-07-15",
  "document": {
     "size": 123456,
     "mimetype": "application/pdf",
     "filename": "example-invoice.pdf"
  },
  "pay_before": "2024-08-14",
  "total_amount": 15.95,
  "_links": {
    [...]
  },
  "_templates": {
    [...]
  }
}
curl -i -X PUT https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID   \
    --json "{\"pay_before\": \"2024-08-31\", \"received\": \"2024-07-15\", \"total_amount\": 15.95}"   \
    -H "Authorization: Bearer $TOKEN"
PUT /invoices/$INVOICE_ID HTTP/1.1
Authorization: Bearer $TOKEN
Content-Type: application/json

{"pay_before": "2024-08-31", "received": "2024-07-15", "total_amount": 15.95}
HTTP/1.1 204 No Content

Note that document was not present in the request body, and has now been set to null, removing the document.

curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices/$INVOICE_ID HTTP/1.1
Authorization: Bearer $TOKEN
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "3211be1d-1ed1-4850-8ea6-3fa3218031f6",
  "received": "2024-07-15",
  "document": null,
  "pay_before": "2024-08-31",
  "total_amount": 15.95,
  "_links": {
    [...]
  },
  "_templates": {
    [...]
  }
}
curl -i -X PATCH https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID   \
    --json "{\"pay_before\": \"2024-08-31\", \"received\": \"2024-07-15\", \"total_amount\": 15.95}"   \
    -H "Authorization: Bearer $TOKEN"
PATCH /invoices/$INVOICE_ID HTTP/1.1
Authorization: Bearer $TOKEN
Content-Type: application/json

{"pay_before": "2024-08-31", "received": "2024-07-15", "total_amount": 15.95}
HTTP/1.1 204 No Content

Fields that are absent in the request body are not modified from their original values.

curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices/$INVOICE_ID HTTP/1.1
Authorization: Bearer $TOKEN
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "3211be1d-1ed1-4850-8ea6-3fa3218031f6",
  "received": "2024-07-15",
  "document": {
     "size": 123456,
     "mimetype": "application/pdf",
     "filename": "example-invoice.pdf"
  },
  "pay_before": "2024-08-31",
  "total_amount": 15.95,
  "_links": {
    [...]
  },
  "_templates": {
    [...]
  }
}

entity-collection operations

The entity-collection resource is exposed on the path /<entity-name-plural> (e.g. /invoices for the invoice entity type).

The following operations are available:

Method Content-Type Description
GET application/hal+json Read the entity-collection, applying the specified filters and pagination
POST application/json Create a new entity-item. The response will be the created entity-item resource
POST application/x-www-form-urlencoded Create a new entity-item with a form submission. The response will be the created entity-item resource
POST multipart/form-data Create a new entity-item, and immediately upload content as well. The response will be the created entity-item resource
Create an entity using JSON
curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices   \
    --json "{\"received\": \"2024-07-15\", \"total_amount\": 15.95, \"pay_before\": \"2024-08-14\"}"   \
    -H "Authorization: Bearer $TOKEN"
POST /invoices HTTP/1.1
Authorization: Bearer $TOKEN
Content-Type: application/json

{"received": "2024-07-15", "total_amount": 15.95, "pay_before": "2024-08-14"}
HTTP/1.1 201 Created
Location: /invoices/3211be1d-1ed1-4850-8ea6-3fa3218031f6
Content-Type: application/prs.hal-forms+json

{
  "id": "3211be1d-1ed1-4850-8ea6-3fa3218031f6",
  "received": "2024-07-15",
  "document": null,
  "pay_before": "2024-08-14",
  "total_amount": 15.95,
  "_links": {
    [...]
  },
  "_templates": {
    [...]
  }
}
Create an entity with content using multipart/form-data
curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices   \
    -F received=2024-07-15   \
    -F total_amount=15.95   \
    -F pay_before=2024-08-14   \
    -F document=@example-invoice.pdf   \
    -H "Authorization: Bearer $TOKEN"
POST /invoices HTTP/1.1
Authorization: Bearer $TOKEN
Content-Type: multipart/form-data;boundary="delimiter123"

--delimiter123
Content-Disposition: form-data; name="received"

2024-07-15
--delimiter123
Content-Disposition: form-data; name="total_amount"

15.95
--delimiter123
Content-Disposition: form-data; name="pay_before"

2024-08-14
--delimiter123
Content-Disposition: form-data; name="document"; filename="example-invoice.pdf"

....Omitted contents of file example-invoice.pdf....
--delimiter123--
HTTP/1.1 201 Created
Location: /invoices/3211be1d-1ed1-4850-8ea6-3fa3218031f6
Content-Type: application/prs.hal-forms+json

{
  "id": "3211be1d-1ed1-4850-8ea6-3fa3218031f6",
  "received": "2024-07-15",
  "document": {
     "size": 123456,
     "mimetype": "application/pdf",
     "filename": "example-invoice.pdf"
  },
  "pay_before": "2024-08-14",
  "total_amount": 15.95,
  "_links": {
    [...]
  },
  "_templates": {
    [...]
  }
}

Collection query parameters

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
Example of navigating through pages with a cursor

curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices HTTP/1.1
Authorization: Bearer $TOKEN
HTTP/1.1 200 OK
Content-Type: application/prs.hal-forms+json

{
    "_embedded": {
        [...]
    },
    "page": {
        "size": 20,
        "next_cursor": "1wskufc1",
        "total_items_estimate": 48,
        "total_items_exact": 48
    }
}
Navigate to the next page. Note that a prev_cursor appears, which can be used to navigate to the previous page
curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices?_cursor=1wskufc1   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices?_cursor=1wskufc1 HTTP/1.1
Authorization: Bearer $TOKEN
HTTP/1.1 200 OK
Content-Type: application/prs.hal-forms+json

{
    "_embedded": {
        [...]
    },
    "page": {
        "size": 20,
        "prev_cursor": "0dtcbvz0",
        "next_cursor": "0qk2h5e2",
        "total_items_estimate": 48,
        "total_items_exact": 48
    }
}

Navigate again to the next page. Note that this is the final page, and next_cursor is absent, indicating that there are no further pages.

curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices?_cursor=0qk2h5e2   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices?_cursor=0qk2h5e2 HTTP/1.1
Authorization: Bearer $TOKEN
HTTP/1.1 200 OK
Content-Type: application/prs.hal-forms+json

{
    "_embedded": {
        [...]
    },
    "page": {
        "size": 20,
        "prev_cursor": "1wskufc1",
        "total_items_estimate": 48,
        "total_items_exact": 48
    }
}

Sorting

By default, the collection is not sorted. The collection can be sorted by using the _sort query parameter.

The _sort query parameter can be repeated multiple times to sort on multiple attributes.

The _sort value is composed of the attribute name, followed by ,asc or ,desc to sort ascending or descending.

Note that not all attributes support sorting. The OpenAPI spec lists the valid values for the _sort query parameter.

Example of collection sorting on multiple attributes

First sort ascending on received, then descending on total_amount

curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices?_sort=received,asc\&_sort=total_amount,desc   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices?_sort=received,asc&_sort=total_amount,desc HTTP/1.1
Authorization: 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

Show invoices with total_amount equal to 15.95

curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices?total_amount=15.95   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices?total_amount=15.95 HTTP/1.1
Authorization: Bearer $TOKEN
Show invoices with total_amount equal to 15.95 OR 123.4
curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices?total_amount=19.95\&total_amount=123.4   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices?total_amount=19.95&total_amount=123.4 HTTP/1.1
Authorization: Bearer $TOKEN
Show invoices with total_amount equal to 15.95 AND pay_before equal to 2024-08-14
curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices?total_amount=15.95\&pay_before=2024-08-14   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices?total_amount=15.95&pay_before=2024-08-14 HTTP/1.1
Authorization: Bearer $TOKEN
Show invoices with (total_amount equal to 15.95 OR 123.4) AND pay_before equal to 2024-08-14
curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices?total_amount=15.95\&total_amount=123.4\&pay_before=2024-08-14   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices?total_amount=15.95&total_amount=123.4&pay_before=2024-08-14 HTTP/1.1
Authorization: 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 text/uri-list Update the relation link. Requires a single URL to the entity-item that the relation should refer to
DELETE N/A Remove the relation link. No entity-items are deleted, only the link between the two is severed
Reading and updating a to-one relation

Initially, no supplier is linked to the invoice, and fetching the relation results in a 404 Not Found error.

curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID/supplier   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices/$INVOICE_ID/supplier HTTP/1.1
Authorization: Bearer $TOKEN
HTTP/1.1 404 Not Found

Setting a relation requires sending the URL of the created entity-item in the body.

curl -i -X PUT https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID/supplier   \
    --data-binary "https://$APP_ID.$REGION.contentgrid.cloud/suppliers/$SUPPLIER_ID"   \
    -H 'Content-Type: text/uri-list'   \
    -H "Authorization: Bearer $TOKEN"
PUT /invoices/$INVOICE_ID/supplier HTTP/1.1
Authorization: Bearer $TOKEN
Content-Type: text/uri-list

https://$APP_ID.$REGION.contentgrid.cloud/suppliers/$SUPPLIER_ID
HTTP/1.1 204 No Content

Reading the relation afterwards will result in a redirect to the specific item that was linked.

curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID/supplier   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices/$INVOICE_ID/supplier HTTP/1.1
Authorization: Bearer $TOKEN
HTTP/1.1 302 Found
Location: /suppliers/$SUPPLIER_ID

to-many relation operations

These operations are applicable to one-to-many and many-to-many relations.

Method Content-Type Description
GET application/json Read the relation collection. Redirects to the entity-collection containing all entity-items that the relation refers to
POST text/uri-list Adds entity-items to the relation. Accepts one or more URLs to the entity-items that should be added to the relation collection
DELETE N/A Clear the relation collection. No entity-items are deleted, only the relation collection is emptied.
Reading and updating a to-many relation

In this example, we will add 2 invoices to a supplier.

curl -i -X POST https://$APP_ID.$REGION.contentgrid.cloud/suppliers/$SUPPLIER_ID/invoices   \
    --data-binary "https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID
https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE2_ID"   \
    -H 'Content-Type: text/uri-list'   \
    -H "Authorization: Bearer $TOKEN"
POST /suppliers/$SUPPLIER_ID/invoices HTTP/1.1
Authorization: Bearer $TOKEN
Content-Type: text/uri-list

https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID
https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE2_ID
HTTP/1.1 204 No 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.

curl -i https://$APP_ID.$REGION.contentgrid.cloud/suppliers/$SUPPLIER_ID/invoices   \
    -H "Authorization: Bearer $TOKEN"
GET /suppliers/$SUPPLIER_ID/invoices HTTP/1.1
Authorization: Bearer $TOKEN
HTTP/1.1 302 Found
Location: /invoices?_internal_supplier=$SUPPLIER_ID

relation-item operations

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

curl -i -X DELETE https://$APP_ID.$REGION.contentgrid.cloud/suppliers/$SUPPLIER_ID/invoices/$INVOICE_ID   \
    -H "Authorization: Bearer $TOKEN"
DELETE /suppliers/$SUPPLIER_ID/invoices/$INVOICE_ID HTTP/1.1
Authorization: Bearer $TOKEN
HTTP/1.1 204 No Content

After the entity-item is removed from the relations, further attempts to remove the same item will result in a 404 Not Found error

curl -i -X DELETE https://$APP_ID.$REGION.contentgrid.cloud/suppliers/$SUPPLIER_ID/invoices/$INVOICE_ID   \
    -H "Authorization: Bearer $TOKEN"
DELETE /suppliers/$SUPPLIER_ID/invoices/$INVOICE_ID HTTP/1.1
Authorization: Bearer $TOKEN
HTTP/1.1 404 Not Found

entity-content operations

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

Read and write content

Read content of an invoice with id $INVOICE_ID

curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID/document   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices/$INVOICE_ID/document HTTP/1.1
Authorization: Bearer $TOKEN
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment;filename="example-invoice.pdf"

....Raw PDF body omitted....
Overwrite content of an invoice using direct upload, setting the filename as well.
curl -i -X PUT https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID/document   \
    --data-binary "@example-invoice.pdf"   \
    -H 'Content-Disposition: attachment;filename="example-invoice.pdf"'   \
    -H 'Content-Type: application/pdf'   \
    -H "Authorization: Bearer $TOKEN"
PUT /invoices/$INVOICE_ID/document HTTP/1.1
Authorization: Bearer $TOKEN
Content-Disposition: attachment;filename="example-invoice.pdf"
Content-Type: application/pdf

....Omitted contents of file example-invoice.pdf....
HTTP/1.1 204 No Content
Overwrite content of an invoice using multipart upload
curl -i -X PUT https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID/document   \
    -F file=@example-invoice.pdf   \
    -H "Authorization: Bearer $TOKEN"
PUT /invoices/$INVOICE_ID/document HTTP/1.1
Authorization: Bearer $TOKEN
Content-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.1 204 No 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.1
Authorization: Bearer $TOKEN
HTTP/1.1 200 OK
ETag: "1e0k4j9"
Content-Type: application/hal+json

{
  "id": "019be613-baa4-7644-9438-e252be3bbe84",
  "received": "2024-07-15",
  "document": {
    "filename": "invoice.txt",
    "length": 13,
    "mimetype": "text/plain"
  },
  "pay_before": "2024-08-14",
  "total_amount": 15.95,
  "_links": {
    [...]
  }
}

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.

curl -i -X PATCH https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID   \
    --json "{\"pay_before\": \"2024-08-13\"}"   \
    -H 'If-Match: "1e0k4j9"'   \
    -H "Authorization: Bearer $TOKEN"
PATCH /invoices/$INVOICE_ID HTTP/1.1
Authorization: Bearer $TOKEN
If-Match: "1e0k4j9"
Content-Type: application/json

{"pay_before": "2024-08-13"}
HTTP/1.1 204 No Content
ETag: "i4xcj6"

The response to the modification also contains the new ETag value for the updated version of the object.

curl -i -X PATCH https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID   \
    --json "{\"pay_before\": \"2024-08-13\"}"   \
    -H 'If-Match: "1e0k4j9"'   \
    -H "Authorization: Bearer $TOKEN"
PATCH /invoices/$INVOICE_ID HTTP/1.1
Authorization: Bearer $TOKEN
If-Match: "1e0k4j9"
Content-Type: application/json

{"pay_before": "2024-08-13"}
HTTP/1.1 412 Precondition Failed
Content-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"
}

More details about the error response format can be found in the error handling section.

Range requests

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.

curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID/document   \
    -H 'Range: bytes=0-3'   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices/$INVOICE_ID/document HTTP/1.1
Authorization: Bearer $TOKEN
Range: bytes=0-3
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 0-3/13
ETag: "8xhtyfikrvnts4izydc95vfcn"
Content-Length: 4
Content-Type: text/plain

dumm

After the initial fetch, If-Match is used to ensure that the rest of the same file is fetched.

curl -i https://$APP_ID.$REGION.contentgrid.cloud/invoices/$INVOICE_ID/document   \
    -H 'If-Match: "8xhtyfikrvnts4izydc95vfcn"'   \
    -H 'Range: bytes=4-'   \
    -H "Authorization: Bearer $TOKEN"
GET /invoices/$INVOICE_ID/document HTTP/1.1
Authorization: Bearer $TOKEN
If-Match: "8xhtyfikrvnts4izydc95vfcn"
Range: bytes=4-
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 4-12/13
ETag: "8xhtyfikrvnts4izydc95vfcn"
Content-Length: 9
Content-Type: text/plain

y-invoice