# Timjami API > Multi-tenant time tracking. 417 operations under `https://timjami.app/api/v1`, API version 0.1.0. Generated from this server's own OpenAPI document, so it describes the deployment that served it rather than a build somebody remembered to run. ## Documentation - [Full reference](https://timjami.app/docs/llms-full.txt): every operation, parameter, body and schema, as markdown. - [Domain API OpenAPI document](https://timjami.app/api/v1/openapi.json): the machine contract for time tracking and business data under `/api/v1`. - [Auth & organizations OpenAPI document](https://timjami.app/api/auth/open-api/generate-schema): Better Auth's sign-in, accounts, sessions, users and organization membership under `/api/auth`. - [HTML reference](https://timjami.app/docs): the same content, rendered. - [Interactive console](https://timjami.app/docs/reference): try a request. ## Getting started Timjami tracks time for more than one organization at once, and everything below happens inside exactly one of them: the one your credential carries. There is no organization parameter anywhere in this API, and that is a deliberate rule rather than an omission — a request that could name its own tenant is a request that can name somebody else's. ### In one minute Mint a personal access token in the web app, at **Verbindungen → API-Keys**, tick the scopes you need, and call the API with it: ```bash curl -s https://timjami.app/api/v1/projects \ -H 'Authorization: Bearer thyp_…' ``` ```jsonc { "data": [ { "id": "7f3f1d0e-6d1a-4f5e-9b1a-2f8c1a4b6d33", "name": "Website-Relaunch", "customerId": "…", "rules": [{ "taskId": null, "personId": null, "validFromMonth": "2026-01", "hourlyRateMinor": 9900 }] } ] } ``` That token acts **as you**, in **one organization**, and may do exactly what you ticked. It is shown once; only a hash is stored. A lost token is replaced, never recovered. ### Then Recording an hour takes three ids, and they nest: a **customer** has **projects**, a project has **tasks**, and a time entry hangs off a task. So the shortest useful path through the API is `GET /tasks` → `POST /time-entries/start` → `POST /time-entries/{id}/stop`. If you are recording time that already happened, skip the timer and `POST /time-entries` with `startedAt` and `endedAt` — it is the same row either way, and only one entry per person may be running at a time. ### The one thing that will surprise you A time entry stores **no money at all**. `isBillable`, `billableRateMinor`, `currency` and `rateAxes` are on every entry you read, and every one of them is *computed* for that response: billability from the task, the rate from the project's rules for the month the work falls in, the currency from the organization. `rateAxes` says which axes the rule that decided constrains — `[]` is the project's default rule, `null` means nothing priced these hours. So repricing a project **does** change what already-recorded hours are worth. That is the point rather than a hazard — a rate is dated, so "from September we charge more" is entered in July and prices September, and correcting July's rules corrects July. Treat any amount you read as provisional. What cannot change is a **document**. Creating an invoice freezes what it bills: its lines keep their own quantity, unit rate and amount, and each claim records which seconds of which entry it billed and at what rate. Nothing recomputes either, ever. If you need the number somebody was actually charged, read the invoice — not the entries behind it. Two consequences worth knowing before you write against this: - An entry may be **partly** billed. Extend one after it was invoiced and the new time is billable on its own; `GET /invoices/unbilled` counts seconds, not entries. - `PATCH /time-entries/{id}` answers **409** if you move the times or the task of an entry an invoice bills. The note stays editable. A token never gets an exception to this, whatever role its owner holds. ## Authentication Timjami publishes two API contracts from the server you are reading: - [Domain API OpenAPI document](https://timjami.app/api/v1/openapi.json) — time tracking, projects, customers, budgets, invoices, reports and integrations under `/api/v1`. - [Auth & organizations OpenAPI document](https://timjami.app/api/auth/open-api/generate-schema) — Better Auth's sign-in, account, session, invitation and organization-membership endpoints under `/api/auth`. The second document is generated by the installed Better Auth version, including its enabled plugins. It is the canonical reference for those endpoints; the paths inside it are relative to its `/api/auth` server URL. For example, its `GET /organization/list-members` operation is called as `GET /api/auth/organization/list-members`. It lists the members of one organization, not every user on the deployment, and requires the caller to have a signed-in session and to belong to that organization. Three credentials reach the domain API, and each operation below says which of them it accepts. | You are | Use | Get one | |---|---|---| | a script, a cron job, a server you control | a **personal access token** | mint it yourself — no OAuth round trip | | an application other people connect to *their* Timjami | **OAuth 2.1**, authorization code + PKCE | each person approves it and picks an organization | | Timjami's own web, desktop or CLI client | a **session** | not for third parties | Both scoped credentials are **user-owned**. They act as the person who created or approved them, and they stop working the moment that person leaves the organization. There is no organization-owned service account. ### A personal access token `POST /api/v1/access-tokens` with a name and a list of scopes, or the same thing in the web app. Four things to know: 1. **It is shown once.** Only a hash is stored. 2. **It carries the organization that was active when you minted it.** Not a parameter, not switchable — mint a second token for a second organization. 3. **Send it as `Authorization: Bearer thyp_…` or `X-API-Key: thyp_…`.** Both work. The `thyp_` prefix is what makes it recognisable in a log or to a secret scanner. 4. **1000 requests an hour**, per token. Minting one needs a session. A token cannot mint another — the second would survive the first's revocation and there would be no chain to follow. ### OAuth 2.1 Timjami is the authorization server. Discovery is at the root, where RFC 8414 and RFC 9728 put it: ``` GET /.well-known/oauth-authorization-server GET /.well-known/oauth-protected-resource/api/v1 GET /.well-known/oauth-protected-resource/api/v1/mcp ``` PKCE is required. The authorization request always goes through Timjami's approval page, where the person chooses **which organization** the client may act in: an access token carries no organization of its own, so a client that skipped that page would hold a credential that resolves to nothing. ### Why the two are not equal An OAuth client here registered *itself*, unauthenticated, under a name it chose — that is what dynamic client registration is. So a token issued to one can hold only six scopes: `thymian:read`, `thymian:track`, `thymian:customers.write`, `thymian:budgets.write`, `thymian:hints.write` and `thymian:hints.read`. A personal access token can hold the whole ordinary catalogue, because you minted it yourself, in your own settings, from a list that said what each one does. If your integration needs invoices, project or task structure, organization settings, reports or budget maintenance, it needs a personal access token. An authorization request naming a scope outside the six is refused with `invalid_scope` before anybody is shown an approval page. `thymian:budgets.write` is the deliberately narrow exception among those OAuth scopes. Create, supersede and monthly adjustment set/remove also require a live, per-client switch under *Verbindungen → KI-Assistenten*, current organization membership and the current role's `budget.write` capability. The switch does not add a scope to an existing token. Correction, archive/restore and deletion are unavailable to OAuth even with the switch; sessions and personal tokens keep them. `thymian:customers.write` is the second narrow OAuth scope, and unusual in a different way: the four routes it names — create a customer, edit one, set or remove its logo — still also accept `thymian:projects.write`, the scope that governed them before this one existed. So a personal access token minted with `thymian:projects.write` still reaches them without being re-minted, and an OAuth client only needs `thymian:customers.write` when that is the whole of what it should be able to touch. Archiving a customer is not part of this scope and stays on `thymian:projects.write` alone. One route group is an exception worth knowing: `/api/v1/timer/*` names `thymian:timer`, which is reserved for the automatically provisioned Apple Watch key and cannot be requested by any OAuth client — but it also accepts `thymian:read` **and** `thymian:track` held together, since that pair already grants strictly more than `thymian:timer` does. An OAuth client holding both reaches these routes without ever holding the Watch-only scope. ## Scopes One catalogue, whatever the credential. Every operation in the reference below names the scope it needs, and the table is read from the same constant the server enforces against — so a scope documented here is a scope something actually checks. **A scope never widens a role.** A credential's authority is its scopes *intersected* with what its owner may already do. `thymian:reports.read` on a plain member's token still answers `403`, because the report is owner and admin only. Scopes narrow; roles decide. **A scope is not a way to reach everything.** Some operations accept a session and nothing else, whatever scopes a token holds — minting a credential, approving an OAuth client, connecting Slack, Google Calendar or Harvest, importing from Harvest, exporting time entries to it, and changing your own preferences. Each of those hands out access or changes what you have agreed to, and that stays something a person does while signed in. They are marked **session only** below. **`thymian:hints.read` needs a second yes — but only from an OAuth client.** A hint stream is prompts, branch names and message text, and the scope was agreed to in the middle of connecting somebody else's application. So a connected client also needs *Verbindungen → KI-Assistenten → Assistenten dürfen meine Hints lesen* switched on, read fresh on every request. A personal access token is not in that situation, so the switch does not apply to it. Either way, hints are only ever your own. **`thymian:budgets.write` needs a live per-client grant over OAuth.** Scope, current membership, the current role's `budget.write` capability and the switch under *Verbindungen → KI-Assistenten* are checked on every ordinary budget write. Turning the switch on never adds a missing scope to the token. It permits create, supersede and monthly adjustment set/remove; correction, archive/restore and delete remain unavailable to OAuth and therefore are documented without the OAuth security scheme. Sessions and personal access tokens keep their existing access. | Scope | Grants | Operations | Available over OAuth | |---|---|---|---| | `thymian:read` | Read the organization’s customers, projects, tasks, budgets and recorded time — everybody’s hours, not only yours — and its settings. | 48 | yes | | `thymian:track` | Start and stop your timer and manage your own breaks. Record, correct and delete time entries in the organization, a colleague’s included, as every member may. Record absences for yourself — or for anybody, if you administer the organization. | 20 | yes | | `thymian:timer` | Read and operate only your current timer, your completed time today and your five most recently used tasks. | 11 | no | | `thymian:master-data.read` | Read the organization’s customers, projects and tasks. No time, budget, rate or member. | 0 | yes | | `thymian:master-data.write` | Create and edit customers, projects and tasks — names, codes, colours, logos, a project’s visibility, a task’s category, and a new task’s billability. Cannot archive, set rates, change members, or change an existing task’s billability. | 0 | yes | | `thymian:project-members.read` | Read who is assigned to a project. | 0 | yes | | `thymian:project-members.write` | Assign people to projects and remove them, as far as you may. | 0 | yes | | `thymian:project-rates.read` | Read a project’s rate rules. | 0 | yes | | `thymian:project-rates.write` | Set a project’s rate rules and whether a task is billable. Never for hours an invoice already bills. | 0 | yes | | `thymian:time.read-own` | Read your own time entries, your running timer and your totals. Nobody else’s. | 0 | yes | | `thymian:time.write-own` | Start and stop your timer, record time and correct your own entries. Nobody else’s. | 0 | yes | | `thymian:time.read-others` | Read colleagues’ time entries as the organization’s time list shows them to you; a named person only if you administer the organization. | 0 | yes | | `thymian:budgets.read` | Read budgets and how many hours they have used. No money. | 0 | yes | | `thymian:invoice-drafts.write` | Build an invoice draft from tracked time and agreed work, and delete a draft. The answer shows the draft’s amounts even with money reading off, so building and deleting a draft reads them. Nothing is sent, exported or transferred: a person does that. Only works once billing writes are switched on for this client. | 0 | yes | | `thymian:time-allowances.read` | Read monthly allowances („Pauschalen“) and their monthly adjustments, in hours. No money. | 0 | yes | | `thymian:time-allowances.write` | Create, replace, correct and delete monthly allowances („Pauschalen“) and their monthly adjustments, which decide the hours a customer is billed. Never a month a live invoice holds. Only works once billing writes are switched on for this client. | 0 | yes | | `thymian:fixed-price.read` | Read fixed-price agreements and items, including their amounts. Only works once money reading is switched on for this client. | 0 | yes | | `thymian:fixed-price.write` | Create, change and delete fixed-price agreements and items, and link a task to an agreement, which decide what a customer is billed. The answer shows the amounts the call wrote. Never an item an invoice already bills. Only works once billing writes are switched on for this client. | 0 | yes | | `thymian:projects.write` | Create and change customers, projects and tasks, including archiving them. | 15 | yes | | `thymian:customers.write` | Create and edit customers — name, aliases, code, colour and logo. Cannot archive a customer, and cannot touch projects or tasks. | 5 | yes | | `thymian:project-icons.write` | Set or remove a project's logo. Cannot rename a project, touch its rate rules or members, or archive it. | 2 | yes | | `thymian:budgets.write` | Agree and supersede budgets, and set or remove monthly adjustments. Connected clients cannot correct, archive, restore or delete budgets. | 13 | yes | | `thymian:invoices.read` | Read invoices, their lines and amounts, write-offs, and what has not been billed yet. For a connected client this stays off until you also switch it on for that client in Timjami’s settings. | 14 | yes | | `thymian:invoices.write` | Build, issue, cancel and export invoices. | 16 | no | | `thymian:billing.read` | Read recurring billing rules and proposals — customer, period, recurrence, due date, state and affected projects. No amount, rate, or invoice. | 4 | yes | | `thymian:billing-proposals.write` | Confirm a billing proposal with nothing on it needs no invoice, or move a service between two open proposals of the same rule. Cannot create, pause or resume a rule, or touch an invoice. | 2 | yes | | `thymian:billing-rules.write` | Create a recurring billing rule, or change its schedule or which projects it covers from a future period. Cannot pause or resume a rule, move a proposal’s service, or touch an invoice. | 3 | yes | | `thymian:invoice-status.read` | Read an invoice’s customer, period, status and transfer state — draft, transferred to the invoicing tool, or withdrawn. No amount, rate, currency or the invoicing tool’s document. | 1 | yes | | `thymian:organization.write` | Change the organization’s settings — currency, time zone, working hours — and set somebody’s working time model. | 8 | no | | `thymian:reports.read` | Read organization-wide aggregates over everybody’s hours, revenue included. Only works if you are an owner or admin; for a third-party client the revenue stays off until you also switch money reading on in Timjami’s settings. | 11 | yes | | `thymian:hints.write` | Report what it observed you working on. | 6 | yes | | `thymian:hints.read` | Read back what collectors observed about you — prompts, branches, message text. For a third-party client this stays off until you also switch it on in Timjami’s settings. | 5 | yes | | `thymian:absence-subjects.read` | Read the people absences can be recorded against — name, email and whether they are still active. No customers, projects, tasks, time or money. | 1 | no | | `thymian:persons.read` | Read the organization’s people — id, name, email and whether they are still active. No customers, projects, tasks, time or money. | 1 | yes | | `thymian:planning.read` | Read the organization’s staff plan, customer and project requirements, person experience, budget planning context, allocation notes, person names, weekly capacity, absence days (kinds only with absence details), tracked weekly totals and budget hours. Only works if you are an owner, admin, or hold the planning role. | 3 | yes | | `thymian:planning.write` | Create, replace or clear any person’s planned hours and note on a customer, budget or project for an ISO week, and planning context for customers, persons, projects and budgets. Only works if you are an owner, admin, or hold the planning role. Does not record time or change budgets. | 4 | yes | Operations that accept a session and no scoped credential at all: 203. Operations that need no domain scope beyond authenticating: 3. ## Conventions Six rules hold across every operation below. They are worth reading once, because each of them is a place where an integration written against a reasonable assumption goes quietly wrong. **Money is an integer in minor units.** `billableRateMinor: 8500` with `currency: "EUR"` is 85,00 €. Never a float, anywhere, in either direction. Rounding happens once, at the edge — and *which* edge differs on purpose: an invoice line rounds its own whole seconds once, while a day's on-screen total sums per-entry amounts. The two disagree by cents and both are correct for what they are. **Rates on the wire are always hourly.** A day rate is a display, computed from the organization's `hoursPerDay`. There is no daily rate stored anywhere and none accepted here. **Timestamps are ISO-8601 with an offset** — `2026-07-29T09:00:00.000Z` or `2026-07-29T11:00:00+02:00`, both fine. Calendar days are different: an absence's `fromDay` and a budget's `validFromMonth` are `YYYY-MM-DD` and `YYYY-MM` strings, because a calendar day has no offset and storing one as a timestamp moves it across a timezone boundary. **Lists are `{ "data": [...] }`**, and a paginated one adds `nextCursor`. Pass that value back unchanged as `cursor`. It names both the instant and the row, so entries sharing a `startedAt` are not skipped at a page boundary — which is not a rare case, because every entry backdated to "09:00" shares an instant to the microsecond. `nextCursor: null` means you have the whole set; a non-null one means you do **not**, and summing what you have is a wrong number that looks like a right one. **The organization is never a parameter.** It comes from the credential. Read `GET /me` to find out which one you are in. **Nothing is deleted quietly.** Archiving is the normal end of a customer, a project or a task; `includeArchived=true` brings them back into a list. Deleting is refused with `409` when something downstream depends on the row — an invoiced time entry, a budget with hours measured against it. ## Analytics export Timjami does not push anywhere and runs no scheduler. If you want valued hours in Elasticsearch — or in any other analytics store — you run a cron next to that store, pull from two endpoints, and write what they hand you. Timjami holds no credentials into your index, and the token you point at it is read-only. ### The unit One document is **one valued stretch of one finished time entry**: person, task, project, customer, budget, rate, amount, currency, and a `status`. Not one per entry — an entry whose first hours are billed by a transferred invoice while the rest is still open is *two* documents, and one straddling a budget's capacity boundary is two as well. Non-billable time is exported too, with no rate and no amount: the billable rate (the share of tracked time that was billable) needs it as its denominator. The id is `::` — the month, the entry, and the stretch's start offset in seconds within that entry. It guarantees the start, the month and uniqueness; it does **not** guarantee the end. A moved allocation boundary is a tombstone plus a new document rather than an update, because the old id described seconds nobody priced that way. ### Two endpoints ```bash # Everything in one month, complete. This is a first sync, a rebuilt index, # and the nightly full run — the same code path each time. curl -s 'https://timjami.app/api/v1/reports/projection?month=2026-07' \ -H 'Authorization: Bearer thyp_…' # Everything that changed since your last pull, tombstones included. curl -s 'https://timjami.app/api/v1/reports/projection/changes?since=2026-08-13T02:00:00.000Z' \ -H 'Authorization: Bearer thyp_…' ``` Both need a token with `thymian:reports.read`, held by somebody with the `owner` or `admin` role. That is not bureaucracy: those roles see private projects, and a token belonging to anybody else would produce an index quietly missing one of them. Both **recompute and store on the way through**. Every month is recomputed on every run, diffed against what was last delivered, and the new state persisted — a value here moves without its time entry being touched (a dated rate lands, a budget is corrected, a colleague's backdated hour shifts the allocation), so there is no "this month cannot have changed" shortcut, and asking is what makes the answer current. ### The loop ```jsonc // GET /api/v1/reports/projection/changes?since=… { "data": [ { "id": "2026-07:0f4d…:0", "month": "2026-07", "changedAt": "2026-08-14T02:00:03.412Z", "deleted": false, "document": { "id": "2026-07:0f4d…:0", "seconds": 3600, "amountMinor": 9000, "status": "final", /* … */ } }, { "id": "2026-06:1a7c…:0", "month": "2026-06", "changedAt": "…", "deleted": true, "document": null } ], "nextCursor": null, "nextSince": "2026-08-14T02:00:03.412Z", "fullResyncRecommended": false } ``` 1. Start from the full export, month by month, and keep its `syncedAt`. 2. From then on call `/changes` with `since` set to the stored stamp. 3. Page while `nextCursor` is not null, passing it back as `cursor` with the **same** `since`. When it is null the delta is complete. 4. Write your batch. **Then** store `nextSince` as your new cursor. Step 4 in that order is the whole reliability story. The cursor is inclusive and answers from stored documents rather than from the run's diff, so a job that dies mid-write asks again with the same `since` and is told the same thing. Every id is an upsert, so the repeat costs nothing. Advancing the cursor first is how a crash loses a document silently. ### `deleted` is not optional **A document with `deleted: true` means delete that id from your index.** It carries no body. Skip this branch and every hour ever deleted or re-dated in Timjami stays in your dashboards forever, looking exactly like an hour somebody worked. A re-dated entry arrives as precisely this pair: a tombstone in the old month, a document in the new one. Tombstones are kept for 30 days, which is therefore exactly how far back a `since` may reach. Ask with an older one and the answer carries `fullResyncRecommended: true` — call the full export instead of trusting that delta, because what disappeared while you were gone can no longer be named. ### More than one destination Nothing here is registered, so any number of jobs may pull. The cursor lives on your side: `/changes?since=T` is a function of `T` and the stored state — every stored document at or after `T`, not the diff of the run that happens to answer — so one job pulling does not consume anything another job has not fetched yet. Two destinations each keep their own `since` and neither notices the other. Three things are shared, and none of them couples the consumers. Each pull recomputes, so five jobs mean five recomputes of the same months (harmless — the valuation is deterministic, concurrent pulls converge — but it is work). The 30-day tombstone horizon is absolute rather than measured from anybody's last pull, so a fast consumer never shortens a slow one's window. And the state is a snapshot rather than a history: you always receive a document's *current* value, never the intermediate states you were offline for. That is the right shape for upserting into an index and the wrong shape for keeping a change log. ### A nightly full run Keep one, even though every pull already reconciles everything. It insures against the *consumer* side — a rebuilt or re-mapped index, a bug in your own write path — and it is the same request as a first sync, so it costs nothing to keep: ```bash #!/usr/bin/env bash set -euo pipefail month="$(date -u +%Y-%m)" cursor="" while :; do page=$(curl -sf -H "Authorization: Bearer $THYMIAN_TOKEN" \ "$THYMIAN/api/v1/reports/projection?month=$month&limit=1000${cursor:+&cursor=$cursor}") # One `{"index": …}` action line per document, then the document itself. jq -c '.data[] | {index: {_index: "thymian-hours", _id: .id}}, .' <<<"$page" \ | curl -sf -H 'Content-Type: application/x-ndjson' --data-binary @- \ "$ES/_bulk" > /dev/null cursor=$(jq -r '.nextCursor // empty' <<<"$page") [ -n "$cursor" ] || break done ``` The delta's write is the same, with the tombstones split off: ```bash jq -c '.data[] | select(.deleted | not) | {index: {_index: "thymian-hours", _id: .id}}, .document' <<<"$page" jq -c '.data[] | select(.deleted) | {delete: {_index: "thymian-hours", _id: .id}}' <<<"$page" ``` ### Reading the numbers `status` is the field every dashboard has to carry. `final` is a stretch a **transferred** invoice claims — money that has left Timjami for the invoicing tool and cannot change here any more. Everything else is `provisional`, including a draft invoice's hours, and it is provisional in the literal sense: it will move if somebody reprices the project or a colleague backdates an hour into the same budget. It is also **not monotonic**. Withdrawing an invoice releases its claims, and those seconds go back to `provisional` at a recomputed value. A dashboard that sums `final` and `provisional` money into one unlabelled number is stating something nobody can defend; facet on the field instead. Two more, both small and both the kind of thing that is found late: - `rateMinor: null` means nothing priced those seconds. It is never `0`. Billable hours with no rate are a real state and worth a panel of their own. - A document rounds over its own seconds, while an invoice line rounds once over its whole quantity. Summing `final` money therefore *approximates* what was invoiced, to within a minor unit per document. If you need the invoiced total, read the invoice. Finally, the projection is disposable at both ends. Timjami never reads its own sync state back for anything but this export, and your index can be dropped and rebuilt from the full export at any time. ## Errors Every error is an RFC 9457 problem document, served as `application/problem+json`: ```jsonc { "type": "https://timjami.suora.cloud/problems/forbidden", "title": "Forbidden", "status": 403, "detail": "This credential does not carry thymian:invoices.write.", "requiredScope": "thymian:invoices.write", "requestId": "7f3f1d0e-6d1a-4f5e-9b1a-2f8c1a4b6d33" } ``` `type` is stable and is the thing to branch on. `detail` is written for a human and may be reworded; `title` and `status` are what you would guess. Every response also carries the same value in an `X-Request-Id` header — quote it when reporting a problem, it is what correlates a support request with the API's own logs. `requestId` in the body is absent on the rare error a request never reaches this API's own error handler for (better-auth's `/api/auth/*` routes, most notably); the header is set regardless. ### What the codes mean here | Code | Means | Do | |---|---|---| | `400` `no-active-organization` | a session with no organization chosen — only reachable with a session | call Better Auth's `POST /api/auth/organization/set-active`; see the Auth & organizations OpenAPI document linked in the Authentication guide | | `400` `validation-error` | the body is not valid JSON | fix the JSON; `title` says it was malformed | | `401` | no credential, or one that is invalid, expired or revoked | authenticate; the `WWW-Authenticate` header names the metadata document | | `403` + `insufficient_scope` | the credential is fine, the scope is not | `requiredScope` in the body names it | | `403`, no challenge | a session-only operation, a role you do not hold, or no approved organization | `detail` says which | | `404` | not found, **or** found in another organization | the two are deliberately indistinguishable | | `409` | a real conflict: a second running timer, an invoiced entry you tried to delete, a budget with hours against it | read `detail`; retrying unchanged will not help | | `415` `validation-error` | a body sent without `Content-Type: application/json`, or a `Content-Type` this route does not read — even with no body | send JSON as `application/json`; where the body is optional, send no body and no `Content-Type` | | `422` | the request did not match the schema | `errors` lists the fields | | `429` | 1000 requests an hour, per token | back off | **A 401 is not a scope problem and a 403 is not an authentication problem.** A client that retries a 403 with the same credential gets the same answer forever. This is the single most common integration bug against this API. ### Two that are not errors `GET /time-entries/running` answers `404` when no timer is running. That is the normal answer to "is anything running", not a failure. `POST /time-entries/start` answers `409` when a timer is already running — including one started on another device, which is exactly why it is a conflict and not an overwrite. Send `stopRunning: true` to stop the old one and start the new one in a single request, but only after deciding that stopping it is what the person meant. ## Endpoints ### Me Current user, preferences, effective display settings. - `GET /api/v1/me` — Current user, preferences and effective display settings - `GET /api/v1/me/assistant-tours` — Where you stand in Jami’s guided tours - `PUT /api/v1/me/assistant-tours/{tour}` — Record where you stand in one of Jami’s tours - `PUT /api/v1/me/avatar` — Set your own avatar - `DELETE /api/v1/me/avatar` — Remove your own avatar - `POST /api/v1/me/google-provision` — Join the organization claimed by the signed-in Google Workspace domain - `DELETE /api/v1/me/impersonation-session` — End the current support session - `POST /api/v1/me/invite-code` — Redeem an invite code for a signed-in account without an organization - `PATCH /api/v1/me/preferences` — Update personal preferences - `GET /api/v1/me/sessions` — List every active session belonging to the current user - `DELETE /api/v1/me/sessions/{sessionId}` — Revoke one session belonging to the current user - `POST /api/v1/me/watch-credential` — Replace the current user’s Apple Watch credential - `DELETE /api/v1/me/watch-credential` — Revoke Apple Watch credentials ### Organization Settings of the active organization. - `GET /api/v1/organization-icons/{token}` — Serve an organization's icon - `PUT /api/v1/organization/billing-escalation` — Set the global billing-escalation recipient and delay - `GET /api/v1/organization/demo-data` — Get demo-data status for the active organization - `POST /api/v1/organization/demo-data` — Create demo data in the active organization - `DELETE /api/v1/organization/demo-data` — Remove demo data from the active organization - `GET /api/v1/organization/domain` — Google Workspace domain of the active organization - `PUT /api/v1/organization/domain` — Claim a Google Workspace domain - `PATCH /api/v1/organization/domain` — Enable or disable automatic Google membership - `DELETE /api/v1/organization/domain` — Remove the organization domain claim - `POST /api/v1/organization/domain/verify` — Verify the DNS challenge now - `GET /api/v1/organization/features` — Read the organization's own feature switches — `thymian:read` - `PATCH /api/v1/organization/features` — Switch the organization's own features on or off - `GET /api/v1/organization/features/absences/planning-readers` — Read who is concerned when absences are switched back on - `GET /api/v1/organization/icon` — Get the active organization's icon — `thymian:read` - `PUT /api/v1/organization/icon` — Set the active organization's icon — `thymian:organization.write` - `DELETE /api/v1/organization/icon` — Remove the active organization's icon — `thymian:organization.write` - `GET /api/v1/organization/login-policy` — Whether the active organization requires Google-only sign-in - `PATCH /api/v1/organization/login-policy` — Require or stop requiring Google-only sign-in for organization members - `GET /api/v1/organization/member-avatars` — Avatars of the active organization - `GET /api/v1/organization/member-google-accounts` — Which of the active organization’s members have a linked Google account - `GET /api/v1/organization/members` — The active organization’s roster - `GET /api/v1/organization/members/{personId}/daily-report` — Get a member's daily report subscription - `PATCH /api/v1/organization/members/{personId}/daily-report` — Update a member's daily report subscription - `GET /api/v1/organization/running-timers` — Who in the active organization is currently running a timer — `thymian:read` - `GET /api/v1/organization/settings` — Get settings of the active organization — `thymian:read` - `PATCH /api/v1/organization/settings` — Update settings of the active organization — `thymian:organization.write` - `PUT /api/v1/organization/tracking-window` — Set how far back the organization records time ### Customers Customers of the active organization. - `GET /api/v1/customers` — List customers — `thymian:read` - `POST /api/v1/customers` — Create a customer — `thymian:customers.write` - `GET /api/v1/customers/{id}` — Get a customer — `thymian:read` - `PATCH /api/v1/customers/{id}` — Update a customer — `thymian:customers.write` - `POST /api/v1/customers/{id}/archive` — Archive or restore a customer — `thymian:projects.write` - `PUT /api/v1/customers/{id}/icon` — Set a customer's icon — `thymian:customers.write` - `DELETE /api/v1/customers/{id}/icon` — Remove a customer's icon — `thymian:customers.write` - `POST /api/v1/customers/{id}/period` — Close a tracking period for every project of a customer - `GET /api/v1/customers/{id}/suggested-colors` — Suggest colours from a customer's icon — `thymian:read` - `POST /api/v1/customers/setup` — Set up a customer with its first project and billing - `GET /api/v1/customers/setup/templates/{projectId}` — Read what setting a customer up like a project fills in - `POST /api/v1/customers/suggested-colors` — Apply a suggested colour to many customers at once — `thymian:customers.write` ### Projects Projects, their billing rules and assigned members. - `GET /api/v1/projects` — List projects — `thymian:read` - `POST /api/v1/projects` — Create a project — `thymian:projects.write` - `GET /api/v1/projects/{id}` — Get a project — `thymian:read` - `PATCH /api/v1/projects/{id}` — Update a project — `thymian:projects.write` - `POST /api/v1/projects/{id}/archive` — Archive or restore a project — `thymian:projects.write` - `PUT /api/v1/projects/{id}/icon` — Set a project's icon — `thymian:project-icons.write` - `DELETE /api/v1/projects/{id}/icon` — Remove a project's icon — `thymian:project-icons.write` - `GET /api/v1/projects/{id}/members` — List project members — `thymian:read` - `PUT /api/v1/projects/{id}/members` — Assign a person to a project — `thymian:projects.write` - `DELETE /api/v1/projects/{id}/members/{personId}` — Remove a person from a project — `thymian:projects.write` - `POST /api/v1/projects/{id}/period` — Close a tracking period, or open one again - `GET /api/v1/projects/{id}/period-changes` — Who closed and opened this project’s periods — `thymian:read` - `GET /api/v1/projects/{id}/rate-rules` — List a project’s billing rules — `thymian:read` - `PUT /api/v1/projects/{id}/rate-rules` — Replace a project’s billing rules — `thymian:projects.write` - `POST /api/v1/projects/{id}/write-offs` — Write off this project’s open hours and allowance months — `thymian:invoices.write` - `POST /api/v1/projects/{id}/write-offs/preview` — What writing off this project’s open hours and allowance months would waive — `thymian:invoices.read` - `POST /api/v1/projects/setup` — Set up a project with its tasks, people and rates ### Tasks Tasks within a project. - `GET /api/v1/tasks` — List tasks — `thymian:read` - `POST /api/v1/tasks` — Create a task — `thymian:projects.write` - `GET /api/v1/tasks/{id}` — Get a task — `thymian:read` - `PATCH /api/v1/tasks/{id}` — Update a task — `thymian:projects.write` - `DELETE /api/v1/tasks/{id}` — Delete a task — `thymian:projects.write` - `POST /api/v1/tasks/{id}/archive` — Archive or restore a task — `thymian:projects.write` - `GET /api/v1/tasks/bookable` — List every task the caller may book, ordered by personal frecency — `thymian:read` - `GET /api/v1/tasks/recent` — List the tasks the caller books time on, most recent first — `thymian:read` - `GET /api/v1/tasks/suggested-names` — Suggest names for new tasks from the names already in use — `thymian:read` ### Icons Customer and project images, served from signed URLs so they can be used directly as an image source. - `GET /api/v1/icons/{token}` — Serve a customer or project icon ### Time entries Tracking time, including the running timer. - `GET /api/v1/time-entries` — List time entries — `thymian:read` - `POST /api/v1/time-entries` — Log a finished time entry — `thymian:track` - `GET /api/v1/time-entries/{id}` — Get a time entry — `thymian:read` - `PATCH /api/v1/time-entries/{id}` — Update a time entry — `thymian:track` - `DELETE /api/v1/time-entries/{id}` — Delete a time entry — `thymian:track` - `GET /api/v1/time-entries/{id}/references` — List the tickets attached to a time entry — `thymian:read` - `POST /api/v1/time-entries/{id}/references` — Attach a ticket to a time entry — `thymian:track` - `DELETE /api/v1/time-entries/{id}/references/{referenceId}` — Detach a ticket from a time entry — `thymian:track` - `GET /api/v1/time-entries/{id}/revisions` — Read a time entry's history - `POST /api/v1/time-entries/{id}/split` — Split a finished entry into consecutive parts — `thymian:track` - `POST /api/v1/time-entries/{id}/stop` — Stop a running timer — `thymian:track` - `POST /api/v1/time-entries/delete` — Delete several time entries — `thymian:track` - `GET /api/v1/time-entries/deleted` — Deleted entries in a window of days - `GET /api/v1/time-entries/export` — Download the filtered entries list as CSV — `thymian:read` - `POST /api/v1/time-entries/insert` — Insert a finished entry, cutting the entries it overlaps — `thymian:track` - `POST /api/v1/time-entries/insert-break` — Record a break, cutting the entries it overlaps — `thymian:track` - `GET /api/v1/time-entries/last-stopped` — Get the caller’s most recently stopped time entry — `thymian:read` - `POST /api/v1/time-entries/merge` — Merge time entries into one — `thymian:track` - `GET /api/v1/time-entries/policy` — What a booking or a move would be allowed to do — `thymian:read` - `GET /api/v1/time-entries/progress` — Tracked time against its target, and what it is worth — `thymian:read` - `GET /api/v1/time-entries/recent-notes` — Suggest notes for starting a timer — `thymian:read` - `POST /api/v1/time-entries/retask` — Move several time entries onto one task — `thymian:track` - `GET /api/v1/time-entries/revisions` — What changed in one month - `GET /api/v1/time-entries/running` — Get the running time entry — `thymian:read` - `POST /api/v1/time-entries/start` — Start a timer — `thymian:track` - `GET /api/v1/time-entries/summary` — Total tracked seconds in a window — `thymian:read` ### Invoices Billing a period of tracked time. An invoice groups entries that already carry their rate; it never resolves one, and an hour it claims cannot be billed twice. - `GET /api/v1/invoices` — List invoices — `thymian:invoices.read` - `POST /api/v1/invoices` — Create a draft invoice — `thymian:invoices.write` - `GET /api/v1/invoices/{id}` — Get an invoice with its lines — `thymian:invoices.read` - `PATCH /api/v1/invoices/{id}` — Change a draft’s notes — `thymian:invoices.write` - `DELETE /api/v1/invoices/{id}` — Delete a draft or a withdrawn invoice — `thymian:invoices.write` - `POST /api/v1/invoices/{id}/document-refresh` — Read this invoice’s document back from the invoicing tool, now — `thymian:invoices.read` - `POST /api/v1/invoices/{id}/export` — Export an invoice to the connected invoicing tool — `thymian:invoices.write` - `DELETE /api/v1/invoices/{id}/export` — Release an unclear export that created nothing — `thymian:invoices.write` - `PUT /api/v1/invoices/{id}/export/document` — Record the document found for an unclear export — `thymian:invoices.write` - `PUT /api/v1/invoices/{id}/recorded-document` — Record what is known about the document an invoice billed by hand became — `thymian:invoices.write` - `POST /api/v1/invoices/{id}/transfer` — Mark a draft as transferred to the invoicing tool — `thymian:invoices.write` - `POST /api/v1/invoices/{id}/withdraw` — Withdraw a transferred invoice — `thymian:invoices.write` - `GET /api/v1/invoices/allowance-divergences` — Billed allowance months whose agreement has since changed - `GET /api/v1/invoices/divergences` — Live claims that no longer match the time underneath them - `GET /api/v1/invoices/easybill-outstanding` — What Easybill still has outstanding — `thymian:invoices.read` - `GET /api/v1/invoices/externally-billed` — Hours another system has billed, grouped by its documents — `thymian:invoices.read` - `POST /api/v1/invoices/externally-billed/document` — Repair the invoice reference on hours another system billed - `POST /api/v1/invoices/externally-billed/release` — Take back an external settlement, freeing its hours again - `POST /api/v1/invoices/preview` — What an invoice for this selection would contain — `thymian:invoices.read` - `GET /api/v1/invoices/recorded-outstanding` — What the invoices billed by hand still have open — `thymian:invoices.read` - `GET /api/v1/invoices/status` — An invoice's status, customer, period and transfer state — no amount — `thymian:invoice-status.read` - `GET /api/v1/invoices/unbilled` — Hours that are finished, billable and on no live invoice — `thymian:invoices.read` - `GET /api/v1/invoicing-provider` — The connected invoicing tool ### Harvest Exporting your own tracked time to Harvest as time entries, using a personal connection you set up once. Re-exporting an entry updates the same Harvest entry rather than creating a second one. - `GET /api/v1/harvest` — This person's Harvest connection - `POST /api/v1/harvest` — Connect this person’s Harvest account - `DELETE /api/v1/harvest` — Disconnect this person’s Harvest account - `POST /api/v1/harvest/exports` — Export time entries to Harvest - `POST /api/v1/harvest/exports/adopt` — Correct time entries to what Harvest holds - `POST /api/v1/harvest/exports/compare` — Compare already-exported time entries against their live Harvest counterpart - `POST /api/v1/harvest/imports` — Import one person’s Harvest time entries for a day range - `DELETE /api/v1/harvest/links/{timeEntryId}` — Forget a time entry’s Harvest export link - `DELETE /api/v1/harvest/links/by-harvest-entry/{harvestTimeEntryId}` — Forget the link of a Harvest entry whose Timjami entry was deleted - `GET /api/v1/harvest/projects` — List Harvest projects, for the mapping picker - `GET /api/v1/harvest/projects/{harvestProjectId}/tasks` — List a Harvest project’s task assignments, for the mapping picker - `POST /api/v1/harvest/seconds` — Give a period’s linked entries the seconds Harvest publishes only as hours - `GET /api/v1/harvest/summary` — What Harvest itself says this period adds up to - `GET /api/v1/projects/{id}/harvest` — Read a project’s Harvest mapping — `thymian:read` - `PUT /api/v1/projects/{id}/harvest` — Point a project at a specific Harvest project — `thymian:projects.write` - `DELETE /api/v1/projects/{id}/harvest` — Forget a project’s Harvest mapping — `thymian:projects.write` - `POST /api/v1/projects/{id}/harvest/imports` — Import a project’s Harvest hours for every one of its current members - `PUT /api/v1/tasks/{id}/harvest` — Point a task at a specific Harvest project and task — `thymian:projects.write` - `DELETE /api/v1/tasks/{id}/harvest` — Forget a task’s Harvest mapping — `thymian:projects.write` ### Reports Aggregates over the organization, gated to owners and admins — unlike the time entries they are built from, which every member can already read. - `GET /api/v1/agent-usage` — Coding-agent token usage for a month or a day — `thymian:read` - `GET /api/v1/reports/company-metrics` — The weekly „Kennzahlen": two rates and revenue per FTE of sold capacity — `thymian:reports.read` - `GET /api/v1/reports/company-metrics/months` — The „Kennzahlen" by calendar month — `thymian:reports.read` - `GET /api/v1/reports/company-metrics/weeks` — The „Kennzahlen" by whole week — `thymian:reports.read` - `GET /api/v1/reports/dashboard` — The organization's live management dashboard — `thymian:reports.read` - `POST /api/v1/reports/dashboard/widgets` — The figures a dashboard's widgets ask for, in one read - `GET /api/v1/reports/hours-by-project` — Tracked hours by project in a window — `thymian:reports.read` - `GET /api/v1/reports/hours-by-user` — Every member's tracked hours in a window — `thymian:reports.read` - `GET /api/v1/reports/hours-over-time` — Hours over time on one project or customer — `thymian:read` - `GET /api/v1/reports/monthly-statistics/export` — Download hours and revenue by month, person, customer and project as CSV — `thymian:reports.read` - `GET /api/v1/reports/person-activity` — What one person worked on in a window — `thymian:read` - `GET /api/v1/reports/person-revenue` — One named person's provisional revenue over a window — `thymian:reports.read` - `GET /api/v1/reports/projection` — One month of the analytics projection, complete — `thymian:reports.read` - `GET /api/v1/reports/projection/changes` — Everything the analytics projection changed since your last pull — `thymian:reports.read` - `GET /api/v1/reports/projects-by-user` — Every member's project count — `thymian:reports.read` - `GET /api/v1/reports/tickets` — Tracked hours by external ticket — `thymian:read` - `GET /api/v1/reports/tickets/days` — Tracked hours by day and external ticket — `thymian:read` ### Hints What collectors observed you working on, offered as suggestions for time entries. Visible only to the person they belong to. - `GET /api/v1/hint-rules` — List your hint rules — `thymian:hints.read` - `PUT /api/v1/hint-rules` — Declare (or replace) what a subject or path means — `thymian:hints.write` - `DELETE /api/v1/hint-rules/{id}` — Delete a hint rule — `thymian:hints.write` - `GET /api/v1/hints` — List your hints — `thymian:hints.read` - `POST /api/v1/hints` — Report a hint — `thymian:hints.write` - `GET /api/v1/hints/{id}` — Get a hint — `thymian:hints.read` - `DELETE /api/v1/hints/{id}` — Delete a hint — `thymian:hints.write` - `POST /api/v1/hints/{id}/dismiss` — Dismiss a hint, or take that back — `thymian:hints.write` - `POST /api/v1/hints/{id}/link` — Record that a hint became a time entry — `thymian:hints.write` - `POST /api/v1/hints/evaluate` — Evaluate selected hints with the configured LLM - `GET /api/v1/hints/sources` — List the distinct sources across your hints — `thymian:hints.read` - `GET /api/v1/hints/subjects` — List the distinct (source, path) pairs in your hints — `thymian:hints.read` ### Collectors Getting a collector installed. The hook scripts are served here so a project that is not a Timjami checkout can fetch them. - `GET /api/v1/collectors/google-calendar` — Your Google Calendar connection - `PATCH /api/v1/collectors/google-calendar` — Which calendars are observed, and what may be recorded about them - `DELETE /api/v1/collectors/google-calendar` — Disconnect your Google account - `GET /api/v1/collectors/google-calendar/calendars` — The calendars you could observe - `POST /api/v1/collectors/google-calendar/connect` — Start connecting your Google account - `POST /api/v1/collectors/google-calendar/sync` — Read the calendar now - `GET /api/v1/collectors/keys` — List your collector keys - `POST /api/v1/collectors/keys` — Create a key for a collector - `DELETE /api/v1/collectors/keys/{id}` — Revoke a collector key - `GET /api/v1/collectors/slack` — The Slack connection, for this organization and for you - `DELETE /api/v1/collectors/slack` — Disconnect the workspace - `POST /api/v1/collectors/slack/connect` — Start connecting your own Slack account - `POST /api/v1/collectors/slack/huddle` — Start reporting when you are in a Slack Huddle - `DELETE /api/v1/collectors/slack/huddle` — Stop reporting when you are in a Slack Huddle - `PATCH /api/v1/collectors/slack/identity` — Whether your hints may carry what you wrote - `DELETE /api/v1/collectors/slack/identity` — Stop being observed - `POST /api/v1/collectors/slack/install` — Start connecting a Slack workspace - `POST /api/v1/collectors/slack/presence` — Start reporting when Slack shows you as active - `DELETE /api/v1/collectors/slack/presence` — Stop reporting when you are active in Slack - `POST /api/v1/collectors/slack/status` — Start showing a running timer in your Slack status - `PATCH /api/v1/collectors/slack/status` — How your status reads while a timer runs - `DELETE /api/v1/collectors/slack/status` — Stop showing timers in your Slack status - `GET /api/v1/collectors/usage-keys` — List your usage keys - `POST /api/v1/collectors/usage-keys` — Create a usage key for a coding agent - `DELETE /api/v1/collectors/usage-keys/{id}` — Revoke a usage key - `POST /api/v1/otlp/v1/logs` — Export coding-agent telemetry (OTLP/HTTP logs) ### Events Live notifications that something changed, so a client can refresh instead of polling. Signals only — the change itself is read through the endpoints above. - `GET /api/v1/events` — Live change notifications (Server-Sent Events) — `thymian:read` ### Access tokens Minting and revoking the scoped credentials of the public API. A session only — a token that could mint a token would survive its own revocation. - `GET /api/v1/access-tokens` — List your personal access tokens - `POST /api/v1/access-tokens` — Mint a personal access token - `DELETE /api/v1/access-tokens/{id}` — Revoke a personal access token ### MCP A Model Context Protocol server, so an assistant can read projects and record time for the person who connected it. Authenticated by OAuth 2.1 rather than by a session, and scoped to the one organization chosen when the client was approved. - `POST /api/v1/mcp` — Model Context Protocol endpoint (Streamable HTTP) - `GET /api/v1/mcp/consent` — Describe a pending MCP authorization request - `POST /api/v1/mcp/consent` — Approve or deny an MCP authorization request - `GET /api/v1/mcp/grants` — List your connected MCP clients - `PATCH /api/v1/mcp/grants/{id}` — Change a live switch of a connected client ### Absence Subjects - `GET /api/v1/absence-subjects` — List absence subjects — `thymian:absence-subjects.read` ### Absences - `GET /api/v1/absences` — List absences — `thymian:read` - `POST /api/v1/absences` — Record an absence — `thymian:track` - `PATCH /api/v1/absences/{id}` — Correct an absence — `thymian:track` - `DELETE /api/v1/absences/{id}` — Remove an absence — `thymian:track` - `GET /api/v1/absences/{id}/revisions` — Read an absence's history - `PUT /api/v1/absences/external/{source}/{externalId}` — Import an absence — `thymian:track` - `GET /api/v1/absences/revisions` — What changed in one month ### Access - `POST /api/v1/access-requests` — Request access to Timjami - `POST /api/v1/access/invite-codes/check` — Check whether an invite code is usable ### Admin - `GET /api/v1/admin/access-requests` — The access-request queue (operators only) - `POST /api/v1/admin/access-requests/{id}/approve` — Approve a pending access request (operators only) - `POST /api/v1/admin/access-requests/{id}/decline` — Decline a pending access request (operators only) - `GET /api/v1/admin/app-installations` — Recently reported app versions (Suora staff only) - `GET /api/v1/admin/audit-log` — Who opened the admin view, and what they read (Suora staff only) - `GET /api/v1/admin/invite-codes` — Every invite code, with its redemptions (operators only) - `POST /api/v1/admin/invite-codes` — Mint an invite code (operators only) - `POST /api/v1/admin/invite-codes/{id}/revoke` — Revoke an invite code (operators only) - `GET /api/v1/admin/organizations` — Every organization on this deployment (Suora staff only) - `GET /api/v1/admin/organizations/{id}` — One organization's detail (Suora staff only) - `DELETE /api/v1/admin/organizations/{id}` — Delete an organization without members (operators only) - `PUT /api/v1/admin/organizations/{id}/analytics-exclusion` — Leave one organization out of the platform analytics (operators only) - `DELETE /api/v1/admin/organizations/{id}/analytics-exclusion` — Count one organization in the platform analytics again (operators only) - `PUT /api/v1/admin/organizations/{id}/features/{feature}` — Switch a feature on for one organization (operators only) - `DELETE /api/v1/admin/organizations/{id}/features/{feature}` — Switch a feature off for one organization (operators only) - `DELETE /api/v1/admin/organizations/{id}/users/{userId}` — Delete an organization member’s login account (operators only) - `PUT /api/v1/admin/organizations/{id}/users/{userId}/features/{feature}` — Switch a feature on for one member of an organization (operators only) - `DELETE /api/v1/admin/organizations/{id}/users/{userId}/features/{feature}` — Switch a member’s own grant of a feature off (operators only) - `GET /api/v1/admin/releases` — Every product’s release/deploy status (Suora staff only) - `GET /api/v1/admin/usage` — Feature usage per organization (Suora staff only) - `POST /api/v1/admin/users/{userId}/impersonation-sessions` — Open a short-lived support session (Suora staff only) - `GET /api/v1/admin/users/{userId}/push-logs` — One person's recent push delivery logs (Suora staff only) - `GET /api/v1/admin/users/{userId}/push-tokens` — One person's currently registered push tokens (Suora staff only) ### Diagnostics - `POST /api/v1/app-errors` — Report a desktop, CLI or Raycast error - `POST /api/v1/client-errors` — Report a browser error ### App installations - `POST /api/v1/app-installations/check-in` — Report the running version of this app installation ### Authentication - `GET /api/v1/auth/providers` — Available interactive sign-in providers ### Avatars - `GET /api/v1/avatars/{token}` — Serve a person's avatar ### Billing proposals - `GET /api/v1/billing-proposals` — List billing proposals — `thymian:billing.read` - `GET /api/v1/billing-proposals/{id}` — One proposal, with its allocations — `thymian:billing.read` - `POST /api/v1/billing-proposals/{id}/allocations/{allocationId}/move` — Move or defer a service portion to another open proposal of the same rule — `thymian:billing-proposals.write` - `POST /api/v1/billing-proposals/{id}/dismiss` — Confirm an empty proposal needs no billing — `thymian:billing-proposals.write` - `POST /api/v1/billing-proposals/{id}/invoices` — Create the invoice for this proposal’s currently open allocations - `POST /api/v1/billing-proposals/{id}/invoices/preview` — Preview the invoice this proposal’s open allocations would produce - `GET /api/v1/billing-proposals/{id}/services` — The people, tasks, days and notes behind a proposal’s service portions ### Billing rules - `GET /api/v1/billing-rules` — List recurring billing rules — `thymian:billing.read` - `POST /api/v1/billing-rules` — Create a recurring billing rule — `thymian:billing-rules.write` - `GET /api/v1/billing-rules/{id}` — One billing rule — `thymian:billing.read` - `DELETE /api/v1/billing-rules/{id}` — Delete a rule that has not produced a proposal yet - `PUT /api/v1/billing-rules/{id}/coverage` — Change which projects a rule covers, from a future period — `thymian:billing-rules.write` - `POST /api/v1/billing-rules/{id}/pause` — Pause a rule - `GET /api/v1/billing-rules/{id}/project-addition/candidates` — List the projects that can still join the rule - `GET /api/v1/billing-rules/{id}/project-addition/preview` — Preview a project joining the rule from a past month - `POST /api/v1/billing-rules/{id}/project-additions` — Add a project to an active rule from a past, current or future month - `POST /api/v1/billing-rules/{id}/resume` — Resume a paused rule - `PATCH /api/v1/billing-rules/{id}/schedule` — Change a rule’s live fields — `thymian:billing-rules.write` - `GET /api/v1/billing-rules/preview-history` — Preview outstanding historical periods before a rule’s first period - `GET /api/v1/billing-rules/preview-periods` — Preview a rule-to-be’s periods from its first one on - `GET /api/v1/billing-rules/preview-planned` — Planned hours for a rule-to-be’s running and upcoming periods ### Break periods - `GET /api/v1/break-dismissals` — List your dismissed break questions — `thymian:read` - `POST /api/v1/break-dismissals` — Dismiss a break question — `thymian:track` - `GET /api/v1/break-periods` — List explicit breaks — `thymian:read` - `POST /api/v1/break-periods` — Record an explicit break — `thymian:track` - `DELETE /api/v1/break-periods/{id}` — Remove one of your breaks — `thymian:track` ### Budgets - `GET /api/v1/budget-report-charts/{token}` — Serve a budget report’s chart - `GET /api/v1/budgets` — List budgets — `thymian:read` - `POST /api/v1/budgets` — Agree a new budget — `thymian:budgets.write` - `GET /api/v1/budgets/{id}` — One budget — `thymian:read` - `PATCH /api/v1/budgets/{id}` — Correct a budget — `thymian:budgets.write` - `DELETE /api/v1/budgets/{id}` — Delete a budget nothing was measured against — `thymian:budgets.write` - `PUT /api/v1/budgets/{id}/adjustments/{month}` — Adjust one month of a budget — `thymian:budgets.write` - `DELETE /api/v1/budgets/{id}/adjustments/{month}` — Remove a month’s adjustment — `thymian:budgets.write` - `GET /api/v1/budgets/{id}/alerts` — One budget’s alert thresholds — `thymian:read` - `POST /api/v1/budgets/{id}/alerts` — Watch a percentage of a budget — `thymian:budgets.write` - `DELETE /api/v1/budgets/{id}/alerts/{alertId}` — Stop watching a percentage — `thymian:budgets.write` - `POST /api/v1/budgets/{id}/archive` — Archive or restore a budget — `thymian:budgets.write` - `GET /api/v1/budgets/{id}/reports` — One budget’s scheduled reports — `thymian:read` - `PUT /api/v1/budgets/{id}/reports/email` — Subscribe to a budget’s report by e-mail - `DELETE /api/v1/budgets/{id}/reports/email` — Unsubscribe from a budget’s e-mail report - `POST /api/v1/budgets/{id}/reports/slack` — Post a budget’s report to a Slack channel — `thymian:budgets.write` - `PATCH /api/v1/budgets/{id}/reports/slack/{subscriptionId}` — Reschedule a Slack report — `thymian:budgets.write` - `DELETE /api/v1/budgets/{id}/reports/slack/{subscriptionId}` — Stop posting a budget’s report to a channel — `thymian:budgets.write` - `GET /api/v1/budgets/{id}/trajectory` — Where one budget is heading, day by day — `thymian:read` - `GET /api/v1/budgets/alerts` — Every watched budget’s thresholds and what they recorded — `thymian:read` - `GET /api/v1/budgets/consumption` — What was agreed and what was tracked, per month — `thymian:read` - `GET /api/v1/budgets/report-channels` — Slack channels a budget report can be posted to — `thymian:budgets.write` - `POST /api/v1/budgets/supersede` — Replace budgets with the one agreed next — `thymian:budgets.write` - `GET /api/v1/budgets/trajectory` — Where each budget of the current month is heading — `thymian:read` ### Capacity agreements - `GET /api/v1/capacity-agreements` — Every stored capacity agreement version — `thymian:read` - `POST /api/v1/capacity-agreements` — Record a new capacity agreement version — `thymian:organization.write` - `PATCH /api/v1/capacity-agreements/{id}` — Correct one stored capacity agreement version — `thymian:organization.write` - `DELETE /api/v1/capacity-agreements/{id}` — Remove one capacity agreement version — `thymian:organization.write` ### Changelog - `GET /api/v1/changelog/{kind}` — A product's changelog - `GET /api/v1/changelog/{kind}/status` — Whether a product has unreleased changes ### Customer portal - `GET /api/v1/customer-portal/{organizationSlug}` — Get the invited customer portal overview - `GET /api/v1/customer-portal/{organizationSlug}/branding` — Get public customer portal branding - `GET /api/v1/customer-portal/{organizationSlug}/budgets` — Get the shared budgets of the invited customer - `GET /api/v1/organization/customer-portal` — Get customer portal settings and invitations - `PUT /api/v1/organization/customer-portal/branding` — Update customer portal branding - `PUT /api/v1/organization/customer-portal/budgets` — Choose budgets shared in the customer portal - `POST /api/v1/organization/customer-portal/invitations` — Invite a customer contact - `DELETE /api/v1/organization/customer-portal/invitations/{id}` — Revoke a customer portal invitation - `PUT /api/v1/organization/customer-portal/projects` — Choose projects visible in the customer portal ### Easybill - `PUT /api/v1/customers/{id}/easybill` — Point a customer at a specific Easybill customer — `thymian:invoices.write` - `DELETE /api/v1/customers/{id}/easybill` — Forget the Easybill mapping — `thymian:invoices.write` - `GET /api/v1/easybill` — The Easybill connection - `POST /api/v1/easybill` — Connect Easybill, or replace the key and service article - `DELETE /api/v1/easybill` — Disconnect Easybill - `GET /api/v1/easybill/customer-mappings` — Every customer’s Easybill mapping — `thymian:invoices.read` - `GET /api/v1/easybill/customers` — List Easybill customers, for the mapping picker - `PUT /api/v1/easybill/expense` — Choose the Easybill article a fixed-price expense references - `PUT /api/v1/easybill/service` — Choose the Easybill service article every exported line references - `GET /api/v1/easybill/services` — List Easybill service articles, for the settings picker ### sevdesk - `PUT /api/v1/customers/{id}/sevdesk` — Point a customer at a specific sevdesk contact - `DELETE /api/v1/customers/{id}/sevdesk` — Forget the sevdesk mapping — `thymian:invoices.write` - `GET /api/v1/sevdesk` — The sevdesk connection - `POST /api/v1/sevdesk` — Connect sevdesk, or replace the token - `DELETE /api/v1/sevdesk` — Disconnect sevdesk - `GET /api/v1/sevdesk/account` — What the sevdesk account says about its tax rule - `PUT /api/v1/sevdesk/contact-person` — Choose the sevdesk user printed as „Ihr Ansprechpartner“ - `GET /api/v1/sevdesk/contact-persons` — List the sevdesk users, for the contact-person picker - `GET /api/v1/sevdesk/contacts` — List sevdesk contacts, for the mapping picker - `GET /api/v1/sevdesk/customer-mappings` — Every customer’s sevdesk mapping — `thymian:invoices.read` - `PUT /api/v1/sevdesk/expense` — Choose the sevdesk part whose tax rate an expense is exported with - `GET /api/v1/sevdesk/parts` — List sevdesk parts, for the settings pickers - `PUT /api/v1/sevdesk/service` — Choose the sevdesk part whose tax rate time lines and services are exported with - `PUT /api/v1/sevdesk/tax-rule` — Choose the tax rule every export sends ### Dashboards - `GET /api/v1/dashboards` — Every saved dashboard this person may open - `POST /api/v1/dashboards` — Save a new dashboard - `GET /api/v1/dashboards/{dashboardId}` — One saved dashboard - `PATCH /api/v1/dashboards/{dashboardId}` — Change a dashboard you own - `DELETE /api/v1/dashboards/{dashboardId}` — Delete a dashboard you own ### Downloads - `GET /api/v1/downloads` — List client downloads - `GET /api/v1/downloads/{kind}` — Download a client - `POST /api/v1/downloads/cli-native/install-tickets` — Create one-time installer URLs for the native CLI - `GET /api/v1/downloads/cli-native/install/{ticket}` — Native CLI installer for Linux and macOS - `GET /api/v1/downloads/cli-native/install/{ticket}/{kind}` — Redeem a one-time native CLI download - `GET /api/v1/downloads/cli-native/install/{ticket}/powershell` — Native CLI installer for Windows - `POST /api/v1/downloads/cli/install-tickets` — Create a one-time CLI installer URL - `GET /api/v1/downloads/cli/install/{ticket}` — Native CLI installer at its former address - `GET /api/v1/downloads/desktop/updater-artifact/{tag}` — Download the desktop app updater artifact - `GET /api/v1/downloads/desktop/updater/{target}/{arch}/{currentVersion}` — Desktop app update check (Tauri dynamic update server) - `GET /api/v1/downloads/firefox-extension/updates.json` — Firefox extension update manifest - `GET /api/v1/downloads/firefox-extension/xpi/{tag}` — Download the signed Firefox extension ### Fixed-price work - `GET /api/v1/fixed-price-agreements` — List fixed-price agreements - `POST /api/v1/fixed-price-agreements` — Create a fixed-price agreement - `GET /api/v1/fixed-price-agreements/{id}` — Get a fixed-price agreement - `PATCH /api/v1/fixed-price-agreements/{id}` — Rename a fixed-price agreement - `DELETE /api/v1/fixed-price-agreements/{id}` — Delete a fixed-price agreement - `GET /api/v1/fixed-price-agreements/{id}/controlling` — Read a fixed-price agreement’s controlling - `GET /api/v1/fixed-price-agreements/{id}/revisions` — Read a fixed-price agreement’s history - `GET /api/v1/fixed-price-items` — List fixed-price items - `POST /api/v1/fixed-price-items` — Add a fixed-price item - `GET /api/v1/fixed-price-items/{id}` — Get a fixed-price item - `PATCH /api/v1/fixed-price-items/{id}` — Change a fixed-price item - `DELETE /api/v1/fixed-price-items/{id}` — Delete a fixed-price item - `GET /api/v1/fixed-price-items/revisions` — Read a project’s standalone expenses’ history ### Imports - `POST /api/v1/imports/harvest` — Import clients, projects, tasks and project members from Harvest - `POST /api/v1/imports/harvest/users` — Create people from Harvest’s own user list ### Operator security - `GET /api/v1/operator-security` — List your operator passkeys and approval status - `POST /api/v1/operator-security/authentication-options` — Request a one-use passkey confirmation for a specific operator action - `POST /api/v1/operator-security/passkeys` — Finish registering an operator passkey with required user verification - `DELETE /api/v1/operator-security/passkeys/{id}` — Remove your operator passkey after fresh confirmation; keeps the last approved key - `POST /api/v1/operator-security/registration-options` — Prepare an operator passkey; an existing approved key must authorize additional keys ### Personio - `GET /api/v1/personio-attendance-export` — Whether the Personio attendance export is active - `PUT /api/v1/personio-attendance-export` — Activate the Personio attendance export - `DELETE /api/v1/personio-attendance-export` — Deactivate the Personio attendance export - `GET /api/v1/personio-person-mappings` — Read Personio person assignments and email suggestions - `PUT /api/v1/personio-person-mappings/{id}` — Confirm, change or revoke one Personio person assignment ### Persons - `GET /api/v1/persons` — List persons — `thymian:persons.read` ### Planning - `PUT /api/v1/planning/allocations` — Plan one person’s hours for one customer, budget or project in one week — `thymian:planning.write` - `POST /api/v1/planning/allocations/refine` — Move hours between compatible planning targets — `thymian:planning.write` - `PUT /api/v1/planning/allocations/remaining` — Set or clear the expected remaining work for the current week — `thymian:planning.write` - `GET /api/v1/planning/context` — Read project requirements and people’s planning experience — `thymian:planning.read` - `PUT /api/v1/planning/context` — Replace planning context for a customer, person, project or budget — `thymian:planning.write` - `GET /api/v1/planning/forecast` — Read the budget forecast and unresolved planning work — `thymian:planning.read` - `GET /api/v1/planning/overview` — Read the staff plan — `thymian:planning.read` ### Search - `GET /api/v1/search` — Search customers, projects and tasks — `thymian:read` ### Time allowances - `GET /api/v1/time-allowances` — List monthly time allowances - `POST /api/v1/time-allowances` — Agree a monthly time allowance - `GET /api/v1/time-allowances/{id}` — One agreement - `PATCH /api/v1/time-allowances/{id}` — Correct an agreement - `DELETE /api/v1/time-allowances/{id}` — Delete an agreement nothing billed - `PUT /api/v1/time-allowances/{id}/adjustments/{month}` — Adjust one month of an agreement - `DELETE /api/v1/time-allowances/{id}/adjustments/{month}` — Remove a month’s adjustment - `POST /api/v1/time-allowances/supersede` — Replace agreements with the one agreed next ### Timer - `GET /api/v1/timer` — Get the caller’s own timer snapshot — `thymian:timer` - `GET /api/v1/timer/events` — Live timer change notifications for restricted clients — `thymian:timer` - `POST /api/v1/timer/forgotten-timer-push-tokens` — Opt this iPhone installation into forgotten-timer notifications — `thymian:timer` - `DELETE /api/v1/timer/forgotten-timer-push-tokens` — Opt this iPhone installation out of forgotten-timer notifications — `thymian:timer` - `POST /api/v1/timer/live-activity-start-token` — Register the caller’s own Live Activity push-to-start token — `thymian:timer` - `DELETE /api/v1/timer/live-activity-start-token` — Forget the caller’s own Live Activity push-to-start token — `thymian:timer` - `POST /api/v1/timer/live-activity-tokens` — Register one running Live Activity’s own update token — `thymian:timer` - `DELETE /api/v1/timer/live-activity-tokens/{activityId}` — Forget one running Live Activity’s own update token — `thymian:timer` - `POST /api/v1/timer/start` — Start or switch the caller’s own timer — `thymian:timer` - `POST /api/v1/timer/stop` — Stop the caller’s own timer — `thymian:timer` - `POST /api/v1/timer/widget-push-tokens` — Register a widget or complication kind for its own WidgetKit push channel — `thymian:timer` ### Work schedules - `GET /api/v1/work-schedules` — List working time model versions — `thymian:read` - `POST /api/v1/work-schedules` — Start a new working time model version — `thymian:organization.write` - `PATCH /api/v1/work-schedules/{id}` — Correct a working time model version — `thymian:organization.write` ### Write-offs - `GET /api/v1/write-offs` — List write-offs — `thymian:invoices.read` - `POST /api/v1/write-offs` — Write off hours that will never be billed — `thymian:invoices.write` - `POST /api/v1/write-offs/{id}/release` — Take a write-off back — `thymian:invoices.write` - `POST /api/v1/write-offs/allowance` — Write off whole monthly time allowances — `thymian:invoices.write` - `POST /api/v1/write-offs/allowance/preview` — What writing off these allowance months would waive — `thymian:invoices.read` - `POST /api/v1/write-offs/preview` — What writing off this selection would waive — `thymian:invoices.read` ## Undocumented, as of this request 1847 of 4364 things in this document have no prose on them, counted on this request. - **Operations with a summary and nothing else** — 56 of 417. The summary says what it is called. Without a description there is nowhere to say what it does to the data, or when not to call it. - **Groups with no description** — 30 of 45. A group heading with no sentence under it makes the reader open every operation in it to find out what the group is for. - **Parameters with no description** — 70 of 434. A type tells you a parameter takes a string. It does not tell you what happens when you omit it. - **Response and request fields with no description** — 1691 of 3468. The largest of the four and the least urgent: most are named well enough to guess. The ones that carry a rule — a frozen rate, a cursor, a month — are the ones worth writing.