Changelog
Changes to the API contract that an existing integration may notice. Additive changes (new endpoints, new optional properties) are not listed here, except when they come with a behaviour an integration should know about.
2026-11-09
Section titled “2026-11-09”PUT /api/v1/agent-segments/{segmentId}: a later revision renames the session
Section titled “PUT /api/v1/agent-segments/{segmentId}: a later revision renames the session”On a revision that is accepted (greater than the stored one, same session,
segment not detached and not locked by a submission), description, taskId
and tagIds of the body are now applied to the existing entry instead of
being ignored. An integration that resends the same mapping on every revision
sees no change. What to know:
descriptionabsent ornullkeeps the stored one.taskIdabsent keeps the task, an explicitnullclears it.tagIdsabsent ornullkeeps the tags,[]removes them, a list replaces them.projectIdis immutable: a value different from the stored one (a missingprojectIdcounts as different from a stored project) answers422with an error onprojectIdand the codeimmutable, and nothing in the revision is written. Open a new segment to change project.- The applied values are validated like at creation:
descriptionat most 2000 characters (too_long), a changedtaskIdmust belong to the entry’s project (invalid, orrequiredon an entry without project): both422. Tag ids that no longer exist are ignored rather than refused (a heartbeat never fails because a tag was deleted); the response lists the tags applied. - An entry locked by a timesheet submission still answers
409 entry_locked; a detached segment409 segment_detached. A finalised segment that is not detached can still be renamed (finalisation is not a validation), but its end cannot move (409 segment_finalized). - The
200response is the up-to-date entry (description,taskId,taskName,tagIds): read it to know that the server applied the rename. Replay and revision rules are unchanged (exact replay is a no-op, a reused number with another content is409 agent_revision_conflict). AtaskIdabsent (keep) andtaskId: null(clear) are different requests: the same revision sent once each is409 agent_revision_conflict.
2026-11-05
Section titled “2026-11-05”Release “agent provenance and entry write coordination”. Everything below is documented in full on Agent provenance and Errors; this entry lists what an existing integration may notice.
New refusals on writes: 503 workspace_busy, 409 entry_id_conflict, 409 submission_scope_changed
Section titled “New refusals on writes: 503 workspace_busy, 409 entry_id_conflict, 409 submission_scope_changed”Every write that touches time entries, timesheet lines or submissions now takes a short per-workspace coordination lock, so that a timesheet submission never sees half of a concurrent edit.
503 service_unavailablewithdetail: "workspace_busy"and aRetry-After: 1header when the lock is not obtained within the wait budget (5 s by default). Nothing was written: retry exactly the same request afterRetry-After, with exponential back-off and jitter. It applies to the/api/v1entry and timer routes as well as to the internal ones. If your HTTP client treats every5xxas a hard failure, make an exception for this code.409 conflictwithdetail: "entry_id_conflict"when an entry created with a client-chosenid(bulk upsert) was created concurrently by another request and is visible to you. Re-read the entry; sending the same id again takes the update branch.409 conflictwithsubmission_scope_changedwhen the entries selected by a timesheet submission changed while it locked them. Nothing was submitted.
Self-hosted deployments tune the wait budget with ENTRY_LOCK_WAIT_SECONDS
(default 5, from 1 to 60).
Entries have an origin; agents can declare themselves
Section titled “Entries have an origin; agents can declare themselves”Every entry now exposes an immutable origin (web, api, agent,
agent_session, import_toggl, import_tickr, calendar, and unknown for
entries created before the release). With an API key, an entry is api unless
you send the Tickr-Origin: agent and Tickr-Agent: <name>/<version> headers
(then agent; Tickr-Origin: agent without Tickr-Agent answers
422 agent_required, field Tickr-Agent). Two new routes record token usage
and automatic agent sessions: PUT /api/v1/entries/{id}/agent-usage and
PUT /api/v1/agent-segments/{id} (the latter answers 404 unless the
deployment enables agent sessions). Entries of the agent_session origin may be
excluded from work-time totals by the workspace’s counting policy, and can
neither be resumed as a timer nor split — see
Agent provenance.
Stricter limit on entry spans
Section titled “Stricter limit on entry spans”Entry create, update, bulk upsert and timer stop now refuse an entry spanning
more than 2 147 483 647 seconds (about 68 years) with 422 invalid_value
(field stoppedAt, or items[i].stoppedAt for a bulk upsert). Starting a timer,
or editing an entry so that it stays running, with a start older than that
answers 422 invalid_value on startedAt: such an entry could never be stopped.
Removed members can no longer write
Section titled “Removed members can no longer write”A timer running when a member is removed or leaves is stopped by the exit.
A write of that member (timer start, entry create or edit, bulk upsert, agent
segment) that was queued behind the exit answers 401 and writes nothing.
Internal routes that administrators often script
Section titled “Internal routes that administrators often script”These routes belong to the web app (session authentication, not available with an API key):
PUT /api/workspaces/me/brandingtakes three new optional properties,phone,registrationNumberandvatNumber(trimmed, 50 characters at most,422 max_lengthbeyond). ThePUTis a full replace: a script that sends only the previous properties clears the new ones.POST /api/tagsanswers403 tags_creation_disabledto a plain member when the workspace setting “members can create tags” is off.GET /api/auth/mereturnsagentSegmentsEnabled, the deployment flag the web app reads to show or hide the agent-session interface.- The dashboard, the agent-activity report and the entry exports accept
origins(agentSessionIdis accepted only by the agent-activity report and the entry exports; the dashboard ignores it); the exports append ten agent columns after the existing ones (the positions of the existing columns do not change).
Migrating: nothing breaks for an integration that already retries 5xx and
409 responses sensibly. If yours does not, add the 503 workspace_busy retry
rule above before relying on timesheet validation in a workspace your tool
writes to.
2026-10-18
Section titled “2026-10-18”errors[].field names body properties in camelCase
Section titled “errors[].field names body properties in camelCase”In 422 validation responses, when the offending value is a JSON body
property, errors[].field is now its name as you sent it, in
camelCase, on every endpoint. List items keep their JSON path
(items[0].projectId).
Errors about something other than a body property keep naming it where you
sent it (see Errors): a query parameter by its documented
name (summary[breakdown_by]), a group of query parameters by the group name
(rounding for rounding[grain] / rounding[mode], range for from /
to), a request header by the header name (Tickr-Origin, Tickr-Agent),
and an import blocker by its location in the uploaded file
(manifest.schema_version, projects.visibility).
Until now the spelling depended on the handler: entry and timer endpoints
returned snake_case (project_id, task_id, stopped_at), some
validations returned the C# property name (StartedAt, Role), others
camelCase.
| Before | Now | Where |
|---|---|---|
project_id, task_id, stopped_at |
projectId, taskId, stoppedAt |
Create / update entry, bulk upsert, start / stop timer (internal and /api/v1) |
Items[0].project_id |
items[0].projectId |
POST /api/v1/entries:bulk (missing project) |
project_id, task_id (flat, one error per offending id) |
items[n].projectId, items[n].taskId (one error per offending item) |
POST /api/v1/entries:bulk (item referencing a project outside the workspace, or a task outside the workspace or in another project) |
StartedAt, StoppedAt, Email, Name… |
startedAt, stoppedAt, email, name… |
Requests validated by a schema rule |
Role, ExpiresInDays |
role, expiresInDays |
API key creation |
hourly_rate, default_hourly_rate |
hourlyRate, defaultHourlyRate |
Rate endpoints, clients |
preferred_language, default_project_visibility, view_config |
preferredLanguage, defaultProjectVisibility, viewConfig |
Preferences, workspace settings, saved reports |
Unchanged: the code of each error, the type URI, and the field of
errors about query parameters, headers and import files.
Bulk upsert references: in POST /api/v1/entries:bulk, an item that
references a project outside the workspace, or a task outside the workspace
or in another project, now gets its own error at its JSON path
(items[3].projectId, items[7].taskId), like every other bulk error. They
used to be reported once per offending id, without the item index. When
several items carry the same unknown id, each of them is reported. At most
100 such errors are returned, in item order; beyond that, one last error with
field items and code too_many_errors says how many were omitted.
PATCH /api/v1/entries/{id}: when the update would put the stop before the
start and you sent only startedAt, the error now names startedAt (it
named stoppedAt, a property you had not sent). It still names stoppedAt
when you sent stoppedAt.
Migrating: if you matched field against the old spelling, compare
against the camelCase name — or, to stay compatible with both, compare
ignoring case and separators (task_id is taskId). Matching on code is
unaffected.