Skip to content

API conventions

  • JSON in and out, content-type: application/json. Request bodies have a size cap; documents travel base64-encoded in JSON.
  • GET reads, POST changes. Deletes are POST .../delete, and most changes are named actions such as POST /api/invoices/:id/hold.
  • Responses carry Cache-Control: no-store and security headers (CSP, X-Frame-Options: DENY, Referrer-Policy: no-referrer).

Every error has the same shape:

{ "error": { "code": "invalid_request", "message": "Request did not validate", "details": [{ "path": "lines.0.quantity", "message": "Number must be greater than 0" }] } }
StatusCodesMeaning
400invalid_requestThe body or query did not validate; details lists each problem
401unauthenticatedNo valid token
403permission_denied, sod_violation, staff_onlyThe person lacks the duty, or doing it would break segregation of duties
404not_foundNo such record in this tenant
409invalid_stateThe record is not in a state that allows the action
422policy_violation and other domain codesThe policy pack does not allow it, such as a late bid
500internalLogged on the server

Every list endpoint takes the same query parameters:

ParameterMeaning
qFree text, matched case-insensitively against the row’s search text
sort, dirA sortable column and asc or desc
page1-based
pageSizeDefault 25, at most 5000 so an export can take everything
any declared filterSuch as status=exception,on_hold; comma-separated values mean “any of”

The response holds the rows under a named key (invoices, requisitions…), plus page (page, pageSize, total, pages, sort, dir, q) and facets: counts for each filter’s values, computed before that filter applies.

  • Amounts in records are integers in minor units (cents), in fields ending Minor, such as totalMinor. Some request bodies, such as a manually entered invoice, take dollars as numbers; the reference says which.
  • Dates are YYYY-MM-DD; times are ISO 8601 in UTC.
  • Ids are opaque strings with a type prefix, such as inv_... or sup_....