This page documents the API for Intempus Admin. There are two key parts to this documentation. The first part documents the connection flow required to prepare credentials. The second part documents the data exchange, which can be performed using those credentials.
There are two separate instances of the Intempus system, a production system and a testing system. The Intempus production system is running on https://intempus.dk/, the testing system is running on http://api-test.intempus.dk/ and https://api-test.intempus.dk/. The rest of this document will show prodution URLs, the testing URLs will be identical except for the hostname. There are some notable differences between production and testing:
When a user on your site indicate that they want to connect to Intempus Admin, you do the following:
Your credentials consist of these three values:
The following code shows how the nonce, hash, and URL can be generated using python code.
#!/usr/bin/python from hashlib import sha384 from os import urandom nonce = ''.join([c for c in urandom(99).encode('base64') if c.isalnum()][:65]) sha384_hash = sha384(nonce).hexdigest() base_url = 'https://intempus.dk/api/admin-connect' my_domain = 'example.com' url = '%s?site=%s&hash=%s' % (base_url, my_domain, sha384_hash) print 'Nonce:', nonce print 'Hash:', sha384_hash print 'URL:', url
The data exchange happens on the URL https://intempus.dk/api/admin-data-exchange?pk=the pk we issued. Each time this URL is accessed, each party will send a message consisting of JSON structured data. The request must conform to this JSON schema.
A minimal data exchange looks as follows. Client sends following as POST parameter with the name data:
{ "nonce": "the nonce you chose", "token": "the token we issued" }
Server replies with:
{ "condition_success": true, "failed_conditions": {}, "guaranteed_timestamp": a number, "namespace": "a UUID", "responses": [] }
Notice that since the POST request has to be URL encoded the JSON request will end up looking like this on the wire: data=%7B%22nonce%22%3A+%22the+nonce+you+chose%22%2C+%22token%22%3A+%22the+token+we+issued%22%7D
A data exchange through this API consists of the following phases, all of which are optional:
All preconditions must be satisfied in order for updates to happen in the database. If any precondition fail, we will skip from verification of preconditions to performing queries. None of the database updates will be performed in that case.
The response from the server will list the outcome of precondition checks as well as the responses to the queries. This will be the case both if all preconditions succeed as well as if some of them fail.
Each query is given as a set of key-value pairs. There are two mandatory parameters and seven optional parameters. The mandatory parameters are:
The optional parameters are:
JSON data with a list of queries could look like this:
{ "nonce": "the nonce you chose", "token": "the token we issued", "queries": [ {"class": "Customer", "type": "data"}, {"class": "Case", "type": "pk"}, {"class": "Employee", "type": "data-list", "creation_id": [ "Or7bG9Y6uXbjOug6KdjIfaHkUm58I9RD", "Ry1ix68IqmH5TktdE9R1dwivWs8w91Y9"] }, {"class": "WorkReport", "type": "count"} ] }
The reply from the server could look like this:
{ "condition_success": true, "failed_conditions": {}, "guaranteed_timestamp": 222, "namespace": "e758e41f-b7bc-56f6-ba84-e7b44e06d2b9", "responses": [ { "1": { "city": "", "contact": "D. Hallandsen", "country": "", "customer_group_id": null, "email": "", "logical_timestamp": 167, "name": "Forsikringsselskabet af 1873", "notes": "", "number": "", "phone": "", "street_address": "Strandvejen 42", "zip_code": "" } }, [1, 2, 3, 4], [ { "city": "", "country": "", "cpr_number": "", "creation_id": "Or7bG9Y6uXbjOug6KdjIfaHkUm58I9RD", "email": "", "id": 592, "logical_timestamp": 168, "may_CUD_customers_and_cases": false, "may_add_work_reports_to_all_cases": false, "name": "Kim Andersen", "number": "", "phone": "", "street_address": "", "uuid": "81620b21-e88e-5b70-bcb0-92549e05b037", "wipe_native_clients_logged_in_before": 0, "zip_code": "" } ], 22 ] }
This example reply would tell the client that there is one customer in the database. That customer has primary key "1", and the customer data is returned. Additionally it tell the client that there are four cases with primary keys "1", "2", "3", and "4". Finally it tells the client, that there are 22 work reports in the database.
Every time an object is updated that update is assigned a timestamp. These are assigned from a sequence of integers. There is no guarantee that all integers will be used. There will be cases where some numbers are skipped. There is also no guarantee, that updates will become visible in order of sequence number. One transaction may commit an update with sequence number 444, making it visible, while another ongoing transaction will later commit an update with sequence number 333. It is also not guaranteed that sequence numbers will be unique. When multiple objects are updated within a transaction it is legal for the server to assign separate sequence numbers to these or reuse the same sequence number.
It is guaranteed that when one transaction is committed and another transaction is started after the first was committed, then all sequence numbers assigned by the second transaction will be higher than all sequence numbers assigned by the first transaction.
Additionally the server provides a guaranteed timestamp in the reply. This guarantees that no update with a sequence number lower than the guaranteed value will become visible at a later time.
In the above example the guaranteed timestamp was 222, which means if any customer was created or updated with a timestamp less than 222, they would have been visible to that API call. Thus to get customers updated after the above API call, the following query can be used to get updates to customers made after the above example.
{ "nonce": "the nonce you chose", "token": "the token we issued", "queries": [ {"class": "Customer", "type": "count"}, {"class": "Customer", "type": "data", "mintime": 222} ] }
The reply from the server could look like this:
{ "condition_success": true, "failed_conditions": {}, "guaranteed_timestamp": 300, "namespace": "e758e41f-b7bc-56f6-ba84-e7b44e06d2b9", "responses": [ 2, { "1": { "city": "", "contact": "D. Hallandsen", "country": "", "customer_group_id": null, "email": "", "logical_timestamp": 250, "name": "Forsikringsselskabet af 1873", "notes": "", "number": "", "phone": "", "street_address": "Strandvejen 42", "zip_code": "" }, "2": { "city": "", "contact": "Sofie Petersen", "country": "", "customer_group_id": null, "email": "", "logical_timestamp": 350, "name": "Sofie Petersen", "notes": "", "number": "", "phone": "", "street_address": "N\u00f8rregade 7", "zip_code": "" }, } ] }
A list of classes accessible through the API can be retrieved by using query_license.
{ "nonce": "the nonce you chose", "token": "the token we issued", "query_license": true }
The reply will provide five lists of classes named according to the operations they can be used for.
{ "condition_success": true, "failed_conditions": {}, "guaranteed_timestamp": 310, "namespace": "e758e41f-b7bc-56f6-ba84-e7b44e06d2b9", "responses": [], "license": { "condition": [ "Customer", "Employee" ], "create": [ "Employee" ], "delete": [ "Employee" ], "query": [ "Customer", "Employee" ], "update": [ "Employee" ] } }
Currently the following classes are available under the standard Intempus license.
The JSON data for object creation could look like this:
{ "nonce": "the nonce you chose", "token": "the token we issued", "update": { "Case": { "4": { "conditions": { "active": true } } } }, "create": { "WorkReport": [ { "amount": 7.5, "case_id": 4, "employee_id": 4, "start_date": "2014-05-12", "end_date": "2014-05-12", "work_type_id": 1, "creation_id": "8tktmPSafvMsDPBgcWJM" } ] }, "queries": [ {"class": "WorkReport", "type": "data", "mintime": 350} ] }
This example will first verify that case number 4 is active. If case number 4 is active, a work report will be created. If case number 4 had been inactive, this would be reported as a failed condition, and no work report will be created.
After creating the object a query is performed listing all work reports created after time stamp 350.
On any class that has a uuid field, this can be filled in at creation time by passing creation_id field in the create structure. The assigned UUID will be a version 5 name based UUID computed as follows.
The following three ranges of ASCII characters are guaranteed to be valid in creation_id: 43 - 57 (+ , - . / and digits), 65 - 90 (upper case letters), and 97 - 122 (lower case letters). Any other characters may be rejected by the server or result in a different UUID from the one resulting from the above calculations.
The server could reply with:
{ "condition_success": true, "failed_conditions": {}, "guaranteed_timestamp": 350, "namespace": "e758e41f-b7bc-56f6-ba84-e7b44e06d2b9", "responses": [ { "48": { "amount": 7.50000000000000000000, "approved": false, "case_id": 4, "contract_id": 4, "creation_datetime": "2014-05-12 16:28:34.441547", "employee_id": 4, "end_date": "2014-05-12", "end_time": null, "logical_timestamp": 398, "product_id": null, "remarks": "", "start_date": "2014-05-12", "start_time": null, "uuid": "eb6fd268-a9e0-5c20-bfc3-c709eee5b385", "work_type_id": 1 } } ] }
Since condition_success is true, the client will know that case 4 was indeed active. The server will automatically add conditions to check for existence of all referenced objects. Had case 4 not existed or had work type 1 not existed, it would have reported this as a failed condition, even if not explicitly listed as a condition.
Since the reply contains a work report with the UUID computed from the creation_id chosen by the client, the client will know, that this was in fact the work report, which it just created.
If the client want to update the object created in the previous example to set it as approved, it could use the following JSON data:
{ "nonce": "the nonce you chose", "token": "the token we issued", "update": { "WorkReport": { "48": { "conditions": { "logical_timestamp": 398 }, "update": { "approved": true } } } } }
It is recommended that updates are conditioned on the timestamp to prevent lost updates in case multiple updates are performed in parallel.
If instead of updating the object in the previous example, the client had wanted to delete it, the following JSON could be used:
{ "nonce": "the nonce you chose", "token": "the token we issued", "update": { "WorkReport": { "48": { "conditions": { "logical_timestamp": 398 }, "delete": true } } } }
Should both be executed in parallel by two different clients, only one can succeed. If for example the update happened first, the second request would not delete the now approved work report, because of the condition. The server would reply as follows:
{ "condition_success": false, "failed_conditions": { "WorkReport": [ 48 ] }, "guaranteed_timestamp": 350, "namespace": "e758e41f-b7bc-56f6-ba84-e7b44e06d2b9", "responses": [] }