Tracker API
Manage tracker projects, work entries, and per-user timers through organization-scoped REST endpoints.
The Tracker API captures billable and non-billable work against clients and projects. It gives the same project, entry, and timer records that Conduitt Tracker uses.
/v1/work/trackerUse the tracker:read scope for GET requests. Use tracker:write for create, update, delete, start, stop, or discard requests. Conduitt scopes every request to the organization attached to the credential. The API rejects a client, project, entry, or timer identifier from another organization, or returns it as not found.
Projects
| Method | Endpoint | Purpose |
|---|---|---|
GET | /projects | List projects with tracked and billable totals |
POST | /projects | Create a project |
GET | /projects/{id} | Retrieve one project with all-time totals |
PATCH | /projects/{id} | Update supplied project fields |
DELETE | /projects/{id} | Delete a project but keep its work entries |
List Projects
GET /v1/work/tracker/projects
| Query parameter | Type | Needed | Default | Description |
|---|---|---|---|---|
limit | integer, 1–200 | No | 50 | Maximum projects returned |
offset | integer, 0 or greater | No | 0 | Projects skipped |
status | in_progress, completed, or archived | No | — | Filter by lifecycle status |
clientId | UUID | No | — | Filter by an organization-owned client |
Each project includes totalMinutes for all time, billableMinutes for billable work, and totalAmount calculated from entry-level rates. These totals can change when you edit or import entries. Refresh them after a mutation or a completed remote sync.
{
"data": [
{
"id": "a97b0cda-1c68-4fae-b84e-46d4340f63c6",
"clientId": "74957d67-9132-42b5-9238-b6fe520611d5",
"name": "Website redesign",
"code": "WEB-2026",
"description": "Discovery, design, and implementation",
"externalRef": null,
"billingMode": "time_and_materials",
"billable": true,
"defaultRate": "175.00",
"currency": "USD",
"estimateHours": 80,
"status": "in_progress",
"color": "#4F46E5",
"tags": ["design", "priority"],
"assignedUserIds": [],
"totalMinutes": 945,
"billableMinutes": 870,
"totalAmount": 2537.5,
"createdAt": "2026-07-01T13:00:00.000Z",
"updatedAt": "2026-07-12T14:30:00.000Z"
}
],
"meta": { "limit": 50, "offset": 0, "total": 1 }
}Create or Update a Project
Create with POST /v1/work/tracker/projects. Update with PATCH /v1/work/tracker/projects/{id}. A patch must contain at least one field and changes only supplied fields.
| Field | Type | Needed on create | Default | Constraints and behavior |
|---|---|---|---|---|
name | string | Yes | — | 1–255 characters; unique within the organization |
description | string | No | — | Up to 1,000 characters |
clientId | UUID or null | No | — | Must point to a client in the current organization |
code | string | No | — | Up to 50 characters |
billable | boolean | No | false | Default billable state inherited by new timers |
rate | number | No | — | Non-negative default hourly rate |
currency | string | No | USD | Three-character ISO 4217 code |
estimateHours | integer | No | — | Zero or greater |
status | enum | No | in_progress | in_progress, completed, or archived |
color | string | No | — | Up to 20 characters |
tags | string array | No | — | Up to 20 category names, each 1–50 characters |
assignedUserIds | UUID array | No | — | Up to 50 assigned users |
curl -X POST "https://api.conduitt.io/v1/work/tracker/projects" \
-H "Authorization: Bearer $CONDUITT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Website redesign",
"clientId": "74957d67-9132-42b5-9238-b6fe520611d5",
"billable": true,
"rate": 175,
"currency": "USD",
"tags": ["design", "priority"]
}'The API returns rates as decimal strings, such as "175.00", to preserve money precision. Project deletion sets the project link on current work entries to null. It does not erase tracked work.
Work Entries
| Method | Endpoint | Purpose |
|---|---|---|
GET | /entries | List native and imported work entries |
GET | /entries/summary | Calculate billable totals for a date range |
POST | /entries | Create a native work entry |
GET | /entries/{id} | Retrieve one work entry |
PATCH | /entries/{id} | Update a work entry |
DELETE | /entries/{id} | Permanently delete a work entry |
List Entries
GET /v1/work/tracker/entries
| Query parameter | Type | Needed | Description |
|---|---|---|---|
limit | integer, 1–200 | No | Maximum entries returned; default 50 |
offset | integer, 0 or greater | No | Entries skipped; default 0 |
dateFrom | YYYY-MM-DD | No | Include entries on or after this UTC date |
dateTo | YYYY-MM-DD | No | Include entries on or before this UTC date |
projectId | UUID | No | Filter by project |
clientId | UUID | No | Filter by client |
source | enum | No | Filter by native_tracker, harvest_api, or another documented work source |
billable | true or false | No | Filter by billing eligibility |
q | string, up to 200 characters | No | Search work descriptions and labels for clients |
Responses give normalized billing fields. They omit provider rawPayload, internal deduplication hashes, and extensible metadata.
Create an Entry
POST /v1/work/tracker/entries
| Field | Type | Needed | Default | Constraints and behavior |
|---|---|---|---|---|
description | string | Yes | — | 1–2,000 characters |
durationMinutes | integer | Yes | — | 1–525,600 |
entryDate | YYYY-MM-DD | Yes | — | Calendar date interpreted in UTC |
startTime | HH:mm | No | — | Send it together with endTime |
endTime | HH:mm | No | — | Send it together with startTime |
billable | boolean | No | true | Whether you can invoice the entry |
rate | number | No | — | Non-negative hourly rate |
currency | string | No | USD | Three-character ISO 4217 code |
clientId | UUID | No | — | Must belong to the current organization |
projectId | UUID | No | — | Must belong to the current organization |
{
"description": "Implemented invoice approval workflow",
"durationMinutes": 135,
"entryDate": "2026-07-12",
"startTime": "09:30",
"endTime": "11:45",
"billable": true,
"rate": 175,
"currency": "USD",
"projectId": "a97b0cda-1c68-4fae-b84e-46d4340f63c6"
}The response includes the server-owned source, billingStatus, quantity, timestamps, and identifiers. quantity and rate are decimal strings. entryDate, startedAt, and endedAt are ISO 8601 timestamps.
For updates, PATCH /entries/{id} accepts the same fields as create, all optional. If you change a date, duration, start, or end time, Conduitt recomputes the related time fields together. Supply both startTime and endTime when you add explicit clock times.
Billable Summary
GET /v1/work/tracker/entries/summary?dateFrom=2026-07-01&dateTo=2026-07-31
You must supply both dates. Both dates are inclusive. The response returns totalMinutes, totalAmount, currency, and a byProject breakdown. Entries without a project appear with projectId: null.
Timers
Timers belong to the authenticated user as well as the organization. One user cannot stop or discard another user's timer.
| Method | Endpoint | Purpose |
|---|---|---|
GET | /timer/active | Return the current user's active timer or null |
POST | /timer/start | Start a timer |
POST | /timer/{sessionId}/stop | Stop a timer and optionally create a work entry |
POST | /timer/{sessionId}/discard | Discard an active timer without an entry |
GET | /timer/sessions | List the current user's timer history |
Start a Timer
{
"description": "Client discovery call",
"projectId": "a97b0cda-1c68-4fae-b84e-46d4340f63c6",
"billable": true
}description, clientId, projectId, billable, and rate are optional. When you supply a project, the timer inherits its billable setting and default rate. An explicit rate overrides the inherited rate.
Conduitt serializes concurrent start requests. If a timer is already active, the API returns 409 Conflict. Stop or discard the active timer before you retry.
Stop a Timer
POST /v1/work/tracker/timer/{sessionId}/stop
{
"description": "Client discovery and follow-up notes",
"billable": true,
"rate": 175,
"createWorkEntry": true
}All fields are optional. createWorkEntry defaults to true. The response contains the stopped session, the created workEntry or null, and durationMinutes rounded to at least one minute.
A stop is duplicate-safe within the timer workflow. A repeated or concurrent stop returns 409 Conflict and does not create a second work entry. Do not retry a timed-out stop blindly. First read /timer/active. Then search entries for the timer's time range.
Timer History
GET /v1/work/tracker/timer/sessions accepts limit, offset, and optional status (running, stopped, or discarded). It returns only sessions owned by the authenticated user.
Errors and Recovery
400shows an invalid date range, cross-organization link, or malformed business input.401shows missing or invalid authentication.403shows that the credential lackstracker:readortracker:write.404hides the difference between a missing record and a record outside the active organization.409shows a timer state conflict or another uniqueness conflict.422reports Zod validation failures with field paths and constraints.429shows a rate limit. HonorRetry-After.
After any ambiguous network failure, read the affected resource before you repeat a mutation. Project names are unique within an organization. General entry creation is not idempotent. The timer stop workflow and the remote-import deduplication workflow are the exceptions.