Last modified: 2016-02-08

WebUntis API v. 3.4.4

WARNING: This is not the official API provided by Untis GmbH. Instead, it is a custom API developed by Affect IT I/S primarily for use by Danish customers of WebUntis. If you are not a Danish WebUntis customer, then this API will probably not work for you.

Overview

The main purpose of the API is efficient, system-wide synchronization of data to and from WebUntis.

It is a RESTful API, built on the Django web framework which is written in Python, and uses a PostgreSQL backend. It has been tested with Apache to serve the data, but any WSGI-capable web server should work. The API has it's own copy of the data in WebUntis that is updated via a cron job, so there is a small delay from a change is made via WebUntis until it is available in the API. The cron job only uses server resources if something actually changed in WebUntis, and typically completes in less than 5 seconds if there are updates. To control which schools are exposed via the API, and other administrative tasks, Django provides a web-accessible administration interface.

The API is hosted here: https://api.webuntis.dk/api/

Authentication

Access to data is controlled by an API key that must be sent as an HTTP header with the name X-API-KEY. Each school has its' own set of API keys, and ach API key only gives access to the data connected to that school.

There are two kinds of API keys; one that gives access to all data belonging to the school, and one that only gives access to data that is normally publicly available to non-privileged users via WebUntis. The API keys for full and public access are handed out on request. In the following examples, the API key must be added to the HTTP headers. Any errors in the request are communicated in the HTTP status.

Data format

Data from the API are in JSON format. Timestamps are timezone-aware and in UTC, and references from one object to other objects (e.g. the department for a teacher) are given by Untis ID of the object pointed to. Untis IDs are only unique within a category, and only within a specific school. In other words, an object is only uniquely identifiable in the API as a combination of school ID, category name and Untis ID.

Synchronization

Synchronization is done in a tree-like fashion, based on timestamps of the latest change in data. We must cache certain synchronization information locally. This consists of a global timestamp of the school, a timestamp for each category to be synchronized and a list of (Untis ID, timestamp) tuples (called a sync list) for each category.

To synchronize data, we first fetch the global timestamp for the school and compare with our locally stored value from the previous sync. If the timestamp has changed, we proceed to sync each category that we are interested in syncing. For each category, we fetch the timestamp for said category (e.g. teachers or lessons) and compare with our locally stored value from the previous sync. If the timestamp has changed, we fetch the sync list for said category and compare with our locally stored sync list from the previous sync. Any Untis ID only present in the local list should be deleted locally. Any Untis ID only present in the remote list should be created locally. For every Untis ID present in both lists, we compare the remote timestamp with the local timestamp. For the objects that need to be updated or created, we fetch the full data for these objects from the API (see examples section) and update our local store. Next, we update the local timestamp of the category. Finally, we update the local timestamp of the school.

The timestamp of an object only refers to changes in fields directly related to that object, i.e. attributes and foreign key or many-to-many relations to other objects. For changes in the related object itself, you must refer to the timestamp of that object. For example, if the surname of a teacher changes, then the timestamp of the teacher changes. But the timestamp of a lesson taught by the teacher is not changed. But if the teacher is replaced by a substitute teacher on this lesson, then the timestamp of the lesson changes.

In some circumstances, it is possible for a timestamp to be updated even though there are no visible changes to attributes exported in the API. This is not an error.

Many objects reference other object types, so it is important to sync categories in the correct order. I.e. don't sync Lessons before Teachers, because you would end up getting a Lesson that points to a Teacher that you have not yet imported. If a Teacher disappears from the API but still references Lessons in your local cache, you can rest assured that the following sync of Lessons will have removed the references as well.

Creating, updating and deleting

Given an API key with full access, it is possible to create, update and delete most master data using the POST, PUT and DELETE verbs. Create and update must send JSON objects in the same form as the object returned in a GET request. POST requests must be sent to /some_model, while PUT and DELETE requests must be sent to /some_model/123.

On creation, the untis_id value must not be present in the JSON object. A last_update value will be created automatically. It is possible to set the value of the last_update field explicitly by adding this field to the JSON object. When adding or updating foreign key or many-to-many relations, send the Untis ID or Untis IDs of the related objects. It is possible to create multiple objects in one request by sending a list of JSON objects. A POST request will return the new object or list of objects.

When updating, the last_update field will be updated automatically. However, it is possible to set the value of the last_update field explicitly. Do NOT set it to the previous value, as other API consumers would not be able to detect your changes. It is possible to perform partial updates of objects by supplying only the fields to be updated. If a foreign key or many-to-many relation should be empty, and and the object supports it, set the field explicitly to null or [], respectively. A PUT request will return the resulting, updated object.

With update and delete requests, the API checks that current timestamp of the object being updated or deleted by the user is the same as your local copy of the object. Therefore, the user must send the last_update value of his local copy of the object in a separate field called original_last_update. This eliminates race conditions when multiple users update the same object.

Performance considerations

HEAD requests to detect changes can be sent as often as needed. Currently, data are fetched from WebUntis every 2 minutes, so polling much more often than that does not make sense.

The API will compress the response when a standard Accept-Encoding: gzip, deflate header is received. Compression often reduces response size by more than 90% for large (Untis ID, timestamp) lists.

Examples

Following is a couple of examples of using the API. It is assumed that the API key is passed as an X-API-KEY: myapikey HTTP header.

To see if data in the API is up-to-date compared to WebUntis:

GET https://api.webuntis.dk/api/status

This can be used as a sanity check so see if the API is publishing fresh or stale data.

Timestamps on objects and object types are passed as the Last-Modified header in HEAD requests. To see when any data for any school was changed:

HEAD https://api.webuntis.dk/api/schools

To see when any data for school with ID 8 was changed:

HEAD https://api.webuntis.dk/api/schools/8

The API key uniquely identifies the school, so the following URLs don't contain the school ID.

To see when data for teachers was changed:

HEAD https://api.webuntis.dk/api/teachers

To get a list of teacher ID's for synchronization:

GET https://api.webuntis.dk/api/teachers

To get a list indexed by extern_id instead (also works with name):

GET https://api.webuntis.dk/api/teachers?id_field=extern_id

To get the full data for individual items, it is preferable to get full data for multiple objects in one request, to cut down on the number of requests. This is done by sending a list of Untis ID's as a GET parameter to e.g.:

GET https://api.webuntis.dk/api/teachers?untis_ids=1,2,3

This drastically reduces the number of requests needed compared to getting one object at a time. Users must rate-limit the amount of IDs in the GET request to something sensible, e.g. 100. The maximum URL length of Apache is 8177 bytes. This method also works with extern_id and name:

GET https://api.webuntis.dk/api/teachers?extern_ids=a1,b2,c3 GET https://api.webuntis.dk/api/teachers?names=Foo,Bar,Baz

For testing and debugging, it is possible to get full data for a single object (here the teacher with untis ID 1):

GET https://api.webuntis.dk/api/teachers/1

Equivalent debugging URLs exist for getting objects by extern_id or name:

GET https://api.webuntis.dk/api/teachers/a1?id_field=extern_id GET https://api.webuntis.dk/api/teachers/Foo?id_field=name

To get all timetable events:

GET https://api.webuntis.dk/api/lessons

With the API for public access, this will only return the lessons that obey the general access restriction rules entered in WebUntis.

For the object types that support it, it is also possible to restrict the request to only return objects for specific departments, or a limited date range. To do this, send a GET request with the relevant parameters:

GET https://api.webuntis.dk/api/lessons?departments=1,2,3&start=2013-10-01&end=2013-12-31

For reasons originating outside WebUntis, courses in the API corresponds to what is called lessons in WebUntis, and lessons in the API corresponds to timetable elements in WebUntis.

If the API user only wants to synchronize the timetable for specific teachers, rooms or students, the following URLs can be used:

HEAD https://api.webuntis.dk/api/teachers?relation=lessons

This behaves identical to https://api.webuntis.dk/api/teachers but returns teacher Untis IDs and a timestamp which records any change (creates, updates and deletes) to lessons connected to the specific teacher. Use HEAD https://api.webuntis.dk/api/lessons to decide when to refresh this URL.

HEAD https://api.webuntis.dk/api/teachers/1/lessons

This behaves identical to HEAD https://api.webuntis.dk/api/teachers?relation=lessons but for just one teacher.

GET https://api.webuntis.dk/api/teachers/1/lessons

This behaves identical to https://api.webuntis.dk/api/lessons but only returns the lesson Untis IDs and lesson timestamps belonging to this specific teacher.

URL reference

The following URL contains a full, browseable overview of the URL structure of the API from within a browser: https://api.webuntis.dk/api/index You can browse a specific school by adding the API key: https://api.webuntis.dk/api/index?api_key=[my_api_key]

Changelog

Version 3.4.2: Extended the /named/ URLs to all object types that have a name attribute. If multiple objects have the same name, HTTP 300 is returned. On GET requests, a list is returned with the Untis IDs that share this name. Documented the id_field on single URLs, and the extern_ids and names GET parameters on list URLs. /foo/named/bar is equivalent to foo/bar?id_field=name which is now the preferred form.

Version 3.4.1: Added extern_id and name attributes to Course objects. Renamed User.personalname to User.extern_id to improve consistency.

Version 3.4: Fixed multiple bugs in POST and PUT requests. Added /some_model/named/abc which is an alias for /some_model/123 (when the name parameter of /some_model/123 is abc) for the models where unique names are guaranteed in WebUntis, i.e. users, usergroups and schools.

Version 3.3: Added GET /some_model?active=true for the models that support the active attribute. Adding this to the URL causes the API to only returns some_model Untis IDs for model instances that are active. The attribute has been added to the JSON output og some_model instances. If the parameter is not set, the default is to return all objects, active as well as inactive. Previously when using a public API key, we sent only active objects, and requests for non-active objects returned 410 Gone as if they did not exist. This caused problems for API users because inactive objects could still be referenced in JSON output of other models, e.g. lessons.

Version 3.2: Added GET /some_model?relation=lessons which returns some_model Untis IDs and timestamps of timetable changes to each model instance.

Version 3.1.2: Added homework_title, homework, topic_title and topic fields to lessons.

Version 3.1.1: Added /timegrid objects. Timegrids are the pre-defined start- and end times which normal lessons must follow.

Version 3.1: Added /some_model/123/lessons to teachers, students, groups, locations, subjects and users. Fixed the following conceptual mistakes in previous versions: