Welcome to the Intempus REST API. This document contains how to read, create, update and delete data on Intempus using common server-side development environments. While Intempus' API supports all HTTP clients, we believe most integrations with Intempus will be data synchronization processes that are run as background processes.

Support

All support requests have to be in English.

If you encounter any problems with the API, contact us at api@intempus.dk. We intend to answer within one working day. Please make sure to describe the problem along with the full url, headers and body sent and the response and statuscode received. Example:

Request:

    GET intempus.dk/web/v1/work_category/
    Headers:
        Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
        Content-Type: application/json
    Body: empty

Response:

    Statuscode 401
    Body: Empty

To help us understand what you need, please also include a short description of your application.

If you received an error message with an incident ID (see Error responses below), please include the incident ID as text (not a screenshot).

Authentication

To authenticate you have to get an api key. We recommend using our key authentication system to connect to the API. In order to get an api key, you must have a working Intempus account with an active company. If you do not, you can create a demo company. To connect you have to include the following HTTP Header in your request:

Authorization: ApiKey <username>:<api_key>

Intempus model

The following will describe the Intempus model briefly in order to understand and use the api.

The main model in Intempus is the workreport. There are a number of models that are directly attached to a workreport.

To facilitate the identification of the appropriate endpoint, we recommend setting the application language of the administration interface to English This adjustment aligns with the English naming convention of our models and attributes, thereby simplifying the process of locating the correct endpoint.

Case

A case that can be worked on. All cases have a customer attached.

Employee

The employee performing the work. An employee needs an open contract to be able to make a workreport. The contract also describes which workmodel the employee is working under.

WorkModel

A workreport can have a worktype attached, that describe the type of work performed WorkTypes are grouped by workcategories, which are grouped by workmodels.

General notes for the api

Recommendations for integrators

We recommend getting a separate user account for your integration, to avoid the integration interfering with the admin normal usage of the system. Please add an email address to the user account, so we can contact you if we see any issues with the integration.

When integrating to another system a lot of data is expected to be sent back and forth between the two systems. For both systems too much can be detrimental to performance. The following are recommendations to reduce load and response sizes for the benefit of both systems.

Details on the mentioned features can be found below.

Throttling

The api is throttled to 500 requests per minute per user, and 50.000 per day. If any limit is hit the user will receive a 429 status code along with a Retry-After header containing the number of seconds until the user can make another request.

Parameter formats

Parameters indicated as URL expect the id of a model formatted as their url. This can always be found in the "resource_uri" field in the response of GET calls to models. Example: "/web/v1/work_type/2983/".

If nothing else is indicated, dates are specified as "YYYY-MM-DD" and times are specified as "HH:MM:SS"

Strings are generally allowed to be 100 characters. Exceptions will be noted on the specific attribute.

Nested resources

When a resource is specified in the type column, that attribute is a nested resource. A nested resource can be manipulated directly through the parent resource. When editing an existing nested resource, the 'resource_uri' attribute must be included, otherwise the api will attempt to create a new instance of the nested resource.

Filters

Available filters are described in the description section of each item. To filter on an attribute, and a query parameter with the filter parameter. The filter parameter is specified as the attribute name and the filter type, separated by a double underscore.

The following types of filters are available:

Examples:

https://intempus.dk/web/v1/work_category/?id__in=204,205
https://intempus.dk/web/v1/case_upload/?case__number=12

Paging

All list endpoints are paged. The maximum page size is 10000, and the default is 1000 per page. The page size can be changed by setting the 'limit' parameter.

We provide two options for changing to the next page. The default is using offset. But using offset is less performant for large datasets, so we also provide the option of using cursor based pagination. This is done by setting the query parameter 'pagination_type' to 'cursor'.

Offset based pagination

Changing to the next page is done by setting the 'offset' parameter. The following example sets the page size to 500 and requests the third page.

https://intempus.dk/web/v1/case/?limit=500&offset=1000

All list endpoints have a 'meta' attribute which contains information about paging. 'limit' and 'offset' shows the current settings, while 'next' and 'previous' contains links to the next/previous page. These are null when no such page exists. The final attribute 'total_count' shows how many objects exist in total for the current filtering. Example of a meta object below

{
  "meta": {
  "limit": 2,
  "next": "/web/v1/employee/?limit=2&offset=2",
  "offset": 0,
  "previous": null,
  "total_count": 25
}

Cursor based pagination

To use cursor based pagination, set the query parameter 'pagination_type' to 'cursor'. The cursor based pagination will always be ordered by the id of the objects and return the newest first. The response will then contain a 'meta' attribute with a 'next' attribute containing a URI to the next page. The following example sets the page size to 500 and requests the next page.

https://intempus.dk/web/v1/case/?pagination_type=cursor&limit=500&after=eyJwYWdlIjoxLCJvZmZzZXQiOjB9

All list endpoints have a 'meta' attribute which contains information about paging. For the cursor based pagination it only contains the next URI.

{
  "next": "/web/v1/employee/?pagination_type=cursor&limit=500&after=eyJwYWdlIjoxLCJvZmZzZXQiOjB9"
}

Ordering

Available orderings are described in the description section of each item. If no description is present, 'id' will be the default ordering. To order by an attribute, add a query parameter 'order_by' with the name of the attribute. To perform the inverse ordering, add a minus before the attribute name

Examples:

https://intempus.dk/web/v1/work_type?order_by=-work_category

Incremental updates

For applications synchronizing data from Intempus it is very valuable to only get objects that changed since last update. The 'logical_timestamp' attribute can be used to accomplish this, by filtering by the newest timestamp header received. Notice that deletions will not be shown in the list, and a full sync is needed to capture deleted objects. All responses to GET requests on list resources contains a 'Logical-Timestamp' header. This timestamp should be used for filtering the subsequent request. When filtering this way you are guaranteed to see each update at least once. An example of a logical_timestamp filter:

https://intempus.dk/web/v1/case/?logical_timestamp__gt=5436995

Currently implemented on the following models: workreport, case, customer, employee and product.

Notice that the logical timestamp is a 64 bit integer. It has no correspondence with a real timestamp, it is simply a counter that is incremented on each update. It has the sole purpose of allowing incremental updates without missing any updates. The Logical-Timestamp header contains the newest timestamp that is guaranteed to be committed to the database. It is therefore important to use this value, and not the logical_timestamp of an individual object.

Safeguarding against duplicate creations from duplicate POST requests

Consider the following scenario, a classic problem with a basic REST API:

The Intempus API implements a mechanism to ensure such duplications are impossible. Each of our database tables includes a uuid column, which can be filled in at creation time by supplying a creation_id field in the body of a POST request. This UUID defines a uniqueness constraint on the database table, and thus can be used as a guarantee that duplication as described above is impossible, provided the creation_id provided in the request's body is the same on duplicate/retry requests).

The assigned UUID will be a version 5 name based UUID computed as follows:

So, as long as the client includes a creation_id parameter in the body of the POST requests, the problematic scenario outlined above will now play out as before, with one crucial difference: instead of a duplicate entry being created in the server database upon the request retry, we return a "200-OK" HTTP response to indicate to the user that we did successfully receive and process the response, but did not create a new item (since the new item would violate the uniqueness of the UUID created in the first request).

Example

A user creates a case object via a POST to /web/v1/case with the following request body:

{
	"name": "my_case",
	"number": "1",
	"creation_id": "6",
	"customer": "/web/v1/customer/1/"
}

Let's suppose the POST successfully creates the case object in the database, but the client does not receive the 201 response, as described in the problem scenario above. The client retries the same request again. This time, the client is met with a "200-OK" response. The body of the response contains the UUID, namespace and creation_id as described above (as well as all other fields which would usually be returned in a 201 response):

{
  ...
  "creation_id": "6",
  ...
  "namespace": "39138a56-85f0-5e96-85d8-2aa537978054",
  ...
  "uuid": "71afbace-20b0-5803-be81-466aa94713cf",
  ...
}

Bulk operations

It is possible to do creations, updates and deletions in bulk by sending a PATCH request to the relevant list endpoint. The body of the request should contain an 'objects' attribute containing a list of the objects to be created or updated. Any entry with a resource_uri attribute will be updated, while any entry without a resource_uri attribute will be created as a new object. The body can also contain a 'deleted_objects' attribute containing a list of the resource_uri of the objects to be deleted.

Example

Send PATCH to /web/v1/work_report/. This will create a new workreport, update an existing workreport and delete an existing workreport.

{
  "objects":
    [
      {
        "creation_id": "1",
        "employee": "/web/v1/employee/1/",
        "start_date": "2024-01-01",
        "end_date": "2024-01-01",
        "worktype": "/web/v1/work_type/1/",
        "start_time": "09:00:00",
        "end_time": "10:00:00",
        "amount": 1,
        "remarks": "Create this object"
      },
      {
        "resource_uri": "/web/v1/work_report/1/",
        "remarks": "Update this field"
      }
    ],
    "deleted_objects":
    [
      "/web/v1/work_report/2/"
    ]
}

Error responses

In addition to the appropriate HTTP status code, we strive to return relevant details about any error that occurs during the handling of requests. For 500 errors, we provide a fallback message when no specific details are provided (subject to translation):

Sorry, this request could not be processed. Please try again later.

It might be followed by an incident id:

Incident ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

If you need to report an error to us, please include the incident ID as text (not a screenshot).