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

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”.

Scopes. A scope never adds to what the person's role allows.
ScopeAllows
time:readTime entries, deleted entries, the timer and weeks. Owners read everyone's, members their own.
time:writeAdding, changing and deleting time entries; starting and stopping the timer. Includes time:read.
projects:readProjects, tasks and budgets; the team and clients for owners.
invoices:readInvoices, credit notes and their files. Owners only.
reports:readThe time report. Members get their own hours without money.

Rate limits

Over a limit the answer is 429 with Retry-After in seconds.
BucketLimit
Per token, all requests100 per 60 seconds
Per token, writes30 per 60 seconds and 2,000 per day
Per token, the time report and invoice files30 per 15 minutes
Per workspace, all tokens together300 per 60 seconds
Per IP, failed sign-ins with a token20 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.

Every error code
CodeStatusMeaning
invalid_parameter400A parameter is missing or not valid.
invalid_json400The request body must be a JSON object.
invalid_cursor400The cursor is not valid. Start again without it.
cursor_mismatch400The cursor was made for other filters. Start again without it.
idempotency_key_required400This request needs an Idempotency-Key header.
token_in_url400Tokens are accepted only in the Authorization header. This token is now revoked because it was sent in a URL or cookie.
token_missing401Send a token in the Authorization header: Bearer <token>.
token_invalid401The token is not valid here.
token_expired401The token has expired. Create a new one in your profile.
token_revoked401The token was revoked.
insufficient_scope403The token doesn't have the scope this request needs.
owner_only403Only workspace owners can do this.
api_disabled403An owner turned off the API for this workspace.
workspace_closed403This workspace is closed. Everything stays readable; nothing can change.
person_paused403This person is paused on the Free plan and can't add or change time.
not_found404Not found.
method_not_allowed405Method not allowed.
timer_conflict409Another timer started at the same moment. Try again.
idempotency_in_flight409A request with this Idempotency-Key is still running. Retry later.
payload_too_large413The request body is larger than 64 KB.
unsupported_media_type415Send JSON with Content-Type: application/json.
unknown_field422The body contains a field this endpoint doesn't know.
idempotency_key_reused422This Idempotency-Key was used for a different request.
invalid422The entry is not valid.
invalid_date422Dates must look like 2026-10-23.
invalid_time422Times must be ISO 8601 with an offset, like 2026-10-23T09:05:00+02:00.
invalid_minutes422Minutes must be a whole number from 1 to 1440 (0 is allowed only when changing an entry or starting a timer).
invalid_notes422Notes must be text.
notes_too_long422Notes can be at most 2000 characters.
entry_invoiced422This entry is on an invoice or was billed elsewhere and can't change.
entry_running422A running entry has no minutes or date to change. Stop the timer first, or move its start with started_at.
entry_not_running422started_at can change only on a running entry.
week_approved422This week is approved. Ask an owner to reopen it.
week_submitted422This week is submitted for approval. Withdraw it in the app, or ask an owner.
week_locked_for_timer422Today is in a submitted or approved week, so a timer can't start.
project_not_trackable422The project doesn't exist or is archived.
task_not_active422The task doesn't exist or is archived.
timer_not_running422No timer is running.
rate_limited429Too many requests. Wait for the number of seconds in Retry-After.
server_error500Something 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.

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.