API reference
18 operations in timers. Raw spec: openapi.json.
/api/v1/time-entriesGET /api/time-entries
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
ticket_id | query | — | No | |
user_id | query | — | No | |
since | query | — | No | |
limit | query | integer | No |
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/time-entriesPOST /api/time-entries — manual entry (no timer).
Request body
CreateTimeEntryRequest
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/time-entries/approvals/decidePOST /api/time-entries/approvals/decide — bulk approve or bulk reject (`special:approve_time`). Only entries currently in the queue apply; every other id comes back with a per-item outcome. Emits one `time_entry.approval_*` event per applied entry.
Write-gate symmetry with `submit` and `reopen_entry`: an approver never signs off their own time. A self-owned id comes back as `own_entry` and is never transitioned; an owner or admin keeps the override, so a one-person org can still decide.
Request body
BulkDecisionRequest
Responses
| Status | Description |
|---|---|
200 | |
422 | unknown rejection reason_code |
Security: bearer
/api/v1/time-entries/approvals/queueGET /api/time-entries/approvals/queue — the approver queue (submitted entries by default). Approvers (`special:approve_time`) see the org queue and can filter `decided_by` for their own decision history; everyone else sees only their own submissions.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
status | query | — | No | Filter by approval state; defaults to `submitted` (the queue). |
user_id | query | — | No | Narrow to one submitter. |
decided_by | query | — | No | Per-approver decision history: entries this approver decided. |
limit | query | integer | No |
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/time-entries/{id}/approvalsGET /api/time-entries/:id/approvals — the append-only decision trail (`seq` ascending). Approvers and owner/admins read any trail; other users only their own entries'.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | |
404 | entry not visible in scope (not_found) |
Security: bearer
/api/v1/time-entries/{id}/fixPOST /api/time-entries/:id/fix — apply one verdict's fix to a queued entry (`special:approve_time`).
This is the server side of the queue's one-click fix: adopt the factual description the server assembled from the ticket, or cut the minutes that another entry of the same person already claimed. The entry stays in the queue afterwards — the approver still presses Approve, because a fix clears an objection and it does not sign anybody's time off.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Request body
FixEntryRequest
Responses
| Status | Description |
|---|---|
200 | |
404 | entry not visible in scope (rls_sparse) |
409 | entry locked by invoicing (time_entry_locked) |
422 | entry not in the queue, or the fix is not allowed |
Security: bearer
/api/v1/time-entries/{id}/reopenPOST /api/time-entries/:id/reopen — move a rejected entry back to draft (owner or admin) so it can be corrected and resubmitted.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Responses
| Status | Description |
|---|---|
200 | |
409 | entry locked by invoicing (time_entry_locked) |
422 | only rejected entries can be reopened |
Security: bearer
/api/v1/time-entries/{id}/submitPOST /api/time-entries/:id/submit — the entry owner (or an admin) pushes a draft/rejected entry into the approver queue.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string | Yes |
Request body
SubmitEntryRequest
Responses
| Status | Description |
|---|---|
200 | |
404 | entry not visible in scope (rls_sparse) |
409 | entry locked by invoicing (time_entry_locked) |
422 | entry not in a submittable state |
Security: bearer
/api/v1/time-gapsGET /api/time-gaps?user_id= — manager-scoped report for one member. Owner/admin only; everyone else uses `/api/time-gaps/me`.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from | query | — | No | Inclusive local-day start (YYYY-MM-DD). Defaults to yesterday. |
to | query | — | No | Inclusive local-day end (YYYY-MM-DD). Defaults to `from`. |
user_id | query | — | No | Manager report only: whose days. Requires owner/admin. |
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/time-gaps/meGET /api/time-gaps/me — the caller's day-level gap report (defaults to yesterday in the caller's timezone).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
from | query | — | No | Inclusive local-day start (YYYY-MM-DD). Defaults to yesterday. |
to | query | — | No | Inclusive local-day end (YYYY-MM-DD). Defaults to `from`. |
user_id | query | — | No | Manager report only: whose days. Requires owner/admin. |
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/time-settingsGET /api/time-settings — org time-discipline knobs (admin).
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/time-settingsPUT /api/time-settings — update the org's time-discipline knobs (admin).
Request body
UpdateTimeSettingsRequest
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/timers/activeGET /api/timers/active — the caller's running session (at most one).
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/timers/pausePOST /api/timers/pause — pauses the active (or given) session, recording the reason on the segment.
Request body
PauseTimerRequest
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/timers/resumePOST /api/timers/resume — resumes a paused session with a new segment.
Request body
PauseTimerRequest
Responses
| Status | Description |
|---|---|
200 |
Security: bearer
/api/v1/timers/startPOST /api/timers/start — starts (or switches to) a timer. If another session is running it is auto-paused with reason `switched` and stays in the working set; the DB's partial unique index is the backstop. Instant-first (IF1/IF2): idempotency-keyed and read-your-write.
Request body
StartTimerRequest
Responses
| Status | Description |
|---|---|
200 | |
404 | ticket not visible in scope (rls_sparse) |
409 | idempotency key reused |
Security: bearer
/api/v1/timers/stopPOST /api/timers/stop — stops the session and materializes a time entry (rounded up to the minute) in the same transaction.
Request body
StopTimerRequest
Responses
| Status | Description |
|---|---|
200 | |
404 | session not visible in scope (rls_sparse) |
409 | version conflict or idempotency key reused |
Security: bearer
/api/v1/timers/working-setGET /api/timers/working-set — recent sessions (running first): the docked working set the UI renders.
Responses
| Status | Description |
|---|---|
200 |
Security: bearer