Okouro docs

Consultants declare out-of-pocket expenses; operations settle them. An expense has a category, a gross amount, an optional VAT amount, and a link to the mission it belongs to (which decides whether it is billable to the client). It moves through pending, approved, paid, rejected, or cancelled.

Who does what

ActionRole
CreateThe consultant (own), an admin, or a team leader for their team
Update or cancelThe owner or an admin, while still pending (cancel also from approved)
Approve or rejectAdmin or the consultant’s team leader
PayAdmin or operations

1. Create an expense

POST /expenses HTTP/1.1
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "date": "2026-01-14",
  "category": "train",
  "description": "Client site visit",
  "amount": "120.00",
  "vat_amount": "24.00",
  "mission_id": "0190a1c2-...",
  "billable": true
}

Returns 201 Created with an ExpenseResponse. Key points:

  • amount is the gross figure; net_amount is derived and returned for you.
  • category is one of train, taxi, fuel, tolls, meal, hotel, office_supplies, software, telephony, other.
  • billable marks the expense for re-invoicing to the client; set mission_id to attach it to a mission.
  • receipt is an optional data URL (data:<mime>;base64,<data>); the response exposes a has_receipt boolean, and the full receipt only on GET /expenses/{id}.
{
  "id": "0190a1c2-...",
  "consultant_id": "0190a1c2-...",
  "consultant_full_name": "Ada Lovelace",
  "date": "2026-01-14",
  "category": "train",
  "description": "Client site visit",
  "amount": "120.00",
  "vat_amount": "24.00",
  "net_amount": "96.00",
  "mission_id": "0190a1c2-...",
  "mission_name": "Apollo",
  "billable": true,
  "has_receipt": false,
  "status": "pending",
  "created_at": "2026-01-14T12:00:00Z"
}

A consultant-role token with no linked profile cannot create an expense (422).

2. Edit or cancel before settlement

While pending, the owner or an admin can update the expense with PUT /expenses/{id} (same fields as create). Cancelling withdraws it:

POST /expenses/{id}/cancel HTTP/1.1
Authorization: Bearer <access_token>

Cancel is allowed from pending or approved, but not once paid (409 otherwise).

3. Approve or reject

A manager reviews a pending expense:

POST /expenses/{id}/approve HTTP/1.1
Authorization: Bearer <access_token>
POST /expenses/{id}/reject HTTP/1.1
Authorization: Bearer <access_token>
Content-Type: application/json

{ "reason": "No receipt attached" }

Both return 200 OK. Acting on an expense that is not pending returns 409.

4. Pay

Operations settles an approved expense:

POST /expenses/{id}/pay HTTP/1.1
Authorization: Bearer <access_token>

Returns 200 OK with status now paid. Paying an expense that is not approved returns 409.

The monthly summary

GET /expenses/summary rolls totals up by status, optionally for one month and one consultant:

GET /expenses/summary?month=2026-01 HTTP/1.1
Authorization: Bearer <access_token>
{
  "date_from": "2026-01-01",
  "date_to": "2026-01-31",
  "statuses": [
    { "status": "pending", "count": 3, "total": "420.00" },
    { "status": "paid", "count": 12, "total": "1850.00" }
  ]
}

Reading expenses

GET /expenses is paged and filterable by consultant_id, status, category, mission_id, date_from/date_to, search, and scope (mine/team). See Conventions.

Endpoints at a glance

ActionMethodPathSuccess
ListGET/expenses200
SummaryGET/expenses/summary200
GetGET/expenses/{id}200
CreatePOST/expenses201
UpdatePUT/expenses/{id}200
CancelPOST/expenses/{id}/cancel200
ApprovePOST/expenses/{id}/approve200
RejectPOST/expenses/{id}/reject200
PayPOST/expenses/{id}/pay200