Time entries
Log and adjust manual time on tasks and subtasks.
A time entry records work time against a task or subtask on one calendar day. There is at most one entry per (person, task or subtask, date): posting more time on the same day merges into the existing entry — summing the duration and stacking the new description on top — rather than creating a second row. Manual entries are posted via this API; entries produced by the in-app timer are server-generated and not creatable via REST.
Endpoints
| Method | Path | Notes |
|---|---|---|
GET | /api/v1/time-entries | List with filters. Cursor pagination. |
POST | /api/v1/time-entries | Log a manual entry. Accepts Idempotency-Key. |
GET | /api/v1/time-entries/{id} | Single entry with user, task, subtask. |
PATCH | /api/v1/time-entries/{id} | Edit description, start time, duration. |
DELETE | /api/v1/time-entries/{id} | Delete the entry. |
The in-app timer endpoints (start, stop, switch) are intentionally not exposed via REST in v1.
TimeEntry model
{
"id": "e1…",
"organizationId": "...",
"userId": "u1…",
"taskId": "t1…",
"subtaskId": null,
"description": "Initial wiring",
"date": "2026-05-19",
"durationSeconds": 5400,
"source": "manual",
"autoStopped": false,
"createdAt": "...",
"updatedAt": "..."
}| Field | Notes |
|---|---|
date | The work day this time counts for (YYYY-MM-DD). One entry per person/task/day. |
source | manual (this API) or timer (in-app start/stop). A day mixing both reads as manual. |
autoStopped | true when the 8h-cap sweeper persisted a timer-driven entry. |
Each entry belongs to exactly one of taskId or subtaskId (XOR). The parent is immutable after creation.
Listing entries
GET /api/v1/time-entries?userId=...&startDate=2026-05-01&endDate=2026-05-31| Param | Type | Notes |
|---|---|---|
userId | UUID | One member's entries. |
taskId | UUID | Direct task entries. |
subtaskId | UUID | Subtask entries. |
projectId | UUID | Entries on tasks/subtasks of one project. |
startDate | date | Inclusive lower bound on date (the work day). |
endDate | date | Inclusive upper bound on date (the work day). |
cursor | UUID | From a previous response. |
limit | int 1–100 | Default 50. |
Returns { data, pagination: { nextCursor } }. Entries are filtered by project visibility — standard members may not see entries on tasks they cannot access.
Logging a manual entry
POST /api/v1/time-entries
Content-Type: application/json
Idempotency-Key: ...
{
"taskId": "t1…",
"date": "2026-05-19",
"durationSeconds": 5400,
"description": "Initial wiring"
}Required:
| Field | Type | Constraint |
|---|---|---|
Exactly one of taskId or subtaskId | UUID | Sending both, or neither, returns 422. |
date | date | The work day (YYYY-MM-DD). Must not be in the future. |
durationSeconds | int | 1 – 86 400 (≤ 24 h). |
Optional:
| Field | Type | Constraint |
|---|---|---|
description | string | ≤ 50 000 chars (rich-text HTML). |
timezone | string | IANA zone for the future-date guard and the stacked block's time label. Defaults to UTC. |
Response: 201 Created with the entry — whether a new entry was created or the time merged into an existing one for that person/task/day. The entry is always attributed to the API key's user.
Editing an entry
PATCH /api/v1/time-entries/{id}
{ "durationSeconds": 7200 }Caller must be one of:
- The entry's author, or
- An organization owner / admin, or
- A project manager on the entry's parent project.
Allowed fields: description, date, durationSeconds. Moving date onto a day that already holds this person's entry for the same task/subtask returns 409.
Deleting an entry
DELETE /api/v1/time-entries/{id}Returns { "data": { "success": true } }. Same authorization rules as edit.
Common precondition failures
| Code | Cause |
|---|---|
422 | Body sent both taskId and subtaskId, or neither. |
403 | Caller can read the entry but cannot edit/delete it. |
404 | Cross-org id, or entry belongs to a task the caller cannot access. |
409 | PATCH moved date onto a day that already holds this person's entry for the same task/subtask. |
412 | The parent task is finalized or blocked, so it is read-only. |
412 | The parent task's project is closed (completed / cancelled). POST and PATCH are rejected — those hours would fall outside the billing cycle — including on tasks linked before the project closed. DELETE is still allowed, so wrongly-logged hours can be removed. |