Backend documentation

Overview

The MOTIONTAG backend API provides endpoints meant to be consumed by other backends, for example to download analysis results in bulk. In order to access it, you need to be given access by MOTIONTAG staff. As part of that you will be provided with a dedicated domain, which also serves as the base URL for the API.

API base URL

Only accessible when signed in. You need a custom domain setup by MOTIONTAG (e.g. my-tenant-key.motion-tag.de) and an account to sign in. Contact MOTIONTAG Support to request a custom domain setup and an account. If you already have both, visit your custom domain and sign in to see a customized version of this documentation.

The API tries to follow the JSON API standard where applicable.

As part of evolving the API endpoints described here, additional attributes may be added later. Clients should take this into account and not break when new attributes are added.

Authentication with JWT

Every request must be authenticated with a JWT (see JSON Web Token for general information about JWTs). The JWT must be signed with the shared secret which is available when signed in. Depending on the endpoint, the JWT must include specific claims such as sub and exp, which are documented in each endpoint's parameter list. Depending on the intended use, a JWT can either be scoped to a single end user, or to all users.

Unless noted otherwise, the JSON Web Token must be sent as HTTP Authorization header with Bearer, like this:

Authorization: Bearer abc123.def456.gehz7890

Shared secret

Only accessible when signed in. You need a custom domain setup by MOTIONTAG (e.g. my-tenant-key.motion-tag.de) and an account to sign in. Contact MOTIONTAG Support to request a custom domain setup and an account. If you already have both, visit your custom domain and sign in to see a customized version of this documentation.

Example code

In Ruby using the jwt gem, assuming the shared secret is available in the environment variable SHARED_SECRET, and sub is foo:

require "jwt"
JWT.encode({ "sub" => "foo", "exp" => Time.now.to_i + 60 }, ENV["SHARED_SECRET"], "HS256")
# => abc123.def456.gehz7890

Storyline API

This API endpoint is a beta version

While in beta, subject to incompatible changes without notice.

This endpoint allows to retrieve a part of the storyline of a single user. It is meant for interactive use, such as displaying a list of tracks and stays on a map in a mobile app.

GET /storyline/{date}

Request parameters

Name Type In Description
sub String JWT UUID of a user
aud String JWT Fixed value for this endpoint: read
exp Integer JWT Optional. Expiration time
date Date Path Single date, e.g. "2020-01-01"
fields[Track] String, comma separated Query Optional. List of fields to include in Track
fields[Stay] String, comma separated Query Optional. List of fields to include in Stay

The fields parameters allow you to limit the list of fields being returned to a sparse fieldset. In particular the geometry field of Track can become quite large and it makes sense to exclude it when not needed.

Tracks and stays are filtered to have started on the given date, in the local timezone they occured in.

Response body

A list of storyline items. The list is ordered chronologically.

type
Can be Track or Stay. Track has mode attributes, Stay has purpose attributes.
started_at
Is in the local timezone it occured in and includes the UTC offset.
finished_at
Is in the local timezone it occured in and includes the UTC offset.
length
Is only present for Storyline items of type Track and is in meters.
mode_key
Is the untranslated key of the mode, only present if the type is Track.
mode_name
Is the translated name of the mode, only present if the type is Track. See below for more information about the languages.
purpose_key
Is the untranslated key of the purpose, only present if the type is Stay.
purpose_name
Is the translated name of the purpose, only present if the type is Stay. See below for more information about the languages.
geometry
geometry of a Track is a GeoJSON feature of type LineString or MultiLineString. geometry of a Stay is a GeoJSON feature of type Point.

The possible values for mode and purpose depend on the server configuration and can be looked up in the admin dashboard.

The language of translated fields (currently mode_name and purpose_name) is determined by the given Accept-Language header, or the server's default. The available languages depend on the settings of the server.

Example request

GET /storyline/2020-01-01
{
  "data": [
    {
      "id": "4db59ee6-92ae-4e96-b5ec-eb192ee175cc",
      "type": "Track",
      "attributes": {
        "started_at": "2020-11-01T11:00:00+01:00",
        "finished_at": "2020-11-01T13:00:00-05:00",
        "length": 5000,
        "mode_key": "light_rail",
        "mode_name": "Rapid transit railway",
        "geometry": {
          "type": "LineString",
          "coordinates": [[2.0, 3.0], [3.0, 4.0]]
        }
      }
    },
    {
      "id": "26966953-6a54-4f0f-886c-e2e0074b3005",
      "type": "Stay",
      "attributes": {
        "started_at": "2020-11-01T13:00:00-05:00",
        "finished_at": "2020-11-02T01:00:00-05:00",
        "purpose_key": "home",
        "purpose_name": "At home",
        "geometry": {
          "type": "Point",
          "coordinates": [3.0, 4.0]
        }
      }
    }
  ]
}

This example response contains two storyline items, one Track and one Stay.

Example request with limited fields

GET /storyline/2020-01-01?fields[Track]=started_at,finished_at,mode_name&fields[Stay]=started_at,finished_at,purpose_name
{
  "data": [
    {
      "id": "4db59ee6-92ae-4e96-b5ec-eb192ee175cc",
      "type": "Track",
      "attributes": {
        "started_at": "2020-11-01T11:00:00+01:00",
        "finished_at": "2020-11-01T13:00:00-05:00",
        "mode_name": "Rapid transit railway"
      }
    },
    {
      "id": "26966953-6a54-4f0f-886c-e2e0074b3005",
      "type": "Stay",
      "attributes": {
        "started_at": "2020-11-01T13:00:00-05:00",
        "finished_at": "2020-11-02T01:00:00-05:00",
        "purpose_name": "At home"
      }
    }
  ]
}

Calendar API

This API endpoint is a beta version

While in beta, subject to incompatible changes without notice.

This endpoint allows you to retrieve one element for each day with a count of storyline items since the activation of the user for the requested year.

GET /calendar/{year}

Request parameters

Name Type In Description
sub String JWT UUID of a user
aud String JWT Fixed value for this endpoint: read
exp Integer JWT Optional. Expiration time
year Integer Path Single year, e.g. "2020"

Response body

A chronologically sorted list of dates the user was active in, with storyline counts.

Example request

GET /calendar/2020
{
  "data": {
    "id": "7015915d-cb3c-417e-a855-6091bf3f9d14/2020",
    "type": "Calendar",
    "attributes": {
      "days": [
        {
          "date": "2020-12-29",
          "storyline_count": 0
        },
        {
          "date": "2020-12-30",
          "storyline_count": 2
        },
        {
          "date": "2020-12-31",
          "storyline_count": 1
        }
      ]
    }
  }
}

This example response contains always just one entry. It shows all days of the year with a count of storyline items per day since the activation of the user (in this example case the user was activated on '2020-12-29').

Synchronization API

This is an endpoint for full synchronization of the complete storyline of all users. Meant to be implemented by customers that run their own backend service and want to maintain a full copy of all their user's storyline on their backend service.

The storyline is an endless stream of chronologically sorted entries. New entries are appended continuously by the Modedetection. This endpoint implements a mechanism to retrieve the complete stream, grouped into pages of reasonable size, by adding a link to the next page of data to a response that contains entries. An initial request without the special page parameter will return the first available page, and include a link to the next page. When the end is reached, an empty page without a next link is returned. In this case the consumer should pause for a short time and then retry to load the same page again.

GET /sync/storyline

Request parameters

Name Type In Description
sub String JWT Fixed value for this endpoint: sync
exp Integer JWT Optional. Expiration time, recommended to set to near future
page[after] String Query Paging position, used by the server to determine next page

Example initial request

Do not include page parameter on the initial request.

GET /sync/storyline
{
  "data": [
    {
      "type" : "Track",
      "id" : "8861e1e2-abd0-41cc-9b3d-f8c2d2613cf4",
      "attributes" : {
        "user_id" : "d28907c7-0846-461f-950e-9d40f2ca418c",
        "created_at" : "2018-01-01T16:00:00Z",
        "started_at" : "2018-01-01T14:00:00Z",
        "started_at_timezone" : "Europe/Zurich",
        "finished_at" : "2018-01-01T15:00:00Z",
        "finished_at_timezone" : "Europe/Zurich",
        "geometry" : {
           "type" : "LineString",
           "coordinates" : [
              [
                 1,
                 1.5
              ],
              [
                 1.5,
                 2
              ]
           ]
        },
        "track_mode" : "bicycle",
        "track_length_in_meters" : 5000
      }
    },
    {
       "type" : "Stay",
       "id" : "c862b09b-2786-4be3-b0d6-90997783a343",
       "attributes" : {
         "user_id" : "437b0a94-d495-4e4e-96fb-fe1783b5c95e",
         "created_at" : "2018-01-01T16:00:00Z",
         "started_at" : "2018-01-01T14:00:00Z",
         "started_at_timezone" : "Europe/Zurich",
         "finished_at" : "2018-01-01T15:00:00Z",
         "finished_at_timezone" : "Europe/Zurich",
         "geometry" : {
            "type" : "Point",
            "coordinates" : [
               1,
               1.5
            ]
         },
         "stay_purpose" : "study"
       }
    }
  ],
  "links": {
    "next" : "https://api.motion-tag.de/sync/storyline?page[after]=abc123"
  }
}

The consumer is expected to store the received entries and immediately afterwards request the next page given in links.next. The value of page[after] has no meaning beyond being used by the server to determine the next page.

Example subsequent request

In this example, no data is returned. In the case of no data, the consumer should pause for a short time and then request the same URL again.

GET /sync/storyline?page[after]=abc123
{
  "data": []
}

Dump downloads

Data dumps available on the admin dashboard interface under "Dumps" can be downloaded from this endpoint by using their filename. The filename is chosen automatically on creation and consists of the dump type, its user and date criteria, and the format. Note that the API currently only provides the ability to download dumps, not to create or delete them.

GET /dumps/{filename}

Request parameters

Name Type In Description
sub String JWT Fixed value for this endpoint: dumps
exp Integer JWT Optional. Expiration time, recommended to set to near future
filename String Path Filename of the dump

Example request

GET /dumps/ExampleDump.SomeProject.2020-01-01--2020-01-31.csv.gz

Alternative authorization

Alternatively to using an HTTP header, the dump downloads can also be authenticated with a URL parameter.

GET /dumps/ExampleDump.SomeProject.2020-01-01--2020-01-31.csv.gz?jwt=abc123.def456.gehz7890