Every route follows these conventions; a route’s own page says only where it differs.

The envelope

A read answers {offset, now, value}: the ledger offset it was read at, the engine’s clock, and the value. A read that takes at and asOf adds ledgerTime, the ledger time of that offset. An answer from the engine’s own records, such as an operation or an approval, carries offset 0.

Pages

A list answers {offset, now, value, next} and pages by limit, 1 to 1000, and after, the previous page’s next. Each list names its own order, and a page holds the records after the cursor in it; a cursor the list did not issue is refused with 400. next is null on a list’s last page and never null on a feed: GET /v1/events and GET /v1/audit-log.

Change windows

A list you keep in step by change takes updatedFrom and updatedTo, on its records’ updatedAt: from the first instant, inclusive, up to the second. Ask from the last updatedAt you have seen, and read every page. The lists that take it: GET /v1/subscriptions, GET /v1/redemptions, GET /v1/distributions, GET /v1/restrictions, GET /v1/holds.

Time zones

Every instant is UTC. A read that counts calendar days, months or years takes zone, an IANA time zone such as Asia/Seoul, and counts them there: a month starts at midnight in that zone, and so do a year and a day. Without it the read counts in UTC. The answer’s zone says which it counted in. The reads that take it: GET /v1/distribution-calendar, GET /v1/instruments/{instrumentId}/series, GET /v1/holders/{holderId}/statements, GET /v1/holders/{holderId}/tax-statements/{year}.

Reading at a point

A read of the ledger takes at, an offset, or asOf, an instant, never both, and reads the latest when it takes neither, so a report can be read again later exactly as it was. What depends on time, such as a credential’s validity, a holder’s registration or a window’s limit, is judged at that point: the instant asOf names, or the ledger time of the offset at names. An offset the projection does not hold is 409 offset-unavailable with Retry-After, and an instant before its history 409 before-horizon. The answer’s ledgerTime is the ledger time of the offset it read at. An instrument’s policy, the restrictions and the holds, which the engine keeps itself, take the two as well: each record as it stood then.

Idempotency

Send an Idempotency-Key with every write. The same key and body again answer 200 with the operation the write already is, and the key with another body is 422 idempotency-key-reused; without a key, a write is keyed on its body.

Operations

A write answers 202 with the operation it became and Location: /v1/operations/{operationId}. Read the operation until its state is succeeded, with its result, or failed, with its problem. A refusal comes as the call’s answer or, once the write runs, as its operation’s problem, with the code and status the route’s page lists. A few writes finish in the call: POST /v1/reconciliation-runs answers 201 with Location to the record; POST /v1/external-statements answers 201 with Location to the record; POST /v1/approvals/{actionKey}:reject answers 200 with the record; DELETE /v1/webhooks/{webhookId} answers 204 on its approver’s call. Operation results lists the fields each write that moves units or cash answers in its result.

Four eyes

A four-eyes route records its maker’s call and answers 202 with state awaiting, the action’s actionKey and Location: /v1/approvals/{actionKey}. It runs once an approver approves it by its key, POST /v1/approvals/{actionKey}:approve, or repeats the call. An approver is never the maker (same-approver) and never holds the auditor role (auditor-never-approves); a route that takes two approvers says so on its page. A rejected action never runs: the same call is then approval-rejected, and a new Idempotency-Key starts a new action.

Rate limits

A signed-in caller is rate limited per subject. Every answer it gets carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset, and a caller over its limit is answered 429 rate-limited with Retry-After. A public route is not limited.

Request ids and tracing

Every answer carries X-Request-Id: yours, 1 to 128 printable characters, or one the engine makes, and every problem repeats it as requestId. The engine joins a W3C traceparent and passes it on.

Decimals

A decimal is a JSON number in plain notation, never an exponent, and a figure from the ledger carries its 10 decimal places. An absent optional value is an explicit null. A decimal a caller sends is a number too, except where a statement’s own form takes a decimal string.

Times

An instant is RFC 3339 in UTC, ending in Z, with up to 9 fractional digits; a date is yyyy-MM-dd, a month uuuu-MM, and a duration ISO 8601, such as PT24H. A day, a month and a year are UTC unless a field names its zone.

Identifiers

A party id is hint::fingerprint: a readable hint, then the fingerprint of the party’s key. A contract id and an update id are hex strings the ledger assigns, and an offset is an integer it counts up. A record’s own id, such as a holder’s or a subscription’s, is the caller’s choice or the engine’s, and a four-eyes action is named by its actionKey.

Versions

Within /v1 the API only grows: a route, a field or a value is added, and nothing a client relies on is removed or renamed. After the first release, a breaking change goes to /v2. A set of values marked extensible may gain a value, which a client treats as unknown (Values).
Related: How the API works · Problems · Roles and access