Reading the book
Everything you can ask the book is a GET on ingress, with your bearer token. A read never changes
anything and never enters the log of commands. Each read is answered from one consistent view of
the book. The replies to balances, movements and trial-balance say which, in view: the
sequence number the next posting will take. The answer includes every posting below it and none at
or above it, so the last posting it includes has seq one less. A statement reply carries no view:
to pin a statement with as_of, take the view from a balances or movements reply.
The examples below use three shell variables: $INGRESS (the service’s address), $TOKEN (your
bearer token) and $ACCOUNT (a did:aledgent:acct: identifier you can reach). Amounts are
decimal-integer strings of minor units, and times are milliseconds since the Unix epoch.
You see only what your credential reaches. An account outside that reach is refused as
ERR-UnknownAccount, exactly as if it did not exist.
Balances
Section titled “Balances”GET /v1/balances answers “how much is there, and in what state”. Name exactly one subject:
account for one account, or identity for every account of that identity you reach. asset
narrows the answer to one asset; without it you get a row for every asset the account has held.
curl -G "$INGRESS/v1/balances" \ -H "Authorization: Bearer $TOKEN" \ --data-urlencode "account=$ACCOUNT" \ --data-urlencode "asset=USD"{ "view": 4181, "rows": [ { "account": "did:aledgent:acct:…", "asset": "USD", "available": "970", "reserved": "0", "encumbered": "0", "total": "970" } ]}Each kind of balance is labelled, and total is their sum. Only available decides whether a spend
passes. reserved is value held for open reservations, and encumbered is held under standing rules
(always zero for now). A treasury account may be below zero, so each figure is a signed string.
Reference: balances.
Movements
Section titled “Movements”GET /v1/movements lists the entries made against an account, oldest first, a page at a time. A
movement is one entry against one account: a transfer between two accounts shows as a debit on one
and a credit on the other.
Subject (required, exactly one). account, or identity for every account of it you reach.
Filters (all optional, and they combine).
| Parameter | Keeps only |
|---|---|
on_account |
Movements on this account. It must be one of the subject’s, so it is most useful when the subject is an identity |
asset |
Movements in this asset, by its symbol |
direction |
debit or credit. Any other value is refused |
counterparty |
Movements against this ledger account, the one on the other side |
from, to |
Movements in a period, in milliseconds since the Unix epoch. from is inclusive and to is exclusive, so a movement exactly at to is not returned, and consecutive periods that share a boundary never overlap. Send both or neither |
Page (limit required). limit is the page size, from 1 to 1000. There is no default and no
way to ask for everything.
curl -G "$INGRESS/v1/movements" \ -H "Authorization: Bearer $TOKEN" \ --data-urlencode "account=$ACCOUNT" \ --data-urlencode "asset=USD" \ --data-urlencode "direction=debit" \ --data-urlencode "from=1760000000000" \ --data-urlencode "to=1760600000000" \ --data-urlencode "limit=2"{ "view": 4181, "movements": [ { "seq": 4102, "at": 1760021840112, "kind": "transfer", "status": "completed", "operation": "did:aledgent:op:…", "account": "did:aledgent:acct:…", "balance": "available", "asset": "USD", "direction": "debit", "amount": "10", "counterparty": "did:aledgent:acct:…", "authorities": [ { "kind": "owner", "principal": "did:aledgent:id:…", "credential": "did:aledgent:cred:…" } ], "reference": { "invoice": "inv-8812" } } ], "next": "9c41…"}status is held, completed, returned, reversed or opening. balance says which kind of
balance the entry moved: available, reserved or encumbered. authorities names who caused the
entry and under what grant. counterparty is absent where there is no account on the other side.
reference is the pass-through data its operation was submitted with, exactly as sent. It is shown
only to principals of the integrating party that submitted it.
Paging
Section titled “Paging”When more movements match, the reply carries next. Pass it back unchanged as cursor, with the
same subject and filters, to get the following page. Keep going until a reply comes without
next.
cursor=""while :; do url="$INGRESS/v1/movements?account=$ACCOUNT&limit=100" [ -n "$cursor" ] && url="$url&cursor=$cursor" page=$(curl -s -H "Authorization: Bearer $TOKEN" "$url") echo "$page" | jq -c '.movements[]' cursor=$(echo "$page" | jq -r '.next // empty') [ -z "$cursor" ] && breakdone- Every page of one list is read at the first page’s view, so postings committed while you page never shift or duplicate what you have not read yet.
- A page can hold fewer movements than
limitand still carrynext. A page reads a bounded number of postings, so with a sparse filter it may stop early. The end of the list is a reply withoutnext, not a short page. - A cursor belongs to the request that produced it. Change the subject or any filter and the cursor is refused. The page size may change between pages.
Reference: movements.
Statements
Section titled “Statements”GET /v1/statements answers “what happened on this account, in this asset, over this period, and
what were the balances either side of it”. Unlike movements, it returns the whole period in one
reply, with the opening and closing balances.
All of account, asset, from and to are required. from is inclusive and to is exclusive,
in milliseconds since the Unix epoch.
curl -G "$INGRESS/v1/statements" \ -H "Authorization: Bearer $TOKEN" \ --data-urlencode "account=$ACCOUNT" \ --data-urlencode "asset=USD" \ --data-urlencode "from=1760000000000" \ --data-urlencode "to=1760600000000"{ "account": "did:aledgent:acct:…", "asset": "USD", "from": 1760000000000, "to": 1760600000000, "from_seq": 4101, "to_seq": 4108, "closed": true, "opening": { "available": "1000", "reserved": "0", "encumbered": "0" }, "movements": [ { "seq": 4102, "at": 1760021840112, "kind": "transfer", "status": "completed", "operation": "did:aledgent:op:…", "account": "did:aledgent:acct:…", "balance": "available", "asset": "USD", "direction": "debit", "amount": "10", "counterparty": "did:aledgent:acct:…", "authorities": [ { "kind": "owner", "principal": "did:aledgent:id:…", "credential": "did:aledgent:cred:…" } ] }, { "seq": 4107, "at": 1760133011874, "kind": "transfer", "status": "completed", "operation": "did:aledgent:op:…", "account": "did:aledgent:acct:…", "balance": "available", "asset": "USD", "direction": "debit", "amount": "10", "counterparty": "did:aledgent:acct:…", "authorities": [ { "kind": "owner", "principal": "did:aledgent:id:…", "credential": "did:aledgent:cred:…" } ] } ], "closing": { "available": "980", "reserved": "0", "encumbered": "0" }}The closing balances are the opening balances plus the movements, so a statement balances by
construction. closed says whether the period is over, so that no posting can still enter it.
To get a statement that must not move, pass as_of: a view, that is, the view that an earlier
balances or movements read returned. The statement is then computed as the book stood at that view, and asking again with the
same as_of returns the same statement byte for byte. A view the book has not reached is refused.
A period with more than 1000 movements is refused rather than truncated. Ask for a shorter period, or page through movements instead.
Reference: statement.
Trial balance
Section titled “Trial balance”GET /v1/trial-balance checks that the book adds up. It is the operator’s: any other caller is
refused as ERR-NotAuthorised. asset narrows the check to one asset. It also accepts amount,
which belongs to can_spend, and ignores it.
curl -G "$INGRESS/v1/trial-balance" -H "Authorization: Bearer $TOKEN"{ "view": 4181, "accounts": 212, "unbalanced": [], "rows": [ { "asset": "USD", "openings": "0" } ]}It makes two checks, and both should say zero. unbalanced lists every currency and layer whose
accounts do not sum to zero, and is empty when the book balances. rows gives, per asset, the signed
sum of the opening entries, which is "0" when they balance. accounts is how many underlying
accounts the first check summed.
Reference: trial_balance.
What a refused read looks like
Section titled “What a refused read looks like”A refused read has the same shape as any other refusal: an error code from the closed list and its
fixed description, with the status the code carries. Nothing is partly answered.
{ "error": "ERR-QueryUnbounded", "description": "A query was submitted without bounds that aledgent can serve (`REQ-CS-5`)"}| You sent | Refused as | Status |
|---|---|---|
| A parameter the route does not know or repeats, on any read | ERR-MalformedCommand |
400 |
A value that does not parse (limit=ten, from=abc, a negative number) |
ERR-MalformedCommand |
400 |
An asset that is not 2 to 12 letters and digits (asset=US$), or an alias source that cannot be one |
ERR-MalformedCommand |
400 |
A direction that is neither debit nor credit |
ERR-MalformedCommand |
400 |
| An account, identity or operation identifier that is not valid, in a parameter or in the path | ERR-MalformedCommand |
400 |
movements with a cursor that is not hex |
ERR-MalformedCommand |
400 |
balances or movements with both account and identity, or with neither |
ERR-QueryUnbounded |
422 |
movements with no limit, or limit=0, or a limit above 1000 (however many digits it has) |
ERR-QueryUnbounded |
422 |
movements with only one of from and to, or from after to |
ERR-QueryUnbounded |
422 |
movements with a hex cursor that came from a different subject or filter |
ERR-QueryUnbounded |
422 |
statements with no account, asset, from or to, or with from after to |
ERR-QueryUnbounded |
422 |
statements over more than 1000 movements, or with an as_of the book has not reached |
ERR-QueryUnbounded |
422 |
| A valid account you cannot reach | ERR-UnknownAccount |
404 |
movements with an on_account that is not one of the subject’s accounts |
ERR-UnknownAccount |
404 |
| A valid asset symbol that is not in the registry | ERR-UnknownAsset |
404 |
disclosures/verify with a missing or non-hex disclosure |
ERR-DisclosureUnsupported |
422 |
The rule behind the table: a request that is the wrong shape is ERR-MalformedCommand, whichever
read it is sent to, and a request that is missing something or whose bounds conflict is
ERR-QueryUnbounded. A valid identifier that names nothing you may read keeps its own condition.
- Getting started: authenticate and send a transfer.
- The operation lifecycle: how the entries you read here come to exist.
- API reference: every route and every parameter.
© 2026 Pagos Cube LLC · Code: Apache-2.0 OR MIT · agents/: AGPL-3.0-or-later · Docs: CC-BY-4.0