Errors
Every error response uses RFC 7807
Problem Details for HTTP APIs, encoded as application/problem+json.
{ "type": "https://docs.tickr.coderise.cloud/errors/validation", "title": "Validation failed", "status": 422, "detail": "Validation failed", "correlationId": "01HXYZ....", "errors": [ { "field": "email", "code": "email_invalid", "message": "..." } ]}-
type— stable URI and the source of truth for the machine-readable error code. The trailing path segment (validationabove) is the code. There is no top-levelcodefield; parse it fromtype. -
title— short, human-readable HTTP reason phrase. -
detail— the error message (often the same as the code for top-level errors; field-level messages are more descriptive). -
correlationId— include this when reporting a problem to the support team. It links to the server logs.It is
camelCase, like every property of the API’s JSON bodies (see Conventions). -
errors[]— present on422 validationresponses. One entry per offending field withfield,code(machine-readable — e.g.email_invalid,client_not_found,min_length,duplicate_entry_id), andmessage.fieldsays where the offending value was sent. Its form depends on where that is:The value came from… fieldis…Examples the JSON body the property’s camelCaseJSON path, exactly as in your body; list items are indexedprojectId,stoppedAt,items[0].projectIda query-string parameter the parameter’s documented name summary[breakdown_by],summary[stack_by]a group of query parameters the group’s name rounding(coversrounding[grain]/rounding[mode], dashboard and export),range(coversfrom/to, calendar events)a request header the header name Tickr-Origin,Tickr-Agentan uploaded import file (Toggl CSV, Tickr archive) the location inside the file that blocks the import manifest.schema_version,time_entries,projects.visibility, a CSV column such asemailKey on
codeto know what is wrong and onfieldto know where. Some endpoints used to returnsnake_casenames for body properties (task_id) — see the changelog.Bulk upsert. In
POST /api/v1/entries:bulk, every error about an item is indexed by its position initems(items[2].stoppedAt), including an item that references a project outside the workspace or a task outside the workspace or in another project (items[3].projectId,items[7].taskId, codeinvalid; themessagenames the offending id, e.g.Project <id> does not belong to this workspace). Each item carrying an offending id gets its own error. These reference errors are capped at 100, in item order: past the cap, one last error withfielditemsandcodetoo_many_errorsgives the number of omitted errors.
Common codes
Section intitulée « Common codes »The top-level code is the trailing segment of type
(https://docs.tickr.coderise.cloud/errors/<code>). Field-level codes live in
errors[].code on 422 responses.
| HTTP | code |
Meaning |
|---|---|---|
| 401 | unauthorized |
API key missing, malformed, revoked or expired, or session expired — see Authentication. |
| 403 | forbidden |
Authenticated but not authorized for this resource. |
| 403 | forbidden (analyst_read_only) |
Analyst role attempted a mutation (carried in detail). |
| 403 | forbidden (not_project_member) |
Workspace User without project membership. |
| 403 | forbidden (workspace_mismatch) |
Cross-workspace reference rejected. |
| 404 | not_found |
Resource does not exist or is not visible to you. |
| 409 | conflict |
Server-side state conflict (e.g. concurrent edit). |
| 409 | conflict (api_key_limit_reached) |
10 active API keys already exist for you in this workspace. |
| 409 | conflict (entry_id_conflict) |
An entry with the same client-chosen id was created concurrently and is visible to you — see below. |
| 413 | payload_too_large |
Request body exceeds the documented size cap. |
| 422 | validation |
Input validation failed; see errors[] for per-field codes. |
| 422 | validation (duplicate_entry_id) |
Same entry id sent more than once in one bulk batch — see below. |
| 422 | validation (api_key_role_incompatible, api_key_expiry_invalid, api_key_name_invalid) |
API key creation refused — see Authentication. |
| 429 | rate_limited |
See Rate limits. |
| 503 | calendar_disabled |
The Microsoft Calendar feature is not enabled on this instance. |
| 503 | calendar_token_revoked |
The user’s Microsoft refresh token was revoked. Reconnect required. |
| 503 | calendar_provider_unavailable |
Transient error reaching Microsoft Graph. Retry later. |
| 503 | service_unavailable (workspace_busy) |
The workspace was busy longer than the server’s wait budget; nothing was written. Retry after Retry-After — see below. |
| 429 | calendar_provider_throttled |
Microsoft Graph throttled the call. See Retry-After header. |
| 400 | pkce_state_not_found |
OAuth state expired or replayed. Restart the connection flow. |
| 400 | microsoft_oauth_error |
Microsoft rejected the OAuth exchange (e.g. user cancelled consent). |
Sub-codes shown in parentheses are not separate top-level codes. On
forbidden, conflict and service_unavailable responses
(analyst_read_only, not_project_member, workspace_mismatch,
api_key_limit_reached, entry_id_conflict, workspace_busy) they are
carried in the detail field; on validation responses they are the
errors[].code of the offending field. Match on the pair when you need to
distinguish them.
This list is not exhaustive — the full catalogue lives in the source code
(ErrorCodes).
New codes are added as new features ship; existing codes are never removed
without a major version bump.
Duplicate ids in a bulk batch
Section intitulée « Duplicate ids in a bulk batch »POST /api/v1/entries:bulk rejects the whole batch with 422 when the
same id appears more than once. Two instructions about the same entry have
no correct outcome to guess, and applying them in order used to resolve the
billable rate from stale state — a wrong amount, with no error raised.
Every offending row is reported, keyed by JSON path, so you can fix the exact lines rather than parse a message:
{ "type": "https://docs.tickr.coderise.cloud/errors/validation", "status": 422, "errors": [ { "field": "items[0].id", "code": "duplicate_entry_id", "message": "Duplicate entry id …" }, { "field": "items[2].id", "code": "duplicate_entry_id", "message": "Duplicate entry id …" } ]}Nothing is written when a batch is rejected — it is all-or-nothing.
Concurrent creation of the same entry id (409 entry_id_conflict)
Section intitulée « Concurrent creation of the same entry id (409 entry_id_conflict) »POST /api/v1/entries:bulk uses the client-chosen id as upsert key. When two
requests insert the same new id at the same time, one wins; the other is
refused as a whole — nothing from that batch is written:
409withdetail: "entry_id_conflict"when the entry the other request created is visible to you;- the usual neutral
404when it is not (another user’s entry, another workspace) — see 404 is intentional.
Re-read the entry before retrying: the id now exists, so the same batch sent again takes the update branch for that id.
Submission scope changed (409 submission_scope_changed)
Section intitulée « Submission scope changed (409 submission_scope_changed) »A timesheet submission or resubmission found that one of the entries it selected was deleted, moved or no longer counted when it locked them. Nothing was submitted. Submit again.
Workspace busy (503 workspace_busy)
Section intitulée « Workspace busy (503 workspace_busy) »Every write that touches time entries or timesheets — creating, editing,
deleting or bulk-upserting entries, starting or stopping a timer, agent
segments and usage, submitting or validating a timesheet, billing and rate
changes — first takes a short per-workspace coordination lock, so that a
timesheet submission never sees half of a concurrent edit. So do removing a
member and leaving a workspace (even when no timer is running), editing a
project or its agent-time counting policy, editing a client’s agent-time
counting policy (a client PATCH carrying countAgentTime or
clearCountAgentTime; any other client edit takes no lock), changing the
timesheet or agent-time settings and sending a client approval request. When
another operation holds the lock longer than the server’s wait budget (5 seconds by default; self-hosted operators set it with ENTRY_LOCK_WAIT_SECONDS, from 1 to 60), the request is refused
before anything is written:
HTTP/1.1 503 Service UnavailableRetry-After: 1Content-Type: application/problem+json
{ "type": "https://docs.tickr.coderise.cloud/errors/service_unavailable", "title": "Service unavailable", "status": 503, "detail": "workspace_busy", "correlationId": "01HXYZ...."}It is transient and safe to retry:
- wait at least
Retry-Afterseconds, then send exactly the same request again (same body, same ids — for agent segments the samerevisionand payload; never bump the revision or mark it sent because of a503); - back off exponentially with jitter (for example 1 s, 2 s, 4 s … capped at 30 s, ± 20 %) so that several clients do not retry in lockstep;
- a refused timer stop leaves the timer running and a refused start creates none — re-read the state if in doubt.
A few heavy Owner/Admin operations of the web app (agent-time re-apply,
client move, bulk billing apply, workspace resnapshot) wait longer instead of
answering 503.
404 is intentional
Section intitulée « 404 is intentional »Tickr returns 404 not_found instead of 403 forbidden when the caller is
authenticated but the resource is in a different workspace, or in a private
project they don’t belong to. This is on purpose — it prevents leaking
existence via probe requests. If you genuinely cannot see a resource you
expected to, double-check the API key’s workspace and your project membership.