Time Entries API
Generic time tracking on any record in the entity registry (leads, opportunities, accounts, contacts, projects, tasks, work_requests). RBAC module time_entries (Time Tracking). Project task time keeps its own endpoints under /projects. See Associations & Time Entries.
Endpoints
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| GET | /time-entries | view | Entries on a record (entityType + entityId), or across your scope; paginated with totals |
| GET | /time-entries/summary?entityType=&entityId= | view | Per-person totals on a record |
| GET | /time-entries/timer | view | Your running timer, or null |
| POST | /time-entries/timer/start | create | Start a timer on a record |
| POST | /time-entries/timer/stop | create | Stop your timer and log the time |
| DELETE | /time-entries/timer | create | Discard your timer without logging |
| POST | /time-entries | create | Log time manually |
| PUT | /time-entries/:id | edit | Update an entry |
| DELETE | /time-entries/:id | delete | Soft-delete an entry |
Who Can Do What
| Action | Rule (in addition to the module permission) |
|---|---|
| List / summary on a record | Can view the record (work requests: the request's view rule) |
| List without a record | Only entries by users inside your time_entries record scope |
| Log time / start a timer | Work requests: assignee, owning-team member, or manager of the request. Other records: can view the record. |
| Update / delete an entry | Your own entry, or an entry by a user inside your time_entries record scope |
List
GET /time-entries?entityType=work_requests&entityId=uuid&userId=&from=2026-10-01&to=2026-10-31&page=1&limit=50
| Param | Description |
|---|---|
entityType + entityId | Together; omit both for a cross-record list |
userId | Filter by who logged it |
from, to | logged_at date range (inclusive) |
page, limit | Default 1 / 50; limit ≤ 500 |
{
"data": [
{
"id": "uuid",
"entityType": "work_requests",
"entityId": "uuid",
"task": { "id": "uuid", "title": "Demo call" },
"user": { "id": "uuid", "firstName": "Pat", "lastName": "Engineer", "avatarUrl": null },
"description": "Demo prep",
"minutes": 90,
"loggedAt": "2026-10-05",
"startedAt": "2026-10-05T09:00:00Z",
"endedAt": "2026-10-05T10:30:00Z",
"source": "timer",
"isBillable": true,
"hourlyRate": 120,
"currency": "USD",
"amount": 180,
"createdAt": "…",
"updatedAt": "…"
}
],
"meta": { "total": 1, "page": 1, "limit": 50, "totalPages": 1 },
"totals": { "minutes": 90, "billableMinutes": 90, "billableAmount": 180 }
}
Summary
{
"byUser": [
{ "user": { "id": "uuid", "firstName": "Pat", "lastName": "Engineer", "avatarUrl": null },
"minutes": 90, "billableMinutes": 90, "billableAmount": 180 }
],
"totalMinutes": 90,
"billableMinutes": 90,
"billableAmount": 180
}
Log Time
POST /time-entries
{
"entityType": "work_requests",
"entityId": "uuid",
"minutes": 45,
"loggedAt": "2026-10-05",
"description": "Solution design review",
"taskId": null,
"isBillable": true,
"hourlyRate": 120,
"currency": "USD"
}
| Field | Notes |
|---|---|
entityType, entityId, minutes | Required. Minutes 1 – 1440. |
loggedAt | Date; defaults to today |
taskId | Optional CRM task (must exist) |
isBillable, hourlyRate, currency | Default from the record — a work request's billing settings; non-billable for other records |
PUT /time-entries/:id accepts any of minutes, loggedAt, description, taskId, isBillable, hourlyRate, currency.
Timer
Start — POST /time-entries/timer/start
{ "entityType": "work_requests", "entityId": "uuid", "taskId": null, "description": "Demo prep" }
Returns 400 if you already have a running timer here or a running project timer. One timer per user.
Get — GET /time-entries/timer
{
"id": "uuid",
"entity": { "type": "work_requests", "id": "uuid", "label": "WR-000042 · Product demo for Acme", "url": "/work-requests/uuid" },
"task": null,
"description": "Demo prep",
"startedAt": "2026-10-05T09:00:00Z",
"elapsedSeconds": 1820
}
Stop — POST /time-entries/timer/stop, body { minutes?, description?, isBillable? }. Logs a source: "timer" entry with the elapsed minutes (minimum 1, capped at 24 h) unless minutes is supplied, and returns the entry. 404 if no timer is running.
Discard — DELETE /time-entries/timer → { "success": true }.
A timer on a work request is removed automatically when the request is completed, cancelled or deleted.