A handful of conventions hold across the whole API. Learn them once and most endpoints behave predictably.
Identifiers
Record ids are UUIDs, generated time-ordered (version 7), so they sort roughly by creation. Treat them as opaque strings; never parse or assume structure.
0190a1c2-3d4e-7f80-9a1b-2c3d4e5f6a7b
You receive ids in responses and send them back in paths (/clients/{id}) or as foreign keys in bodies (client_id).
Dates and times
Two formats, depending on the field:
- Timestamps are RFC 3339 in UTC, e.g.
2026-01-05T09:00:00Z. Used forcreated_at,submitted_at,decided_at, and similar. - Calendar dates have no time component and look like
2026-01-05. Used forstart_date,end_date,period_start,date, and date filters.
Send dates in exactly these shapes. A malformed date is rejected with 400 or 422 depending on the endpoint.
Money
Amounts are decimal strings, not JSON numbers, to avoid floating-point drift:
{ "amount": "1250.00", "vat_amount": "250.00", "net_amount": "1000.00" }
Rules:
- Send a decimal string. Values are normalised to at most 2 decimal places.
- Negative amounts and values of 1,000,000,000 or more are rejected with
422 Unprocessable Entity. - A non-numeric string is rejected with
422. - The currency is set once per account (see organization settings) and is not carried on each amount. It is an uppercase ISO 4217 code, e.g.
EUR.
Remember from Roles and permissions that daily_cost and billing_rate come back as null for consultant-role tokens.
Pagination
List endpoints return the same envelope: an items array plus a meta object.
{
"items": [ { "...": "..." } ],
"meta": {
"total_items": 57,
"page": 1,
"per_page": 20,
"total_pages": 3
}
}
Two query parameters control paging:
| Param | Meaning | Default | Limits |
|---|---|---|---|
page | 1-based page number | 1 | floored at 1 |
per_page | items per page | 20 | clamped to 1..=100 |
Out-of-range values are clamped silently rather than rejected. total_pages is at least 1, so an empty result still reports one page.
GET /clients?page=2&per_page=50 HTTP/1.1
Authorization: Bearer <access_token>
Search and filters
List endpoints accept a search parameter for free-text matching. It is a case-insensitive “contains” match across a few relevant columns (names, emails, descriptions depending on the resource).
GET /clients?search=acme HTTP/1.1
Authorization: Bearer <access_token>
Beyond search, each list endpoint declares its own filters. Common ones:
status- filter by the resource’s status enum (e.g.?status=customeron clients).role- comma-separated roles onGET /users(e.g.?role=admin,operations).consultant_id,mission_id- narrow to one consultant or mission.scope-mine(default) orteamon timesheets and expenses;teamwidens to consultants you manage.- Date bounds - e.g.
date_from/date_toon expenses,period_start/period_endon timesheets.
There is no sort parameter; ordering is fixed per endpoint.
The global search endpoint is the one exception: GET /search uses ?q= (not ?search=) and returns grouped hits (up to five per category) across users, consultants, clients, missions, timesheets, and expenses.
Enumerations
Statuses, categories, and kinds are lowercase snake_case strings: draft, time_and_materials, office_supplies, rate_based. Currencies are the exception - uppercase ISO codes like EUR. The API Reference lists every enum’s allowed values.
Timesheet periods
Timesheets cover a period bounded by period_start and period_end (inclusive calendar dates). The period length follows the account’s cadence - weekly, bi-weekly, monthly, bi-monthly, or quarterly. Day entries inside a line use worked, half_day, or not_worked. See Track time.