HCMS REST Endpoints
Description of the REST endpoints of the Headless CMS including the query language.
The root of the Headless CMS REST endpoints is available as /hcms/v4.2/.
For backward compatibility, it is also available with previous versions: /hcms/v1.0/ to /hcms/v1.12/, /hcms/v2.0 to /hcms/v2.4, /hcms/v3.0 /hcms/v3.3, /hcms/v4.0, /hcms/v4.1 etc. Note that patch version is never part of url, so /hcms/v2.4.1 is not valid.
There were no major incompatible changes yet, so all these endpoints behave the same.
Note that schema name (which also serves as entity type) is restricted to conform to regular expression [a-zA-Z_0-9~.:+*^$!]+ (ie letters, digits and selected few other characters). These values should be safe to include in url path, but some HTTP clients still url-encode them. Before HCMS version 4.0, the only non-alphanumeric character allowed was underscore (corresponding regular expression: [a-zA-Z_0-9]+)
Schema manipulation endpoint
Using the schema endpoint, you can register, update and unregister Headless CMS schemas and also obtain information of possible values for schema types mapped to enumerations on the censhare side.
- /schema/ (GET)
- List all schemas (entity types)
- Required role: schema-ro
- An OPTIONS request may be used to check if the currently authenticated user is actually authorized to execute a method by checking its presence in the Allow header.
- Response is JSON object, with schema names as properties and values containing name and link. The property effective-link points to the effectively used schema with included mixin properties.
- Property "source-type" describes schema source: "repository" for schema was created via REST interface, "internal" for schema loaded from local directory and "override" for schema which exists in local directory but was overwritten by REST interface. Note that the two latter values are officially supported only for server module.
Example:
JSON{ "image": { "name": "image", "source-type": "repository", "link": "http://localhost:8080/schema/image", "effective-link": "http://localhost:8080/schema/image/effective" }, "user": { "name": "user", "source-type": "repository", "link": "http://localhost:8080/schema/user", "effective-link": "http://localhost:8080/schema/user/effective" }, "group": { "name": "group", "source-type": "repository", "link": "http://localhost:8080/schema/group", "effective-link": "http://localhost:8080/schema/group/effective" } }
- /schema/ (POST)
- This is a convenience function for deploying large quantities of schemas quickly and easily. Multiple schemas can be created, updated or deleted in a multipart request (multipart/form-data) without explicitly executing them in the order of their possible mutual references. However, circular dependencies are not possible.
- For this, the respective parts contain the individual schemas, the names of the parts specify the schema names (not the file names). Deletions are expressed as parts with empty content. The individual Content-Type headers of the parts are ignored and application/json is assumed. For example curl -vs http://localhost:8080/hcms/v2.1/schema -F media=@media-schema.json -F image=@image-schema.json
- The return value consists of a JSON object that reports on the actions performed. The properties of the object are the relevant schema names and the values are either "created", "updated", "deleted" or "unchanged", depending on which operation was performed.
- Required role: schema-rw
- Optional query parameter: dry=true or dryRun=true makes the request to process all schemas, but without actually doing any changes. This is useful for validation without doing any actual changes.
- /schema/ (RELOAD)
- Using this special non-standard method forces reload and recompilation of all schemas. This is the only way to reload and recompile schema files from local disk ("internal").
- Required role: schema-rw or schema-reload
- /schema/{name} (GET, PUT, DELETE, OPTIONS)
- GET retrieves the schema or mixin
- PUT updates the schema or mixin, or creates it if does not yet exist
- DELETE removes the schema or mixin.
- The schema definition is described in Schema - Core
- Required roles:
- schema-ro for GET
- schema-rw for PUT and DELETE
- An OPTIONS request may be used to check if the currently authenticated user is actually authorized to execute a method by checking its presence in the Allow header.
- If the schema references another one, it must be registered first. Otherwise, an error will occur.
- Optional query parameter: dry=true or dryRun=true with PUT method disables the actual change. Submitted schema is still validated and compiles, so this can be used for validation.
- /schema/{name}/effective (GET, OPTIONS)
- GET retrieves the effective schema or mixin.
- Effective schema is the schema with all mixins included; see Schema - Core for details.
- Required role: schema-ro
- An OPTIONS request may be used to check if the currently authenticated user is actually authorized to execute a method by checking its presence in the Allow header.
- /schema/{name}/descriptive (GET, OPTIONS)
- GET retrieves the effective schema with metadata descriptions filled in.
- Effective schema is the schema with all mixins included; see Schema - Core for details.
- Metadata descriptions are always the same as returned by /schema/{name}/cs-description/{path} endpoint.
- Required role: schema-ro
- An OPTIONS request may be used to check if the currently authenticated user is actually authorized to execute a method by checking its presence in the Allow header.
- /schema/{name}/cs-values/{property} (GET)
- Provide all possible values for enum properties
- property is the hierarchical name of the property, same as in queries
- property must properly identify simple value in json, otherwise error 400 is returned
- If the property exists, but its server type is not enum or hierarchical, the returned value is an empty JSON object (but still http 200 OK)
- If the property is enum or hierarchical, the response object contains two properties:
- values is a simple array of strings (all possible values)
- full is an array of JSON object with full description of the value:
- value the value in JSON (always present)
- name simple human-friendly name of this value (always present)
- localized_name JSON object with overloaded name in selected languages (content is optional; usually de is present)
- description detailed description of this value (optional)
- sorting number used to order values in list (always present)
- parent parent value (optional, only for hierarchical values)
- Both arrays are sorted by the sorted value
- Requires either schema-ro role or read access to schema entities.
- Note that if the entities are publicly accessible, this endpoint is accessible too!
- /schema/{name}/cs-description/{path}
- GET
- path is dot-separated list of property names in the JSON, similar to path in queries and cs-values endpoint
- Both arrays and localized mappings are skipped.
- Any property can be selected.
- Empty path elements are ignored and automatically discarded; /cs-description/... is thus equal to /cs-description/.
- Root of the schema (the entity itself, mapped to asset) is specified by empty string (or path that consists solely of dots).
- Response is 204 No content if there is no metadata for specified property - usually data-less object or constant.
- Otherwise the response is 200 OK with application/json content.
- Content of this JSON depends on the mapping. It is the same JSON object as cs:$desription in /schema/{name}/descriptive endpoint.
- See Metadata documentation for details and examples.
- All available descriptions can be obtained at /schema/{name}/descriptive endpoint.
- Requires either schema-ro role or read access to schema entities.
- Note that if the entities are publicly accessible, this endpoint is accessible too!
- /schema/{name}/cs-description/{path}/{language}
- This is basically the same as /schema/{name}/cs-description/{path}, but text are in specified languages whenever it is available and there are no other localized texts available.
- Note that description of root (asset types) can be accessed as /schema/{name}/cs-description//{language} or /schema/{name}/cs-description/.../{language}.
Entity CRUD endpoint
Using the entity endpoint, you can create, read, update and delete Headless CMS mapped entities using the schemas and queries.
Each entity endpoint starts with entity and entity name. Note that the sub-endpoints are extensible by predefined queries and views. All these endpoints also honor '→Locale selection', if the corresponding schema is configured to use that information.
- /entity/{name} (GET)
- List all entities
- The result can be filtered using the query parameter query (please remember that it must be always properly url-encoded)
- A required role is defined by the READ operation
- Default is none (i.e. everyone can list this)
- Permission groups are handled by filtering; see Security and Permissions
- An OPTIONS request may be used to check if the currently authenticated user is actually authorized to execute a method by checking its presence in the Allow header.
- The results are paged
- See the '→Paging and Ordering' section for more details about the parameters and the resulted JSON object
- The default page size is 100 (this can be changed in configuration) to prevent unlimited output
- Items in the result list ("result" property) can have three forms, specified by query parameter values
- Default: each item is a complete entity as returned by the appropriate GET on its endpoint
- ?values=id or ?values=ids: each item is just the entity id
- ?values=link or ?values=links: each item is the full link to the entity endpoint
- ?values=all: each item is a JSON object with the entity itself (entity), schema name (schema), id (id), link (link), entity's eTag (etag) and list of all asset ids (assets)
- In other words, it is equivalent to getting links and then one-by-one invoking all those endpoints plus the entity/<id>/dependencies one - except it's all done in one single request.
- Note that the etag element is present even if the header is disabled in the configuration xml (<api etag="none"/>).
- /entity/{name} (POST)
- Create a new entity, with content send as the request body
- The required role is defined by the READ operation, the default is rw
- A new entity is always created and a new id assigned; if the request body contains a property with id, it is ignored
- The response body is the full entity content (just like GET on appropriate endpoint), including the new id
- The response has the Location header set to the full link to the entity endpoint
- This allows the caller to reference the entity even if the entity schema does not contain any id ("censhare:asset.id") property
- The response also has the ETag header set to value that represents state of the newly created entity. This value can be used in If-Match and If-None-Match headers of the following endpoint.
- This header can be configured by setting attribute etag on api element:
- <api etag="none"/> completely disables this header, so it is never sent. This is useful if the value is too long.
- <api etag="long"/> enables legacy values.
- <api etag="auto"/> is the default.
- This header can be configured by setting attribute etag on api element:
- /entity/{name}/{id} (GET, PUT, DELETE)
- This endpoint represents the entity
- The required role is defined by READ, UPDATE and DELETE operations in schema
- GET and PUT are symmetric (the response of GET is usable as the request body of PUT)
- PUT the response is the full content of the updated entity, i.e. the same as the following GET returns
- DELETE the response is 204 No Content on success
- An OPTIONS request may be used to check if the currently authenticated user is actually authorized to execute a method by checking its presence in the Allow header.
- It is not possible to change the id of an entity; the corresponding properties in PUT requests are always ignored
- This endpoint supports optimistic locking as defined in HTTP standard.
- Both GET and PUT operations set ETag header to a value that represents current state of the entity (see above for details). This token can then be passed as a value of a If-Match or If-None-Match header in next request issued to this endpoint.
- If the request has a If-Match header set and its value does not equal the current one (i.e. the entity has changed since the previous request), the request is aborted with 412 Precondition Failed. This can be used to ensure that no intermediate updates occurred between the last read and the current write of the entity and therefor solves the lost update problem.
- If the request has If-None-Match header set and its value does equal current one (i.e. the entity has not changed since the previous request), request is aborted with 412 Precondition Failed (PUT) or 304 Not Modified (for GET).
- Using an invalid token (one that is not obtained from some past invocation of this endpoint) cause 400 error.
- /entity/ (POST)
- Batch operation, containing up to 100 entities to be created, updated or deleted.
- Request body is JSON object that contains array of operations to execute; each operation is equal to one http invocation of /entity/{name} (POST) or /entity/{name}/{id} (PUT, DELETE). This object can be passed either directly as application/json body of the request, or as operations field of the multipart request (see '→BLOB handling' section).
- Before executing any operation, request is validated by batch-schema.json and each operation is also checked for missing properties (id for UPDATE and DELETE operations, entity for UPDATE and CREATE operations). If any of these validations fail, 400 error is directly returned.
- Maximum number of operations is limited to 100; this limit is part of the request validation.
- As long as the request is valid, all operations are executed in given order inside single transaction. If any operation fails, the whole method immediately stops, rolls back the transaction and return response with HTTP code equal to the error code from the failed operation.
- For the purpose of batch operation, HTTP code 412 is not considered a failure: it just means that the operation has been skipped.
- Response (on both success and failure) is a JSON object with single property results, an array of operation results. Results are always provided in the same order as operations in request and size of both arrays is also the same. Each array item is a JSON containing information that would normally be encoded by HTTP protocol:
- "status": HTTP status code
- 200 or 204 on success
- 304 if the operation succeeded, but some subsequent failure caused rollback (no result data are available in this case)
- 412 if the operation has been skipped because of failed If-Match or If-None-Match condition
- 400-411, 413-499, 500+ on error (this is always the last item in array)
- "entity": New or updated entity of successful CREATE and UPDATE, detailed error description in case of error status code, missing if the status code is 204 (DELETE operation).
- "headers": JSON object containing all explicitly set HTTP headers (usually Content-Type, ETag and Location). Values are arrays of strings, because HTTP protocol allows repeated headers.
This value is useful mainly for diagnosis and debugging. Note that some selected headers are also available directly in the result item. - "ETag": Value of the ETag header, from CREATE and UPDATE requests.
- "status": HTTP status code
- /entity/{name}/key/{key} (GET)
- Get all defined keys in given schema.
- Response is JSON object. Each key is represented by one property; property name is the key name and property value is JSON object with properties "name" (key name), "size" (key size = number of key parts) and optional "global" (name of the corresponding global view).
- /entity/{name}/key/{key} (GET, PUT, DELETE)
- This endpoint represents the entity with custom key.
- Composite keys consist by several parts, separated (by default) by slashes.
- These slashes must not be encoded; in reality, the url is actually /entity/{name}/key/{keyPart1}/{keyPart2}/{keyPart3}
- Endpoints with incomplete composite key are actually treated as /entity/{name} endpoint, that is they return list of entities. This list, however, is filtered by the specified key part (ie prefix search).
- If the key is not unique (that is, several entities have the same key), any operation fails with 519 error.
- As long as the key is unique, all operations are exactly the same as for canonical endpoint /entity/{name}/{id}; see above.
- /entity/{name}/{id}/dependencies or /entity/{name}/key/{key}/dependencies (GET)
- Special endpoint that provides list of all assets contained (directly or indirectly) in this entity. See '→Entity-asset mapping' section
- /entity/{name}/{id}/related (GET)
- Get all configured queries for the specified entity, as a JSON object.
- If the schema does not contain any query in cs:related.queries, the response is an empty JSON object.
- /entity/{name}/{id}/related/{queryName} (GET)
- List related entities by using one of preconfigured queries, queryName is the key in cs:related.queries object.
- This endpoint accepts the same query parameter as the standard /query endpoint (even the query one, which is used in conjunction with the predefined one) and it uses exactly the same response.
- If the query name (last path segment) is not present in cs:related.queries of the schema, HTTP code 404 is returned.
- This endpoint is available for mixins, too (as long as the mixing actually has query of this name defined). If the real schema also contains a query of the same name, the
- /entity/{name}/list (GET)
- Get all configured queries in the entity schema, as a JSON object.
- If the schema does not contain any query in cs:queries, the response is an empty JSON object.
- /entity/{name}/list/{queryName} (GET)
- List entities by using one of preconfigured queries, queryName is the key in cs:queries object.
- This endpoint accepts the same query parameter as the standard entity listing endpoint (even the query one, which is used in conjunction with the predefined one) and it uses exactly the same response.
- If the query name (last path segment) is not present in cs:queries of the schema, HTTP code 404 is returned.
- This endpoint is available for mixins, too.
Entities with global custom keys are also available via global views:
- /view/ (GET)
- Get all defined global views.
- Response is JSON object, each view is represented by single property. Property name is the view name, property value is JSON object with sub-properties:
- "name" is the name of the view
- "link" is full link to the view (ie full link to /view/{viewName})
- "entity" is full link to the declared entity (ie full link to /entity/{schemaName})
- /view/{viewName} (GET)
- List all entities in the declaring view that have the key (that is, no key part is missing or null) and conform to additional query filter (if defined).
- Other that this additional filtering and different links, it's the same as listing entities in schema. See /entity/{name} documentation above for detailed info about response, accepted query parameters and required permissions.
- /view/{viewName} (POST)
- Create a new entity, with content send as the request body.
- Exactly the same as /view/{schemaName} above; it is not even required that the new entity is actually part of this view! Entity without valid key is accepted, as long as such entity is accepted by the schema itself.
- /entity/{viewName}/{key} (GET, PUT, DELETE)
- This endpoint represents the entity with custom key.
- Composite keys consist by several parts, separated (by default) by slashes.
- These slashes must not be encoded; in reality, the url is actually /entity/{viewName}/key/{keyPart1}/{keyPart2} or /entity/{viewName}/key/{keyPart1}/{keyPart2}/{keyPart3}, etc.
- Endpoints with incomplete composite key are actually treated as /view/{viewName} endpoint, that is they return list of entities. This list, however, is filtered by the specified key part (ie prefix search).
- If the key is not unique (that is, several entities have the same key), any operation directly fail with 519 error.
- As long as the key is unique, all operations are exactly the same as for canonical endpoint /entity/{name}/{id}; see above.
BLOB/CLOB handling
Download
Large data objects that are mapped (see Schema - Storage Items) to censhare storage items and represented as links in the entity JSON, they can be downloaded using the link provided by the end point. It must considered opaque and never manually created by the end point user. The HTTP content disposition for the download may be set to attachment by appending /download to the provided link (e.g. <provided link>/download). In addition, a download file name for browsers may be set by appending it separated by a slash after the /download path segment (e.g. <provided link>/download/preview.jpg). In a similar way, /direct/<filename> can be appended to make a link that contains custom filename, but without any Content-Disposition header.
Note that download links do check proper authorization. Any GET request that is not properly authorized to read the entity ends with 401 or 403 error (just like the attempt to read the entity itself).
Customised Direct Download Link
Introduced with HCMS 4.2 the schema can specify a cs:storage.$direct-file-name (only word characters, e.g. "my-thumbnail-file") which is accessible under the same rules as every download but doesn't require the first step to get the downloadLink from another request. So a URL path could loke like /entity/image/54301/storage/my-thumbnail
There is a notable disadvantage though - since the link is permanently defined with the schema clients will recognize only changes when strictly following the caching policy. Web browsers are notoriously bad at this and might miss a required refresh.
Upload
The replacement and initial creation of large objects is possible using one of the following procedures:
- Sending the data inside the entity JSON using a data URI during POST/PUT. This is only recommended for a small amount of data, because it is held completely in the memory during processing. e.g.:
{
"content": "data:text/plain,My plain text"
}
- Providing the data on an HTTP server and specifying the URL for the end point to retrieve it. Possible access restrictions on the external HTTP server need to be kept in mind.
- Sending the data along the entity JSON during POST/PUT as multipart/form-data according to RFC 2388. The entity JSON itself must be in a field named entity and the other fields can be referenced from the JSON using a URI with the scheme formdata followed by the name of the corresponding field. e.g.:
{
"content": "formdata:mycontent"
}
Pre-authorized download
It is possible to provide signed, pre-authorized download links:
- This feature must be explicitly configured in the configuration asset XML, element signed-link in the element api.
- Commandline administration tool does not support this feature yet.
- At least one <hmac secret="..."/> element must be provided, even if most storage items are stored in S3. The reason is to provide fallback for non-S3 files.
- Download link must be invoked with special query parameter
signedLinkDays
orsignedLinkDays
added. Value of this parameter is a duration specifying how long will this link will be valid (in days or seconds respectively).
- Example: http://localhost:8080/hcms/v2.1/entity/image/114910/storage/MTE0OTEwLzAvbWFzdGVy/download/xxx.jpg?signedLinkDays=5
- The duration must be positive.
- Maximum duration is limited by either 7 days (as defined by AWS S3 API) or a value specified in the HCMS configuration xml, whichever is lower. Passing longer value as a query parameter does not result in an error; the maximum duration is just used instead. - With this special query parameter, the request will not return file content; instead, it generates signed link and returns 307 Temporary Redirect with empty content and Location: header set.
- Client should read the Location header and store it.
- This request is authorized, as usual.
- If the feature is not configured, the request returns 400 Bad Request error.
- The generated link can be used to download storage file content without any special authorization headers, but it is valid only for limited time.
- If the file is actually stored in AWS S3 bucket, this link is actually a direct link to S3.
- Since version v4.2, this S3 link is correctly constructed to force download only if the original request contains /download/filename part (ie using the same logic as normal link).
- Requests with v4.1 and lower version in path always create forced download link (ie Content-Disposition: download) to ensure compatibility.
- If the file is actually stored in AWS S3 bucket, this link is actually a direct link to S3.
Note that these links cannot be used for upload.
Queries
The query language allows specifying conditions using a dot separated path of JSON property names. This specifies the schema type to a filter optionally followed by of one of the comparison operators =, !=, <, <=, >=, <, =^ and a JSON encoded value. =^ represents the prefix operator which can only be applied on string values.
address.city="New York"
If the comparison operator and the value are missing the condition tests for the existence of a value (not null).
adress.city
The conditions can be combined using boolean logic with the operators in order of precedence !, &, and |. Please remember that the & has a special meaning in URL syntax and must be always properly url-encoded.
address.city="New York" & address.street="Broadway"
The precedence can also be modified by grouping the expressions using parenthesis (, ).
address.city="New York" & (address.street="Broadway" | address.street="Park Avenue")
Conditions sharing the same path prefix can be abbreviated by writing them inside square brackets [, ] after the common prefix.
address[city="New York" & (street="Broadway" | street="Park Avenue")]
In case of arrays/maps of objects, this form must be used in order to evaluate the specified conditions in the context of a single object. For example a query on a company entity with an array of employees:
employee.firstName="John" & employee.lastName="Doe"
The query returns all companies having at least one employee with the first name John and also having at least the same or another employee with the last name Doe:
employee[firstName="John" & lastName="Doe"]
The query only returns companies having at least one employee with the first name John and the last name Doe.
In case of a reference to another entity, conditions can also be defined on these entities by specifying the entity type in the path using the prefix @. This syntax must also be used to specify the entity types in a mixed query.
related.@person.firstName="John"
It is also possible to search by references in reverse direction, by special syntax using curly braces. Inside these braces, colon separates entity name, field name in that entity that is mapped to reference and condition use to filter that entity. Third part is actually optional can be omitted, together with the last colon.
{employee:employed_at:lastName="Doe"} | name="censhare"
{employee:employed_at}
Querying using a censhare full text index can be done using the special path function $text(<index>, <term>, <language>). It can be used at the top level of an entity, but also at the level of related/inlined entities.
$text("censhare:text.content", "john doe", "en")
Query values can be also provided by variable by using special syntax ${variableName} or ${variableName:default}. In the first case (no default is specified), query request fails of the value is not available. All values from request logging are potentially available (values derived from response are not yet available at the time of query evaluation). In addition, few special case-insensitive variable are available in JWT-authenticated request: ${user}, ${userdomain} and ${userdomain2} contain id, domain and domain2 of the user's asset.
More formal description of query language is in separate document.
Locale selection
Some schemas might be configured to sort array by locale preference, or to serve only the single best match (according to this preference). In current version, this feature is limited to relations only (marked by "cs:relation.$filter.locale"); support for localized asset features might be added in the future.
Locale preference can be specified by two ways (both can be used at once):
- Query parameter locale
- Can be present multiple times with different values; order defines the preference (first one is the most preferred locale).
- Values can use full locale (including the country and variant).
- Accept-Language request header
- Values can be only languages (no country, no variant).
- When combined with the query parameters, languages from this header have lower priority.
Paging and Ordering
The paging of listing results is controlled by two integer query parameters (both of them optional):
- limit defines a maximum number of the returned items
- Also called: the page size
- When missing, the default value from the configuration is used; if there is no such value configured, the default is 100
- A negative or a zero value means unlimited; this is not recommended because this can cause a potentially huge result size
- offset defines how many items since the beginning of the list is be skipped
- Also called: page start or page offset
- When missing, 0 is used (i.e. no offset, listing starts at the very beginning)
- Negative values are silently ignored (and treated as 0)
- It is allowed for the offset to be higher than the total number of items (the result list is empty in this case)
- It is possible to have a positive offset with a negative (disabled) limit
The result is JSON with the following properties:
- result: the result list subset (array)
- limit: maximum number of items in the result
- offset: offset used (0 when the offset parameter is missing or negative)
- count: the number of items in the result
- total-count: the total number of results (un-paged)
- page: (Optional) JSON object with meta data describing this page and helping client to navigate pages
- Present only when the result is actually paged, i.e. limit is positive
- Links to various pages; these save client the hassle with constructing them
- next: next page, if available (missing if this result is the last page)
- prev: previous page, if available (missing if this result is the first page)
- first: first page (offset 0)
- last: last page
- current: this page (probably the same link as the request)
Ordering can be specified by the optional query parameter order. The value of this parameter is a comma-separated list of sorting specifiers. A sorting specifier consist of the optional minus sign - (for reverse = descending ordering, by default is ascending) and path, dot- or slash-separated list of property names. This path is somewhat similar to the query language, but very limited: only features are allowed and nothing else. Note that it is not possible to sort by storage items, relations or even related entities.
Examples of the ordering specifiers:
assetId
name
-name
name,-assetId
-social.likes,name
Mixed queries
Many applications need to query several types of entities at once and provide a sorted (and paged) list of mixed records. This is possible in HWCMS, by a special endpoint that allows the caller to specify a query over several entities at once.
- /query (GET)
- The result must be filtered using the query parameter query or no entity is to be returned
- Results are paged and ordered just like the single-entity lists, see previous chapters
- A sorting expression can contain paths in any schema used in the query (as long as it is valid in at least one); it is possible (and useful) to have paths in several schemas at once
- Note that there is no obvious way on how to detect which record is which entity; this must be done by application-specific way, or by using &values=all query parameter
- /query (POST)
- The same as GET, but query is passed in the request body.
- Request body must be JSON object (mime type application/json) with string property named query (this property contains the query expression).
- All query options supported by the GET variant can be also set as properties in request body.
- Query options passed as part of URL are still supported, to provide compatibility with links in paged reply.
- When some parameter is present in both request URL and request body, the value from request body takes precedence.
Example:
/query?query=@miniuser[birthday>"1900-01-01" %26 birthday<"1940-01-01"] | @city.key&order=name
gives result:
{
"result": [
{
"birthday": "1912-06-23",
"name": "Alan Turing",
"id": 14820
},
{
"name": "Beijing",
"id": 10005,
"key": "censhare:city.beijing"
},
{
"name": "London",
"id": 10007,
"key": "censhare:city.london"
},
{
"birthday": "1901-12-05",
"name": "Werner Heisenberg",
"id": 14910
}
],
"offset": 0,
"count": 4,
"total-count": 4,
"page": {
"current": "http://localhost:8080/query?offset=0&order=name",
"last": "http://localhost:8080/query?offset=0&order=name",
"first": "http://localhost:8080/query?offset=0&order=name"
}
}
Faceted search
With the facet search, the search results contain suggestions on how the search can be narrowed down further. If suitable properties and functions are marked as facets, the search result also contains possible values for these and the resulting number of hits. Please note that faceted queries require more resources than standard queries and have longer response times.
Faceted queries
To use faceted queries on a entity, they need to be enabled in the corresponding schema on the root level and on the specific properties (search for "facet"). To obtain facet results, the desired properties and functions in the query must be followed by the suffix # followed by a unique identifier, which used to identify the possible values in the result.
Each facet name can be used only once per query!
address.city#city="New York"
If the comparison operator is omitted for a facet property, this is not interpreted as a not-null condition on the result set as usual, but ignored. The $text() function also supports facets:
$text("censhare:text.content", "doe", "en")#ftxterm
The following query parameters can be set to change the default behaviour:
- facets-limit sets the maximum amount of "values" returned
- Minimum is 1, maximum is 1000.
- Default is 100
- This limit applied to all facets.
- facets-order defines order of facet values
- Comma-delimited list of four possible ordering criteria, similar to the query parameter order:
- value: sort by values ascending
- -value: sort by values descending
- count: sort by frequency ascending
- -count: sort by frequency descending
- Default is -count,value
- The same order is used for all facets.
- Comma-delimited list of four possible ordering criteria, similar to the query parameter order:
Faceted search result
If the query expression contains facet markers the result JSON contains additional the property facets. This property is a JSON object in which keys are facet unique identifier, as declared in the query, and values are JSON objects with properties "count" (total number of facet values available) and "value" (the value).
The value property content can have two different forms:
- Correctly converted value as defined in the mapping (always a scalar value, though). This form is available only in REST API version 3.0 and higher and for feature-mapped properties.
- Raw value from the database (string or number). This is the default form, used
- In REST API before 3.0 (ie the path contains v1.x or v2.x).
- In GraphQL version of the API.
- When the facet is not based on scalar feature (for example, fulltext facets).
{
"facets": {
"ftx": {
"values": [
{ "count": 29, "value": "pdf" },
{ "count": 2, "value": "pdf.pdf" },
{ "count": 1, "value": "pdf-example-bookmarks.original" }
],
"count": 3
}
}
}
Note that all declared facets are always present in response, even when they are empty.
Error handling for read operations
Due to the nature of storage (censhare Server), it is possible that the data stored in an asset does not properly convert to a JSON entity. For this reason, each output is also validated against the JSON schema and this validation can fail due to wrong data type, missing property, etc. It is also possible that the internal conversion process fails.
In these cases, the GET operation fails with 500 Internal Server Error and the JSON body contains a detailed description of the problem. The caller has error handling in place to handle these errors in a graceful manner.
Listing endpoints (full list, query) are more complex: the response contains many entities and a complete failure just because of single bad entity, which is very impractical. Below we describe the several possibilities on how to handle bad entities. A desired error type can be set by adding the query parameter errors:
- skip: a bad entity is simply skipped
- Advantage: very easy to handle, no need to check for errors
- Disadvantage: paging can be effectively broken (page being smaller than requested)
- This is the default behavior
- null: a bad entity is replaced by a null value
- Advantage: paging works as intended
- Disadvantage: the null value must be properly handled
- replace: a bad entity is replaced by the error description
- The same error description is still also present in the errors array (see later)
- Advantage: paging works as intended, called wont get null values
- Disadvantage: it can be quite hard to distinguish valid and invalid items, as both are JSON objects
- fail: fail with 500 error
- It is still possible to extract all successful parts of the listing:
- The response body is a JSON object, the property details contains a full response that is returned by the null error handling (including results array, errors array, and all paging data)
- It is still possible to extract all successful parts of the listing:
When an error occurs, the response contains a new array with the property errors (besides the usual result). Length of this array is always the number of entities found (good and bad ones). Each item of this array is either null (if the entity is good and its JSON representation is part of result) or JSON object with the error description. The error description always contains the index (zero-based index in the array), id (entity ID) and message (error message) properties as well as some additional optional properties depending on the nature of the error.
Example of list that have found three entities, including a bad entry, and an error handling of type skip:
{
"result": [
{
"name": "Thing 1",
"id": 1,
"uuid": 0
},
{
"name": "Thing 3",
"id": 3,
"uuid": 0
}
],
"offset": 0,
"count": 2,
"total-count": 3,
"page": {
"current": "http://localhost:9998/entity/thing2?offset=0&errors=skip",
"last": "http://localhost:9998/entity/thing2?offset=0&errors=skip",
"first": "http://localhost:9998/entity/thing2?offset=0&errors=skip"
},
"errors": [
null,
{
"schemaLocation": "#/properties/uuid",
"pointerToViolation": "#/uuid",
"index": 1,
"causingExceptions": [],
"id": 2,
"keyword": "type",
"message": "expected type: Number, found: String"
},
null
]
}
Media API
The generated media variants can be accessed via URL from the JSON with a key to specify the desired variant (e.g. <provided media URL>/preview or <provided media URL>/m). Just like in the case of raw BLOB links, /download/<filename> or /direct/<filename> can be appended to provide browsers with filename (and, usually, extension). The /download/ version adds Content-Disposition header to the response, /direct/ does not.
Generated media links (the base media URL and all derived variants) change every time the content is updated. Application should always get the correct link from entity JSON, if possible. Old links, however, are guaranteed to work even after change (as long as the entity contains valid image data). This complicated behavior is needed to handle two levels of caching:
- Headless CMS stores generated image variants in its internal cache ("Image Cache"). This cache must be configured - both expiration time and maximum size are required parameters in HeadlessCMSConfiguration.xsd.
- As long as this cache contains non-expired variant, it is always served - even if the source image changed. In that case, Cache-Control header is set from "cs:media.changed.max_age"
- If the cached image is still correct, header Cache-Control is set from "cs:media.max_age" (365 days by default).
- Browser has its own internal cache, based on the Cache-Control header. Images are cached by the full URL.
- If the image is in cache and not expired (note that the default expiration time is one year), most browsers won't even send a request and therefore have no chance to detect change!
- For this reason, media link changes each time the image content changes. Frontend application should always get the correct link from entity JSON and use it; this ensures that the browser treats it as a completely new image.
- If the media link cannot be dynamically obtained (for example when sent as part of an email message) and image changes are allowed, shorter expiration time in Cache-Control header must be configured via "cs:media.max_age" directive in schema (default is 31536000 = 365 days).
Cookies API
This API is used to set and remove cookies in the browser without disabling the standard security measures.
- /auth/cookie/set (GET, POST)
- Always returns an empty response (204 No Content)
- The response sets the appropriate authorization cookies for the configured auth providers
- Cookie value is passed as query parameters (GET) or via request body (POST). In the case of POST, two formats are supported:
- application/json: JSON object, where key corresponds to parameter name and value is string. Non-string values are silently ignored.
- application/x-www-form-urlencoded: standard HTML form format, with fields used as parameters. Multiple occurrences of the same field/parameter name are ignored (only the first value is used).
- Behavior for the JWT auth provider:
- If there is no configured cookie (<cookie> element in the configuration), this endpoint does nothing at all.
- Token is obtained from query parameter (for GET) or parsed body form (POST). In both cases, the name of the parameter is the configured cookie name. Other parameters are ignored.
- If there is no parameter, the token is taken from the old cookie or from Authorization header.
- If there is no token pass by any way, nothing is done. Otherwise, the cookie is set with this value and configured flags.
- The default cookie and query parameter name is access_token which can be changed in the JWT auth provider configuration
- /auth/cookie/remove (GET)
- Always returns an empty response (204 No Content)
- The response removes the appropriate authorization cookies for the configured auth providers.
- If there is no cookie configured, this endpoint does nothing.
User information
- /auth/whoami (GET)
- This endpoint requires one special, hardcoded role: whoami
- Note that this role can be automatically granted by the configuration for all validated JWT requests (<role>whoami</role> inside <jwt> element). Please consider security requirements for your application.
- Returns application/json JSON object, with information about authenticated user.
- If there is no authenticated user (anonymous request), the JSON object is empty. Please note that in most such cases, the whoami role is actually missing so this won't happen.
- "userId": ID of the person asset, if any. Taken
- "userName": Name of the authenticated user, if any. Depending on the authentication provider, this is one of:
- Username used for HTTP Basic authentication.
- Asset name of the person asset (see "userId" above).
- This endpoint requires one special, hardcoded role: whoami
GraphQL
GraphQL is alternative to REST API, allowing to query for limited parts of entities. For details, see documentation.
GraphQL uses its own schema to define data structures and available queries. Headless CMS does not use this schema directly; instead, it is automatically converted from JSON schemas of all entities. The resulting schema is not exactly the same, because GraphQL schema is much stricter about allowed names (only ASCII letters and digits are allowed, identifier must start with a letter).
Note that GraphQL schema specification does not allow empty types, so there is no way to generate a valid one when no schema is available at all. In that case, all following endpoints just fail with special error code 422 No schemas.
- /graphql/schema.graphql (GET)
- Returns the current GraphQL schema.
- Always returns 200 OK with body of type application/graphql.
- /graphql/schema.json (GET)
- Returns full introspection of current GraphQL schema. It is equivalent to query for __schema and its content.
- Always returns 200 OK with body of type application/graphql.
- /graphql (POST, GET)
- Graphql request endpoint conforming to official recommendation), with all three variants supported:
- GET with query parameters query (required), variables (optional) and operationName (optional)
- POST with application/json content type; request body must be JSON document with properties (note that only query is required):
- query is the GraphQL query itself (required)
- operationName is the operation name (optional)
- variables is JSON object with variables and their values (optional)
- POST with application/graphql content type; request body must be correct graphql query
- Query parameters are actually parsed and used by POST method too, but body content always has precedence.
- Response conforms to graphql specification
- Errors are not handled by direct http response, but rather by the errors field in response.
- The only exception is completely refused access, which cause 401 or 403 error.
- Partially refused access (i.e. query for single entity that is not accessible) is also reported as one item in errors array.
- Graphql request endpoint conforming to official recommendation), with all three variants supported:
Entity-asset mapping
One of the main purposes of entities (and Headless CMS in general) is to hide all the details about complex asset structures; any number of assets can be provided as relatively simple JSON document. Sometimes, however, these details are required; the most common case is application that needs to track changes via Change Notifications.
There are two special endpoints: one that can be used to find which assets are needed for specific entity and one that can be used to find which entities need given asset.
Assets needed by specific entity
- This endpoint can be simply derived from any URL that serves entity content by adding /dependencies.
- /entity/{name}/{id}/dependencies or /entity/{name}/key/{key}/dependencies or /view/{name}/{key}/dependencies (GET)
- Method is always GET.
- A required role is defined by the READ operation; this endpoint is available if and only if the corresponding entity (url without /dependencies) is available.
- If the entity does not exist, response is 404 Not Found
If the entity does exist and request has sufficient permissions to read it, response is 200 OK with JSON document in following form:
JSON{"assets":[2438907]}
- The **assets** array is always present and it contains list of **all** assets needed to represent this entity.
- There is always at least one item: the main asset, with **id** equal to the **id** of entity itself.
- All referenced assets in whole JSON document are present in this list.
- **Note** that even assets references and relations mapped as **link** or simple **id** are also present in this result.
Entities that need specific asset
This is also called "reverse lookup".
- /reverse/ (GET)
- This endpoint returns list of all schemas that are available for reverse lookup, depending on request permission.
- Permissions needed are defined by special operation LOOKUP. Default value for this operation is false, which means that reverse lookup is no available for anyone.
- Note that by default, this list is empty.
- /reverse/single/{id} (GET)
- Method is always GET.
- Response is always 200 OK with JSON object result.
- Note that this endpoint never returns 401, 403 or 404 status code; missing assets or insufficient permissions are represented simply by empty result.
- Single property found is always present and it is an array of all entities found. This array might be empty if no entities are found.
- Each entity is represented by JSON object with properties:
- url: "Canonical" url of the entity, in the form of /entity/{name}/{id}.
- This is always present, because every entity is guaranteed to have canonical url.
- aliases: Array of alternative URLs that can be used to obtain the same entity. These aliases are available only when some custom keys are defined in given schema.
- This property is missing if there are no keys nor views available.
- url: "Canonical" url of the entity, in the form of /entity/{name}/{id}.
- Only entities with LOOKUP operation available for the request are searched. List of available schemas can be obtained by the reverse/ endpoint, but even in one single schema only some entities might be available.
- The mechanism is similar to the list and query endpoints - the main difference is that LOOKUP is used instead of READ operation.
- Entities are found by following all possible references (relations, asset references) defined in schema, but in reversed direction.
- Note that even for relatively simple schema with some inlined mappings, this query might be quite complex and resource-intensive. This is the reason why reverse lookup is not allowed by default.
Example: simple PIM application with versioned product that have some images and videos
All three schemas available for reverse lookup:
https://simplepim.sample.com/hcms/v2.1/reverse/
{"schemas":["image","video","product"]}
Reverse lookup for single asset:
https://simplepim.sample.com/hcms/v2.1/reverse/single/2438907
{
"found": [
{
"url": "https://simplepim.sample.com/hcms/v2.1/entity/image/2438907"
},
{
"aliases": [
"https://simplepim.sample.com/hcms/v2.1/entity/product/key/history/71331427/1",
"https://simplepim.sample.com/hcms/v2.1/entity/product/key/latest/71331427",
"https://simplepim.sample.com/hcms/v2.1/view/productMedia/71331427"
],
"url": "https://simplepim.sample.com/hcms/v2.1/entity/product/8207357"
}
]
}