Aller au contenu

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 (validation above) is the code. There is no top-level code field; parse it from type.

  • 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 on 422 validation responses. One entry per offending field with field, code (machine-readable — e.g. email_invalid, client_not_found, min_length, duplicate_entry_id), and message.

    field says where the offending value was sent. Its form depends on where that is:

    The value came from… field is… Examples
    the JSON body the property’s camelCase JSON path, exactly as in your body; list items are indexed projectId, stoppedAt, items[0].projectId
    a query-string parameter the parameter’s documented name summary[breakdown_by], summary[stack_by]
    a group of query parameters the group’s name rounding (covers rounding[grain] / rounding[mode], dashboard and export), range (covers from / to, calendar events)
    a request header the header name Tickr-Origin, Tickr-Agent
    an 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 as email

    Key on code to know what is wrong and on field to know where. Some endpoints used to return snake_case names 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 in items (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, code invalid; the message names 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 with field items and code too_many_errors gives the number of omitted errors.

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.

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:

  • 409 with detail: "entry_id_conflict" when the entry the other request created is visible to you;
  • the usual neutral 404 when 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.

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 Unavailable
Retry-After: 1
Content-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-After seconds, then send exactly the same request again (same body, same ids — for agent segments the same revision and payload; never bump the revision or mark it sent because of a 503);
  • 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.

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.