NAV
bash javascript

Introduction

Welcome to the Timing API reference.

If you prefer a more interactive presentation of the API, you can download our Postman Collection and open it in a tool like Postman or Paw.

We also offer three Siri shortcuts that demonstrate starting and stopping tasks via the API:

Use Cases

Siri Shortcuts and Automation

Using the Timing API, you can quickly create Siri shortcuts to e.g. start and stop tasks. During installation, the shortcuts will ask you for an API which you can generate in the 'API Keys' section of the web app. Once installed, simply run the corresponding shortcut to start or stop a task.

Feel free to customize these shortcuts to your liking, e.g. by changing the "Start Task" shortcut to let you select from a couple of preset titles instead. We are also interested in the shortcuts you create, so please let us know what you build with this!

You could also create scripts that start or stop a task whenever you perform a specific action; see Start a new task. and Stop the currently running task. for the corresponding API calls.

Managing your team's time

The Timing API can also help you manage your team's time. For example, you could write a script that creates the same set of projects for all of your team members. Retrieving all of your team members' logged times in the form of tasks is also possible, see Return a list of tasks.. Note that you will probably restrict this query to tasks that are not currently running by appending ?is_running=false to the end of the URL.

Note that each team member currently has their own Timing Web account. Therefore, each of them will need to generate their own API key and share that with you. Your script would then iterate over each team member's API key and retrieve their tasks or create projects for them. We can also offer API access through OAuth; let us know if you need that.

Integrating with your billing system

The API also makes it possible to integrate with whatever billing system you are currently using. Simply retrieve your most recent time entries, then send them to your billing system in the desired format. You can also create a script to create a custom report in exactly the format you need, of course.

Make sure to have a look at the ?include_project_data=true query parameter to include the corresponding project's attributes in the response. This lets you retrieve project titles without having to do a second API call to the "Projects" collection.

Zapier integration

We also offer a Zapier integration. This lets you connect the API to thousands of other services with just a few clicks, solving the problems mentioned above without having to write any code. To start using this integration, see the Zapier section in the web app.

Example use cases include:

We also recommend for you to reach out and let us know your desired use cases!
This helps us prioritize which API features to build first.

API Usage

The API root is https://web.timingapp.com/api/v1. All endpoint URLs share this prefix. Sample code for each available API call can be found in the right-hand column. Note that query parameters need to be URL-encoded, as shown in the Bash example for the Return a list of tasks. call.

Authentication

The Timing API requires authentication with an API key. You can generate a key in the 'API Keys' section of the web app. Once generated, add an Authorization header to each request with value Bearer {{token}}, where {{token}} is your key.

Rate limiting

The API enforces a rate limit of 500 requests per hour. You can retrieve your current quota via the x-ratelimit-remaining header attached to every request.

Request and response data

The API expects requests and returns responses in the JSON format. The actual response payload can be found in the data field. Additional data might be provided in the links and meta fields described below. Refer to the descriptions of individual API calls for concrete examples.

Date format

As Timing is a time-tracking application, its API has to work with many dates. Dates returned by the API will always be formatted as an ISO8601 string including microseconds as well as the time zone, for example 2019-01-01T00:00:00.000000+00:00. Currently, all dates returned will be in the UTC timezone, but your code should not make any assumptions about that.

When sending dates in your requests, we recommend providing dates in an ISO8601 format without microseconds but including the time zone, for example 2019-01-01T00:00:00+00:00 or 2019-01-01 00:00:00+00. When you provide dates without a time zone is provided, Timing currently assumes these date to be in UTC. That may be subject to change, however, so try to provide a timezone with your request whenever possible.

References

The Timing API identifies entities via the self field, which contains a link relative to the API root, for example /projects/1. This avoids any ambiguity about the type of the linked resource and provides you with a convenient way of looking it up, without having to look up the correct API call in this documentation: Simply append the link's value to the API root, resulting in e.g. https://web.timingapp.com/api/v1/projects/1.

References should be treated as opaque strings; your code should not assumptions about their structure.

Shallow references

For API responses that contain related entities, these entities are usually referenced in a "shallow" fashion. Instead of including the full object, a placeholder containing only the self field is provided, e.g. as "parent": {"self": "/projects/1"}. For the Return a list of tasks. call, you can append the ?include_project_data=true query parameter to include the corresponding project's attributes in the response. This lets you retrieve project titles without having to do a second API call to the "Projects" collection.

Links

Some responses include links to related queries or entities. This includes pagination and queries for related entries.

Pagination

By default, collection responses are paginated to 100 items per page. The links to retrieve the first, last, next and previous pages are part of the response's links field. Additional information about the paginated data can be found in the meta field.

Projects

Return the complete project hierarchy.


Requires authentication
See Display the specified project. for the returned attributes.

Example request:

curl -X GET -G "https://web.timingapp.com/api/v1/projects/hierarchy" \
    -H "Authorization: Bearer {{token}}"
const url = new URL("https://web.timingapp.com/api/v1/projects/hierarchy");

let headers = {
    "Authorization": "Bearer {{token}}",
    "Accept": "application/json",
    "Content-Type": "application/json",
}

fetch(url, {
    method: "GET",
    headers: headers,
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (200):

{
    "data": [
        {
            "self": "\/projects\/1",
            "title": "Project at root level",
            "title_chain": [
                "Project at root level"
            ],
            "color": "#FF0000",
            "productivity_score": 1,
            "is_archived": false,
            "children": [
                {
                    "self": "\/projects\/2",
                    "title": "Unproductive child project",
                    "title_chain": [
                        "Project at root level",
                        "Unproductive child project"
                    ],
                    "color": "#00FF00",
                    "productivity_score": -1,
                    "is_archived": false,
                    "children": [],
                    "parent": {
                        "self": "\/projects\/1"
                    }
                }
            ],
            "parent": null
        }
    ]
}

HTTP Request

GET api/v1/projects/hierarchy

Return a list containing all projects.


Requires authentication
See Display the specified project. for the returned attributes.

Example request:

curl -X GET -G "https://web.timingapp.com/api/v1/projects?title=root" \
    -H "Authorization: Bearer {{token}}"
const url = new URL("https://web.timingapp.com/api/v1/projects");

    let params = {
            "title": "root",
        };
    Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));

let headers = {
    "Authorization": "Bearer {{token}}",
    "Accept": "application/json",
    "Content-Type": "application/json",
}

fetch(url, {
    method: "GET",
    headers: headers,
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (200):

{
    "data": [
        {
            "self": "\/projects\/1",
            "title": "Project at root level",
            "title_chain": [
                "Project at root level"
            ],
            "color": "#FF0000",
            "productivity_score": 1,
            "is_archived": false,
            "children": [
                {
                    "self": "\/projects\/2"
                }
            ],
            "parent": null
        }
    ]
}

HTTP Request

GET api/v1/projects

Query Parameters

Parameter Status Description
title optional Filter for projects whose title contains all words in this parameter. The search is case-insensitive but diacritic-sensitive.

Create a new project.


Requires authentication
See Display the specified project. for the returned attributes.

Example request:

curl -X POST "https://web.timingapp.com/api/v1/projects" \
    -H "Authorization: Bearer {{token}}" \
    -H "Content-Type: application/json" \
    -d '{"title":"Acme Inc.","parent":"\/projects\/1","color":"#FF0000","productivity_score":1,"is_archived":false}'
const url = new URL("https://web.timingapp.com/api/v1/projects");

let headers = {
    "Authorization": "Bearer {{token}}",
    "Content-Type": "application/json",
    "Accept": "application/json",
}

let body = {
    "title": "Acme Inc.",
    "parent": "\/projects\/1",
    "color": "#FF0000",
    "productivity_score": 1,
    "is_archived": false
}

fetch(url, {
    method: "POST",
    headers: headers,
    body: body
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (201):

{
    "data": {
        "self": "\/projects\/2",
        "title": "Acme Inc.",
        "title_chain": [
            "Project at root level",
            "Acme Inc."
        ],
        "color": "#FF0000",
        "productivity_score": 1,
        "is_archived": false,
        "children": [],
        "parent": {
            "self": "\/projects\/1"
        }
    },
    "links": {
        "time-entries": "https:\/\/web.timingapp.com\/api\/v1\/time-entries?project[]=\/projects\/2"
    }
}

HTTP Request

POST api/v1/projects

Body Parameters

Parameter Type Status Description
title string required The project's title.
parent project optional A link to an existing project. The new project will be appended to the parent's children.
color color optional The project's color, in hexadecimal format (#RRGGBB). If omitted, a color with random hue, 70% saturation and 100% value will be used.
productivity_score float optional The project's productivity rating, between -1 (very unproductive) and 1 (very productive). Defaults to 1.
is_archived boolean optional Whether the project has been archived. Defaults to false.

Display the specified project.


Requires authentication
The following attributes will be returned:

Example request:

curl -X GET -G "https://web.timingapp.com/api/v1/projects/1" \
    -H "Authorization: Bearer {{token}}"
const url = new URL("https://web.timingapp.com/api/v1/projects/1");

let headers = {
    "Authorization": "Bearer {{token}}",
    "Accept": "application/json",
    "Content-Type": "application/json",
}

fetch(url, {
    method: "GET",
    headers: headers,
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (200):

{
    "data": {
        "self": "\/projects\/1",
        "title": "Project at root level",
        "title_chain": [
            "Project at root level"
        ],
        "color": "#FF0000",
        "productivity_score": 1,
        "is_archived": false,
        "children": [
            {
                "self": "\/projects\/2"
            }
        ],
        "parent": null
    },
    "links": {
        "time-entries": "https:\/\/web.timingapp.com\/api\/v1\/time-entries?project[]=\/projects\/1"
    }
}

HTTP Request

GET api/v1/projects/{project}

Update the specified project.


Requires authentication
See Display the specified project. for the returned attributes.

Example request:

curl -X PUT "https://web.timingapp.com/api/v1/projects/1" \
    -H "Authorization: Bearer {{token}}" \
    -H "Content-Type: application/json" \
    -d '{"title":"Acme Inc.","color":"#FF0000","productivity_score":1,"is_archived":false}'
const url = new URL("https://web.timingapp.com/api/v1/projects/1");

let headers = {
    "Authorization": "Bearer {{token}}",
    "Content-Type": "application/json",
    "Accept": "application/json",
}

let body = {
    "title": "Acme Inc.",
    "color": "#FF0000",
    "productivity_score": 1,
    "is_archived": false
}

fetch(url, {
    method: "PUT",
    headers: headers,
    body: body
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (200):

{
    "data": {
        "self": "\/projects\/1",
        "title": "Acme Inc.",
        "title_chain": [
            "Acme Inc."
        ],
        "color": "#FF0000",
        "productivity_score": 1,
        "is_archived": false,
        "children": [
            {
                "self": "\/projects\/2"
            }
        ],
        "parent": null
    },
    "links": {
        "time-entries": "https:\/\/web.timingapp.com\/api\/v1\/time-entries?project[]=\/projects\/1"
    }
}

HTTP Request

PUT api/v1/projects/{project}

PATCH api/v1/projects/{project}

Body Parameters

Parameter Type Status Description
title string optional The project's title.
color color optional The project's color, in hexadecimal format (#RRGGBB).
productivity_score float optional The project's productivity rating, between -1 (very unproductive) and 1 (very productive).
is_archived boolean optional Whether the project has been archived.

Delete the specified project and all of its children.


Requires authentication

Example request:

curl -X DELETE "https://web.timingapp.com/api/v1/projects/1" \
    -H "Authorization: Bearer {{token}}"
const url = new URL("https://web.timingapp.com/api/v1/projects/1");

let headers = {
    "Authorization": "Bearer {{token}}",
    "Accept": "application/json",
    "Content-Type": "application/json",
}

fetch(url, {
    method: "DELETE",
    headers: headers,
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (204):

null

HTTP Request

DELETE api/v1/projects/{project}

Tasks

Note that this collection is exposed through the time-entries endpoint. There is no tasks endpoint.

Start a new task.


Requires authentication
This also stops the currently running task if there is one.

See Display the specified task. for the returned attributes.

Example request:

curl -X POST "https://web.timingapp.com/api/v1/time-entries/start" \
    -H "Authorization: Bearer {{token}}" \
    -H "Content-Type: application/json" \
    -d '{"start_date":"2019-01-01T00:00:00+00:00","project":"Unproductive child project","title":"Client Meeting","notes":"Some more detailed notes"}'
const url = new URL("https://web.timingapp.com/api/v1/time-entries/start");

let headers = {
    "Authorization": "Bearer {{token}}",
    "Content-Type": "application/json",
    "Accept": "application/json",
}

let body = {
    "start_date": "2019-01-01T00:00:00+00:00",
    "project": "Unproductive child project",
    "title": "Client Meeting",
    "notes": "Some more detailed notes"
}

fetch(url, {
    method: "POST",
    headers: headers,
    body: body
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (201):

{
    "data": {
        "self": "\/time-entries\/2",
        "start_date": "2019-01-01T00:00:00.000000+00:00",
        "end_date": "2019-01-01T00:00:00.000000+00:00",
        "duration": 0,
        "project": {
            "self": "\/projects\/2"
        },
        "title": "Client Meeting",
        "notes": "Some more detailed notes",
        "is_running": true
    },
    "message": "Task 'Client Meeting' started."
}

HTTP Request

POST api/v1/time-entries/start

Body Parameters

Parameter Type Status Description
start_date date optional The date this task should have started at. Defaults to "now". Example:
project project optional The project this task is associated with. Can be a project reference in the form "/projects/1", a project title (e.g. "Project at root level"), or an array with the project's entire title chain (e.g. ["Project at root level", "Unproductive child project"]).
title string optional The task's title.
notes string optional The task's notes.

Stop the currently running task.


Requires authentication
Returns the stopped task's attributes as listed under Display the specified task..

Example request:

curl -X PUT "https://web.timingapp.com/api/v1/time-entries/stop" \
    -H "Authorization: Bearer {{token}}"
const url = new URL("https://web.timingapp.com/api/v1/time-entries/stop");

let headers = {
    "Authorization": "Bearer {{token}}",
    "Accept": "application/json",
    "Content-Type": "application/json",
}

fetch(url, {
    method: "PUT",
    headers: headers,
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (200):

{
    "data": {
        "self": "\/time-entries\/1",
        "start_date": "2019-01-01T00:00:00.000000+00:00",
        "end_date": "2019-01-01T01:00:00.000000+00:00",
        "duration": 3600,
        "project": {
            "self": "\/projects\/1"
        },
        "title": "Client Meeting",
        "notes": "Some more detailed notes",
        "is_running": false
    },
    "message": "Task 'Client Meeting' stopped."
}

HTTP Request

PUT api/v1/time-entries/stop

Return a list of tasks.


Requires authentication
See Display the specified task. for the returned attributes.

Items are ordered descending by their start_date field.

Example request:

curl -X GET -G "https://web.timingapp.com/api/v1/time-entries?start_date_min=2019-01-01T00%3A00%3A00%2B00%3A00&start_date_max=2019-01-01T23%3A59%3A59%2B00%3A00&projects%5B%5D=%2Fprojects%2F1&search_query=meeting&is_running=false&include_project_data=true&include_child_projects=true" \
    -H "Authorization: Bearer {{token}}"
const url = new URL("https://web.timingapp.com/api/v1/time-entries");

    let params = {
            "start_date_min": "2019-01-01T00:00:00+00:00",
            "start_date_max": "2019-01-01T23:59:59+00:00",
            "projects[]": "/projects/1",
            "search_query": "meeting",
            "is_running": "false",
            "include_project_data": "true",
            "include_child_projects": "true",
        };
    Object.keys(params).forEach(key => url.searchParams.append(key, params[key]));

let headers = {
    "Authorization": "Bearer {{token}}",
    "Accept": "application/json",
    "Content-Type": "application/json",
}

fetch(url, {
    method: "GET",
    headers: headers,
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (200):

{
    "data": [
        {
            "self": "\/time-entries\/1",
            "start_date": "2019-01-01T00:00:00.000000+00:00",
            "end_date": "2019-01-01T01:00:00.000000+00:00",
            "duration": 3600,
            "project": {
                "self": "\/projects\/1",
                "title": "Project at root level",
                "title_chain": [
                    "Project at root level"
                ],
                "color": "#FF0000",
                "productivity_score": 1,
                "is_archived": false,
                "parent": null
            },
            "title": "Client Meeting",
            "notes": "Some more detailed notes",
            "is_running": false
        }
    ],
    "links": {
        "first": "https:\/\/web.timingapp.com\/api\/v1\/time-entries?page=1",
        "last": "https:\/\/web.timingapp.com\/api\/v1\/time-entries?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "path": "https:\/\/web.timingapp.com\/api\/v1\/time-entries",
        "per_page": 1000,
        "to": 1,
        "total": 1
    }
}

HTTP Request

GET api/v1/time-entries

Query Parameters

Parameter Status Description
start_date_min optional Restricts the query to tasks whose start date is equal to or later than this parameter.
start_date_max optional Restricts the query to tasks whose start date is equal to or earlier than this parameter.
projects[] optional Restricts the query to tasks associated with the given project. Can be repeated to include tasks from several projects.
search_query optional Restricts the query to tasks whose title and/or notes contain all words in this parameter. The search is case-insensitive but diacritic-sensitive.
is_running optional If provided, returns only tasks that are either running or not running.
include_project_data optional If true, the properties of the task's project will be included in the response.
include_child_projects optional If true, the response will also contain tasks that belong to any child projects of the ones provided in projects[].

Create a new task.


Requires authentication
See Display the specified task. for the returned attributes.

Example request:

curl -X POST "https://web.timingapp.com/api/v1/time-entries" \
    -H "Authorization: Bearer {{token}}" \
    -H "Content-Type: application/json" \
    -d '{"start_date":"2019-01-01T00:00:00+00:00","end_date":"2019-01-01T01:00:00+00:00","project":"Unproductive child project","title":"Client Meeting","notes":"Some more detailed notes"}'
const url = new URL("https://web.timingapp.com/api/v1/time-entries");

let headers = {
    "Authorization": "Bearer {{token}}",
    "Content-Type": "application/json",
    "Accept": "application/json",
}

let body = {
    "start_date": "2019-01-01T00:00:00+00:00",
    "end_date": "2019-01-01T01:00:00+00:00",
    "project": "Unproductive child project",
    "title": "Client Meeting",
    "notes": "Some more detailed notes"
}

fetch(url, {
    method: "POST",
    headers: headers,
    body: body
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (201):

{
    "data": {
        "self": "\/time-entries\/2",
        "start_date": "2019-01-01T00:00:00.000000+00:00",
        "end_date": "2019-01-01T01:00:00.000000+00:00",
        "duration": 3600,
        "project": {
            "self": "\/projects\/2"
        },
        "title": "Client Meeting",
        "notes": "Some more detailed notes",
        "is_running": false
    }
}

HTTP Request

POST api/v1/time-entries

Body Parameters

Parameter Type Status Description
start_date date required The task's start date and time.
end_date date required The task's end date and time.
project project optional The project this task is associated with. Can be a project reference in the form "/projects/1", a project title (e.g. "Project at root level"), or an array with the project's entire title chain (e.g. ["Project at root level", "Unproductive child project"]).
title string optional The task's title.
notes string optional The task's notes.

Display the specified task.


Requires authentication
The following attributes will be returned:

Example request:

curl -X GET -G "https://web.timingapp.com/api/v1/time-entries/1" \
    -H "Authorization: Bearer {{token}}"
const url = new URL("https://web.timingapp.com/api/v1/time-entries/1");

let headers = {
    "Authorization": "Bearer {{token}}",
    "Accept": "application/json",
    "Content-Type": "application/json",
}

fetch(url, {
    method: "GET",
    headers: headers,
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (200):

{
    "data": {
        "self": "\/time-entries\/1",
        "start_date": "2019-01-01T00:00:00.000000+00:00",
        "end_date": "2019-01-01T01:00:00.000000+00:00",
        "duration": 3600,
        "project": {
            "self": "\/projects\/1"
        },
        "title": "Client Meeting",
        "notes": "Some more detailed notes",
        "is_running": false
    }
}

HTTP Request

GET api/v1/time-entries/{activity}

Update the specified task.


Requires authentication
See Display the specified task. for the returned attributes.

Example request:

curl -X PUT "https://web.timingapp.com/api/v1/time-entries/1" \
    -H "Authorization: Bearer {{token}}" \
    -H "Content-Type: application/json" \
    -d '{"start_date":"2019-01-01T00:00:00+00:00","end_date":"2019-01-01T01:00:00+00:00","project":"Unproductive child project","title":"Client Meeting","notes":"Some more detailed notes"}'
const url = new URL("https://web.timingapp.com/api/v1/time-entries/1");

let headers = {
    "Authorization": "Bearer {{token}}",
    "Content-Type": "application/json",
    "Accept": "application/json",
}

let body = {
    "start_date": "2019-01-01T00:00:00+00:00",
    "end_date": "2019-01-01T01:00:00+00:00",
    "project": "Unproductive child project",
    "title": "Client Meeting",
    "notes": "Some more detailed notes"
}

fetch(url, {
    method: "PUT",
    headers: headers,
    body: body
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (200):

{
    "data": {
        "self": "\/time-entries\/1",
        "start_date": "2019-01-01T00:00:00.000000+00:00",
        "end_date": "2019-01-01T01:00:00.000000+00:00",
        "duration": 3600,
        "project": {
            "self": "\/projects\/2"
        },
        "title": "Client Meeting",
        "notes": "Some more detailed notes",
        "is_running": false
    }
}

HTTP Request

PUT api/v1/time-entries/{activity}

PATCH api/v1/time-entries/{activity}

Body Parameters

Parameter Type Status Description
start_date date optional The task's start date and time.
end_date date optional The task's start date and time.
project project optional The project this task is associated with. Can be a project reference in the form "/projects/1", a project title (e.g. "Project at root level"), or an array with the project's entire title chain (e.g. ["Project at root level", "Unproductive child project"]).
title string optional The task's title.
notes string optional The task's notes.

Delete the specified task.


Requires authentication

Example request:

curl -X DELETE "https://web.timingapp.com/api/v1/time-entries/1" \
    -H "Authorization: Bearer {{token}}"
const url = new URL("https://web.timingapp.com/api/v1/time-entries/1");

let headers = {
    "Authorization": "Bearer {{token}}",
    "Accept": "application/json",
    "Content-Type": "application/json",
}

fetch(url, {
    method: "DELETE",
    headers: headers,
})
    .then(response => response.json())
    .then(json => console.log(json));

Example response (204):

null

HTTP Request

DELETE api/v1/time-entries/{activity}