API reference
List ledger entries
/ api/ v1/ ledgerThe audit chain, oldest first, as a resumable stream. Because the ledger is append-only and ascending by seq, a stored page.nextCursor is a watermark: a request from it never returns an entry before it. Persist the last non-null nextCursor only after processing every entry in the responses read, and resume from it; the last page, which had no cursor of its own, is returned again, so de-duplicate on seq.
seq is monotonic within a workspace but not gap-free; continuity is proven by the hash chain, not by seq.
Send a workspace API key as Authorization: Bearer ….
Parameters
limitintegeroptionalHow many items to return. Defaults to 50; a larger value is capped at 200.
Default:
50Allowed:1 to 200
cursorstringoptionalThe previous response's
page.nextCursor, passed back unchanged to continue. Opaque: never decode or construct one. A cursor this endpoint could not have issued is refused with400.domainstringoptionalOnly entries in this domain.
actorstringoptionalOnly entries written by this actor.
Try it
The key is kept in memory only, for this page.
Code samples
curl "https://www.vestiarion.xyz/api/v1/ledger" \
-H "Authorization: Bearer $VESTIARION_API_KEY"Response
Example{
"data": [
{
"seq": 82,
"id": "4566714f-a9f0-42c8-bcd4-d89adf830806",
"ts": "2026-09-24T11:55:00.370366+00:00",
"actor": "system",
"domain": "system",
"action": "seed",
"summary": "Seeded demo business: Northstar Studio",
"detail": {
"accounts": 3,
"invoices": 6,
"milestones": 3,
"amountScale": 0.001,
"counterparties": 7
},
"bodyHash": "8780d07cb3d083d359119e58fd4783f5e4b32aac14e0a8d9f7a5bb13365a4537",
"signature": "c14f06b751d31be6566ed11676d60d2db731ab8bbc5972f79d0a0c131e9e37200303d5b3ad04993b7973b1ce7200e7214e5a9b511306fa30538f072eb0d34705",
"prevHash": "0000000000000000000000000000000000000000000000000000000000000000",
"hash": "b0ac72908868ddd5ed4722fd8a14ff4a844eee4dad382c5c85288bca73737669",
"signingKeyId": null
}
],
"page": {
"nextCursor": "eyJrIjo4Mn0",
"hasMore": true,
"count": 1
}
}dataarray of object13 fields in data
seqnumberMonotonic within a workspace, but not gap-free; the hash chain proves continuity.
idstringtsstringactorstringdomainstringactionstringsummarystringdetailobjectbodyHashstringPresent so a consumer can verify the chain itself rather than trust us.
signaturestringprevHashstringhashstringsigningKeyIdstring · nullableWhich key signed the entry;
nullfor entries written before key identity existed. A consumer verifying for itself needs this to pick the right key.
pageobjectWhere this page sits in the collection.
3 fields in page
nextCursorstring · nullablePass back as
?cursor=to continue. Null when the end is reached.hasMorebooleanWhether another page follows this one.
countintegerHow many items this response carries.
Errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | An invalid limit or cursor, a filter value outside its allowed values, or a request body that does not validate. The message names the parameter or the field, and lists the accepted values. |
| 401 | unauthorized | No key, or a malformed, unknown or revoked one: "A valid API key is required." |
| 403 | forbidden | The key's scopes do not cover this route: "This key cannot do that." Or, on a write, the person who created the key can no longer add records: "This key's issuer can no longer add records in this workspace." |
| 500 | internal | An unexpected server error. Implementation details are not exposed. |
Notes
Resuming from a cursor
Persist page.nextCursor only after processing every entry in that response, and pass it back unchanged:
GET /api/v1/ledger?limit=100
GET /api/v1/ledger?limit=100&cursor=<previous page.nextCursor>Continue until hasMore is false and nextCursor is null. On the next poll, reuse the last non-null cursor you processed: it returns the entries after it, starting with the last page you read, then everything appended since, and never an entry before it. Store entries keyed by seq and skip the ones you already have. Pagination walks through it.
A malformed cursor answers 400 invalid_request. The API never silently restarts from the beginning.
seq and the hash chain
seq is a global identity column, so within one workspace it rises but is not gap-free. Continuity is proven by the hash chain, not by seq running without gaps: each entry's prevHash is the hash of the entry before it, and the first entry's prevHash is 64 zeros. Verify the ledger replays the chain and the signatures for you.
Filters
domain and actor match exactly. Unlike the enumerated filters on other endpoints, they accept any value: a value no entry has returns an empty page, not an error.
signingKeyId
Each entry names the key that signed it in signingKeyId: the first 16 hex characters of SHA-256 over the signing key's SPKI DER. Older entries carry null. The label is outside both the body hash and the chain hash: it selects which key to check against, and proves nothing by itself. Verifying a ledger entry's signature shows the check.