Okouro docs

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 for created_at, submitted_at, decided_at, and similar.
  • Calendar dates have no time component and look like 2026-01-05. Used for start_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:

ParamMeaningDefaultLimits
page1-based page number1floored at 1
per_pageitems per page20clamped 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=customer on clients).
  • role - comma-separated roles on GET /users (e.g. ?role=admin,operations).
  • consultant_id, mission_id - narrow to one consultant or mission.
  • scope - mine (default) or team on timesheets and expenses; team widens to consultants you manage.
  • Date bounds - e.g. date_from / date_to on expenses, period_start / period_end on 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.