Every endpoint shares one response shape, one pagination contract and one error format. Learn them once.

Response envelope

Successful responses wrap the payload in data:
meta.timestamp is always present. meta.requestId appears when the request carried an id, and correlates the response with our server logs — quote it when reporting a problem.

Errors

Every 4xx and 5xx uses the error envelope:
Branch on error.code, not error.messagecode is the stable field. details is populated for validation failures and null otherwise; entries for enum fields also carry allowedValues.
A malformed identifier returns 422, not 400. Anything that is not a 24-character hexadecimal id is rejected before it reaches the database, with error.details[0].field naming the offending parameter.

Pagination

List endpoints accept page and limit and return:
  • limit defaults to 20 and is capped at 100. Above the cap is a 422, not a silent clamp.
  • page or limit of 0, a negative number, or a non-numeric string is a 422. These were previously ignored and replaced with defaults.
  • Page through with hasNextPage and nextPage rather than requesting an oversized page.
GET /api/v1/transactions/holders defaults to a page size of 10, not 20, and returns a summary alongside docs. That summary covers the whole filtered set rather than the current page, so it does not change as you page.

Sorting

sort takes field names separated by commas or spaces, each optionally prefixed with - for descending. Both ?sort=name,-createdAt and ?sort=name -createdAt are accepted and mean the same thing. The default is -createdAt. A malformed value such as ?sort=- is a 422 naming sort.

Unknown parameters

Query keys an endpoint does not declare are stripped before the handler runs, not rejected. A request carrying one still returns 200; the parameter simply has no effect.

Numeric precision

Share quantities are decimal strings, not numbers. quantity, and the ledger’s balanceBefore and balanceAfter, are exact decimal strings — parse them with a decimal library. Number() loses precision beyond 2^53.The exception is GET /api/v1/transactions/holders, which returns shares, ownershipPercent and its summary totals as JSON numbers. Do not cross-foot the two without converting deliberately, and never reconcile against ownershipPercent — it is derived and rounded for display.