Document Level Meta
Before we go into each feature, we want to provide you, as the API client, with as much information about the possible features for the endpoint you are calling. This information is provided in themeta field of the response, under the features key.
Let’s consider the following example as we step through the features:
meta.features object provides you with the possible query parameters you can use for the endpoint.
The meta.features.params key indicates that these are query parameters.
For each param, the available feature is the key and within each one, the options key provides you with the possible field names that you can use.
We’ll go through each param now.
Sorting
When returning a collection of resources, we can sort the results based on a field. In our example, we can see that we could choose to sort on thename, created_at or updated_at fields.
To do so, you can add the sort query parameter to the request:
https://api.ctrl-hub.com/v3/operations?sort=name
If you would like to reverse the order, you can add a - to the field name:
https://api.ctrl-hub.com/v3/operations?sort=-name
When specifying multiple sort fields, the API will only sort by the first field. Whilst it is possible to send a request that has
?sort=name,created_at and for the API to return a response, the API will only sort by the first field, so the request is the equivalent of ?sort=name.We may change this behaviour in the future, so it is recommended to only specify one sort field for now.Filtering
When returning a collection of resources, you can narrow the results with thefilter query parameter.
Ctrl Hub uses a compact, function-style filter syntax. A single filter is written as operator(field,value), and the whole expression is passed in one filter parameter. For example, to filter operations by their status:
https://api.ctrl-hub.com/v3/operations?filter=eq(status,in_progress)
This performs an exact match, so you will get every operation whose status is exactly in_progress, within the document data. Filtering on a unique field (such as an id) would return a single resource, still inside the data array.
Values are not quoted. Write
eq(status,in_progress), not eq(status,"in_progress") - any quotes are treated as part of the value. Because the syntax uses (, ), , and [], remember to URL-encode the filter value if your HTTP client does not do it for you.Operators
The range operators are
ge and le (greater/less than or equal), not gte / lte.Matching multiple values
To match any one of several values, usein with a bracketed, comma-separated list:
https://api.ctrl-hub.com/v3/operations?filter=in(status,[todo,in_progress])
This returns operations whose status is todo or in_progress.
Combining filters
To require several conditions at once, wrap them inand(...):
https://api.ctrl-hub.com/v3/operations?filter=and(eq(status,in_progress),contains(name,survey))
This returns operations where both the status is in_progress and the name contains survey. Passing several comma-separated expressions at the top level is treated the same way, so filter=eq(status,in_progress),contains(name,survey) is equivalent.
To match either condition, use or(...):
https://api.ctrl-hub.com/v3/operations?filter=or(eq(status,todo),eq(status,in_progress))
And to negate a condition, wrap it in not(...):
https://api.ctrl-hub.com/v3/operations?filter=not(eq(status,done))
Nested fields
Some resources expose nested fields, which you filter with a dotted path. For example, an operation’s scheduled dates live underdates.scheduled, so you can filter on the scheduled start:
https://api.ctrl-hub.com/v3/operations?filter=ge(dates.scheduled.start,2025-06-01T00:00:00Z)
The fields (and nested paths) you can filter on differ per endpoint. Each endpoint documents its available fields on the
filter query parameter in the API Reference.Time Filters
Date and time fields accept RFC 3339 timestamps together with the range operators, so you can filter on a point in time or on a window. For example, to filter operations modified since a certain date:https://api.ctrl-hub.com/v3/operations?filter=ge(updated_at,2025-01-01T00:00:00Z)
This will return all operations modified since the 1st of January 2025 until now.
You could also filter on operations modified up to a certain date:
https://api.ctrl-hub.com/v3/operations?filter=le(updated_at,2025-01-31T23:59:59Z)
This will return all operations modified up to the 31st of January 2025.
To filter on a range of dates (a window), combine both bounds with and:
https://api.ctrl-hub.com/v3/operations?filter=and(ge(updated_at,2025-01-01T00:00:00Z),le(updated_at,2025-01-31T23:59:59Z))
This will return all operations modified between the 1st and 31st of January 2025.
Including Data
Requesting data to include is one of the most powerful features of the API. It allows you to request related resources in the same request, reducing the number of requests you need to make to get the data you need. GraphQL has a similar feature set, but the JSONAPI specification is more easily accessible and means you can avoid the complexity of GraphQL and it’s learning curve. Within JSONAPI, relationships are represented in each resource. Let’s consider an Operation. An operation belongs to a scheme, has an operation type, and is carried out by one or more assignees (the users doing the work). An Operation resource would look like this, with each relationship pointing at a related resource by itstype and id:
include query parameter to the request:
https://api.ctrl-hub.com/v3/operations/7c9e6679-7425-40de-944b-e07fc1f90ae7?include=scheme
Which would return the scheme in the included array:
https://api.ctrl-hub.com/v3/operations/7c9e6679-7425-40de-944b-e07fc1f90ae7?include=scheme,assignees
Both now appear in the included array:
include query parameter for each endpoint in the API Reference. For an operation you can include scheme, work_order, operation_type, assignees, properties, permits, streets, teams, appointment, forms, template and organisation.
Some Considerations
As you include more resources, three things will happen:- The response will get larger, as you are including more data. This means there is more data to transfer over the wire, and more data for you to process.
- The response will get more complex, as you are including more relationships. This means you will have to navigate the response to get the data you need.
- The response will get slower, as you are including more data. This means the response time will increase as you include more data.
Pagination
When requesting a collection of resources, we adopt thelimit / offset approach.
limit restricts the number of resources returned in the data of the response. If you request includes, this limit does not apply to the included data.
offset is the number of resources to skip before returning the resources.
For example, to get the first 10 operations:
meta field of the response, under the pagination key:
counts key provides you with the total number of pages and resources, and the requested key provides you with the limit and offset you requested.
meta.pagination.offsets.previous and meta.pagination.offsets.next provide you with the offset for the previous and next pages respectively. If you are on the first page, previous will be a null value. If you are on the last page, next will be a null value.
If you request a page that is out of bounds, the API will return a
200 status code and an empty data array. If you are iterating though pages, you should check the data array to see if it is empty, and if it is, you should stop iterating.We may support cursors in the future, reach out if this is something you would like to see.