Expenses and meal vouchers
Two reads that a finance integration consumes together: what employees claimed back (expense lines) and how many meal vouchers each is owed (counts).
Neither withholds anything. There is no restricted block on either read — so a missing field here is a
bug, not a refusal, and worth reporting as one.
Expense lines
GET /api/v1/client/expense-reports (scopes: expenses:read)
{
"items": [
{
"id": "eV7dMJ",
"requestId": "ePlQXN",
"employeeId": "GNnBNO",
"companyId": "1VaKPa",
"functionalNumber": "NDF-2026-0042",
"amount": 42.50,
"currency": "EUR",
"isForeignExpense": false,
"expenseDate": "2026-03-12",
"createdOn": "<generated>",
"natureCode": "TRANSPORT",
"natureName": "Transport",
"status": "Completed"
},
{
"id": "0V6Y0N",
"requestId": "ePlQXN",
"employeeId": "GNnBNO",
"companyId": "1VaKPa",
"functionalNumber": "NDF-2026-0042",
"amount": 180,
"currency": "GBP",
"isForeignExpense": true,
"expenseDate": "2026-03-13",
"createdOn": "<generated>",
"natureCode": "HEBERGEMENT",
"natureName": "Hebergement",
"status": "Completed"
}
],
"nextCursor": "qq9Ygc1ab"
}
| Parameter | What it does |
|---|---|
from, to |
ISO dates matching the expense date by containment. |
establishmentId |
Narrows the page to ONE establishment of your perimeter. Omitted reads all of it. |
cursor, limit |
Paging. Pass nextCursor back; stop only when it is null. |
The path says expense-reports; the read serves expense lines. One entry is one expense — one
employee, one day, one nature, one amount — not a whole report. Lines come out oldest expense date first.
There is no report total, and there cannot be one
The amount lives on the line, with its own currency, and one report may mix two of them — a taxi in
euros and a hotel in pounds on the same claim, which is exactly what the example above shows.
A report-level total would therefore be a sum across currencies: a number that is wrong and looks right.
Group on requestId to regroup the lines of one report, then total them per currency yourself.
amount is meaningless without currency. The two are always served together. Never add amounts without
reading the currency, never report a total without saying which currency it is in, and never convert between
them on your own — you do not know the rate the client books at.
isForeignExpense says the employee declared the expense as incurred abroad. That is what tells you a line in
another currency is expected rather than a data error.
Two identifiers, two uses
| Field | Use it for |
|---|---|
requestId |
Grouping the lines of one report. |
functionalNumber |
Talking about a report — it is the human-readable number printed on the screens your client reads. |
Quote the functional number back to a user; group on requestId.
Two dates, two uses
expenseDate is the day the expense was incurred. createdOn is when the line was entered, which can
be weeks later. Reconcile on expenseDate, poll on createdOn.
No free text is served, by design
The report, the line, the payment and each status change all carry a comment in Staff & Go, and none of them crosses. They are typed by employees, unreviewed, and may name a third party or explain a medical trip.
Do not ask for them, and do not infer a justification you were not given. No internal identity crosses either — who created the report, who paid it.
status is the report's, repeated on every line
ActionPending, ActionTerminated, Completed, ActionFailed, Aborted. The lines of one report are
submitted, approved and paid together, so they carry the same code — it is not a per-line state.
Unsubmitted drafts are never served. A draft is an employee's unsent intention, so nothing you receive here is a claim that has not been filed.
Meal voucher counts
GET /api/v1/client/meal-voucher-counts (scopes: meal-vouchers:read)
{
"items": [
{
"id": "YJb51V",
"employeeId": "GNnBNO",
"companyId": "1VaKPa",
"periodFrom": "2026-03-01",
"periodTo": "2026-03-31",
"vouchersCount": 19,
"status": "Validated"
},
{
"id": "OVD2pP",
"employeeId": "rPX1No",
"companyId": "1VaKPa",
"periodFrom": "2026-03-01",
"periodTo": "2026-03-31",
"vouchersCount": 0,
"status": "Aborted"
}
],
"nextCursor": "gx2YbiGpG"
}
| Parameter | What it does |
|---|---|
from, to |
ISO dates matching the period by overlap. |
establishmentId |
Narrows the page to ONE establishment of your perimeter. Omitted reads all of it. |
cursor, limit |
Paging. Pass nextCursor back; stop only when it is null. |
One entry is one employee for one calculated period: how many vouchers they are owed. This is the number a voucher issuer orders on. Counts come out oldest period first.
The window matches by overlap — unlike the expense lines just above, and unlike worked time. A calculation period is a range, and one running from the 26th to the 25th belongs to both months. Ordering on a month boundary without allowing for that misses a calculation.
status is the employee's, not the batch's
A calculation can be transmitted while one employee was aborted out of it. So order on the rows whose own
status says they are settled — Validated or Transmitted — rather than on everything a period returns.
The full set is Created, InTreatment, InProgress, InTransmission, Validated, Transmitted,
Treated, InError, InTransmissionError and Aborted.
The example above shows the trap directly: two employees, one period, one Validated at 19 vouchers and one
Aborted at 0. Ordering 19 is right; ordering 19 and treating the second row as a genuine zero is not.
vouchersCount is a result, not a working
Staff & Go computes it from four coefficients — the allowance, an adjustment, and what leaves and expenses took off — and none of them is served. There is nothing to recompute here and nothing that adds up to a different number.
periodFrom and periodTo are the calculation's window, so every employee of one period carries the same
one. They are not per-employee dates and say nothing about when that employee was present.
The employee is named by employeeId only — no name, no employee number, no job function. Resolve it against
the employee read if you need to show a person.
This read orders nothing and issues nothing. It reports what Staff & Go calculated.
When the page is short
An establishment that lacks the expense-report or meal-voucher module is absent from these reads without being
named, and the answer says so through the partial block — see When a listing is shorter than your
mandate in Discovering what you can call.
Neither read offers a modifiedSince filter: no maintained, indexed column backs one. Re-read the window
instead. A row that vanishes between two reads is absent, not deleted — there is no delta feed and no
tombstone.