Clockaroo API v1
Your own scripts and integrations can read clients, projects, tasks, time, weeks, budgets, reports and invoices, and they can log time and run the timer. A token acts as one person in one workspace, with that person's role. For AI assistants such as Claude or Cursor, see the MCP server.
OpenAPI file (JSON) Base URL https://clockaroo.com/api/v1, JSON only.
What it can and can't do
- Read: you, the team (owners), clients (owners), projects, tasks, time entries, deleted entries, the timer, weeks, budgets, the time report, invoices and their stored PDF and XML files (owners).
- Write: add, change and delete time entries, and start and stop your own timer. The same rules apply as in the app: invoiced time, approved weeks, archived projects and the Free plan's paused people can't change.
- It can't issue, send, change or delete invoices, approve weeks, or change clients, projects or settings. Those stay in the app.
- Members never see money through the API: rates, amounts, money budgets and invoices are left out of every answer, as in the app.
Authentication
Create a personal access token in the app under Profile, “API tokens”. You choose its scopes and when it expires (7, 30, 90 or 365 days). It is shown once. Send it in the Authorization header:
curl https://clockaroo.com/api/v1/me \ -H "Authorization: Bearer $CLOCKAROO_TOKEN"
Tokens start with clk_pat_ and end in a checksum, so typos are refused at once and secret scanners can find leaked ones. A token sent in a URL or a cookie is refused and revoked, because it is in a log somewhere by then. An owner can revoke any token in the workspace and can turn the API off for everyone in Settings, “API and AI assistants”.
| Scope | Allows |
|---|---|
time:read | Time entries, deleted entries, the timer and weeks. Owners read everyone's, members their own. |
time:write | Adding, changing and deleting time entries; starting and stopping the timer. Includes time:read. |
projects:read | Projects, tasks and budgets; the team and clients for owners. |
invoices:read | Invoices, credit notes and their files. Owners only. |
reports:read | The time report. Members get their own hours without money. |
Rate limits
| Bucket | Limit |
|---|---|
| Per token, all requests | 100 per 60 seconds |
| Per token, writes | 30 per 60 seconds and 2,000 per day |
| Per token, the time report and invoice files | 30 per 15 minutes |
| Per workspace, all tokens together | 300 per 60 seconds |
| Per IP, failed sign-ins with a token | 20 per 5 minutes, then 15 minutes of 429 |
Every answer carries RateLimit-Policy and RateLimit headers for the per-token bucket, in the syntax of the IETF draft. The draft may still change, so rely on Retry-After.
Pagination and sync
Lists answer with {"data": [...], "next_cursor": "...", "has_more": true} and a Link: rel="next" header. Pass limit (default 100, at most 500) and follow next_cursor with the same filters; a cursor used with other filters is refused. Time entries are ordered by spent_on and id, everything else by id. To sync, pass updated_since on any list and read GET /time_entries/deleted?since= for deletions (kept 90 days).
Idempotency
A retried “log 2 hours” must not log 4. POST /time_entries, POST /timer/start and DELETE /time_entries/{id} need an Idempotency-Key header (1 to 255 visible ASCII characters, for example a UUID); other writes accept one. Keys are kept 24 hours per token. The same key with the same request replays the first answer with Idempotent-Replayed: true. The same key with a different request is 422 idempotency_key_reused; while the first request still runs, a retry gets 409 idempotency_in_flight.
Errors
{ "error": { "code": "week_approved", "message": "This week is approved. Ask an owner to reopen it.",
"field": null, "request_id": "4f0c..." } }
The code is the contract; messages are English and may change. Treat a code you don't know by its HTTP status.
| Code | Status | Meaning |
|---|---|---|
invalid_parameter | 400 | A parameter is missing or not valid. |
invalid_json | 400 | The request body must be a JSON object. |
invalid_cursor | 400 | The cursor is not valid. Start again without it. |
cursor_mismatch | 400 | The cursor was made for other filters. Start again without it. |
idempotency_key_required | 400 | This request needs an Idempotency-Key header. |
token_in_url | 400 | Tokens are accepted only in the Authorization header. This token is now revoked because it was sent in a URL or cookie. |
token_missing | 401 | Send a token in the Authorization header: Bearer <token>. |
token_invalid | 401 | The token is not valid here. |
token_expired | 401 | The token has expired. Create a new one in your profile. |
token_revoked | 401 | The token was revoked. |
insufficient_scope | 403 | The token doesn't have the scope this request needs. |
owner_only | 403 | Only workspace owners can do this. |
api_disabled | 403 | An owner turned off the API for this workspace. |
workspace_closed | 403 | This workspace is closed. Everything stays readable; nothing can change. |
person_paused | 403 | This person is paused on the Free plan and can't add or change time. |
not_found | 404 | Not found. |
method_not_allowed | 405 | Method not allowed. |
timer_conflict | 409 | Another timer started at the same moment. Try again. |
idempotency_in_flight | 409 | A request with this Idempotency-Key is still running. Retry later. |
payload_too_large | 413 | The request body is larger than 64 KB. |
unsupported_media_type | 415 | Send JSON with Content-Type: application/json. |
unknown_field | 422 | The body contains a field this endpoint doesn't know. |
idempotency_key_reused | 422 | This Idempotency-Key was used for a different request. |
invalid | 422 | The entry is not valid. |
invalid_date | 422 | Dates must look like 2026-10-23. |
invalid_time | 422 | Times must be ISO 8601 with an offset, like 2026-10-23T09:05:00+02:00. |
invalid_minutes | 422 | Minutes must be a whole number from 1 to 1440 (0 is allowed only when changing an entry or starting a timer). |
invalid_notes | 422 | Notes must be text. |
notes_too_long | 422 | Notes can be at most 2000 characters. |
entry_invoiced | 422 | This entry is on an invoice or was billed elsewhere and can't change. |
entry_running | 422 | A running entry has no minutes or date to change. Stop the timer first, or move its start with started_at. |
entry_not_running | 422 | started_at can change only on a running entry. |
week_approved | 422 | This week is approved. Ask an owner to reopen it. |
week_submitted | 422 | This week is submitted for approval. Withdraw it in the app, or ask an owner. |
week_locked_for_timer | 422 | Today is in a submitted or approved week, so a timer can't start. |
project_not_trackable | 422 | The project doesn't exist or is archived. |
task_not_active | 422 | The task doesn't exist or is archived. |
timer_not_running | 422 | No timer is running. |
rate_limited | 429 | Too many requests. Wait for the number of seconds in Retry-After. |
server_error | 500 | Something went wrong on our side. |
Versioning and changes
The version is in the path. Within v1 changes only add things: new endpoints, optional parameters, response fields and error codes, so ignore fields you don't know. A breaking change gets /api/v2, announced here and by email to people with active tokens, and v1 keeps working for at least 12 months after that.
- October 24, 2026, 1.0.0. First version.
Revoke a leaked token
Found a clk_pat_ token somewhere it shouldn't be, such as a chat or a public repository? Paste it here. If it is live, it is revoked at once and its owner gets an email. You don't need an account.