zeiten.io API

Programs can read the entries of a tracker as JSON, for example to attach a timesheet to an invoice. The API has one request and needs no account: the address of a tracker gives access to it, as its page does.

Request

GET https://new.zeiten.io/api/trackers/ID/timesheet

Replace ID with the last part of the tracker's address, for example AbCdE for https://new.zeiten.io/AbCdE. The path may end with a slash, and zeiten answers HEAD with the same status and without a body. All parameters are optional and go into the query:

ParameterMeaning
start The first day of the period, as a date or an expression. Without it, the period has no start.
end The last day of the period, in the same forms. Without it, the period has no end.
tag Selects the entries that carry this tag. zeiten compares whole tags and ignores case and the spaces around the value: work selects an entry tagged Work and not one tagged homework.
comment Selects the entries whose comment contains this text, ignoring case.
tz The time zone that gives today's date for expressions, such as Europe/Zurich. Without it, or when it is empty, today is the date in UTC.

The parameters narrow the selection together, and zeiten ignores other parameters. This request asks for the entries of September 2026 with the tag SynergyPlus:

https://new.zeiten.io/api/trackers/ID/timesheet?start=2026-09-01&end=2026-09-30&tag=SynergyPlus

Dates and expressions

start and end take the same forms as a custom filter. An expression counts from today in the time zone tz. zeiten ignores the case of the letters and the spaces around the value. The examples assume that today is Wednesday, 2026-09-16.

FormMeaningExample
2026-09-01 The date itself, from year 1 to 9999.
-6d, +2w, -1m, +1y Today, moved by days, weeks, months or years. A number without sign moves forward. A month keeps the day, or takes the last day of a shorter month. -6d is 2026-09-10, +2w is 2026-09-30.
ws, we, ms, me, ys, ye The first (s) or last (e) day of the week, month or year of today. A week runs from Monday to Sunday. A sign and one digit move by whole weeks, months or years. ws is 2026-09-14, -1ms is 2026-08-01, -1me is 2026-08-31.

The filters of the menu use these pairs: Today -0d to +0d, Last 7 days -6d to +0d, Last month -1ms to -1me, This week +0ws to +0we, This month +0ms to +0me and This year +0ys to +0ye.

Answer

zeiten answers with status 200 and a JSON document in UTF-8:

{
  "tracker": {"id": "AbCdE", "title": "Roche 2026"},
  "start": "2026-09-01",
  "end": "2026-09-30",
  "total_seconds": 55800,
  "entries": [
    {
      "id": 27579,
      "day": "2026-09-29",
      "duration": "8:00 - 12:00, 13:00 - 16:30",
      "duration_seconds": 27000,
      "tags": ["SynergyPlus"],
      "comment": "- Solution train sync"
    },
    {
      "id": 27583,
      "day": "2026-09-30",
      "duration": "8h",
      "duration_seconds": 28800,
      "tags": ["SynergyPlus", "PDT"],
      "comment": "- Order checks\n- PDT working slot"
    }
  ]
}
FieldMeaning
tracker.id The ID from the address.
tracker.title The title of the tracker, empty for an untitled tracker.
start, end The first and the last day of the period as YYYY-MM-DD, which shows the dates of an expression, or null without the parameter.
total_seconds The sum of duration_seconds of the listed entries.
entries The selected entries, from the oldest day to the newest, and within a day in the order they were saved.
id A number that identifies the entry.
day The date of the entry as YYYY-MM-DD.
duration The normalized duration, such as 8h or 8:00 - 12:00, 13:00 - 17:00.
duration_seconds The length of the entry in seconds. Programs compute with this field.
tags The tags of the entry in the order the user typed them, each once.
comment The comment as plain text, which may contain line breaks.

Errors

StatusReasonBody
400 A parameter is invalid: a date or an expression that zeiten cannot read, an unknown time zone, or a list such as tag[]=a in place of a text. The messages per parameter, see below.
400 The query is not valid UTF-8. No JSON.
404 No tracker has this ID. {"errors": {"detail": "Not Found"}}
406 The request does not accept JSON: its Accept header names other types only. No JSON.
429 This client sent more than 20 requests to the API in the current minute. The header Retry-After gives the seconds until the minute ends. No JSON.
503 All clients together sent more than 100 requests to the API in the current minute. The header Retry-After gives the seconds until the minute ends. No JSON.

An answer with status 400 names each invalid parameter with a list of messages:

{
  "errors": {
    "start": ["use a date like 2026-09-27 or an expression like -6d or +0ws"],
    "tz": ["use a time zone like Europe/Zurich"],
    "tag": ["is invalid"]
  }
}

start and end get the first message, tz the second, and a list in place of a text gets is invalid.

Access

  • The address of a tracker gives access to it. Anyone who knows it can read the entries through the API and read and change them on the page, so keep it private.
  • zeiten sets no cookies for the API and does not count the request as a visit.
  • zeiten sends no CORS headers, so scripts of other websites cannot read the answer in a browser. Call the API from a program or a server.
  • zeiten.io answers over HTTPS. This request reads last month in the time zone of Zurich:
curl 'https://new.zeiten.io/api/trackers/ID/timesheet?start=-1ms&end=-1me&tz=Europe/Zurich'