Skip to content

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.

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.

Terminal window
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.

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.

Terminal window
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.

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.

Terminal window
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" ] && break
done
  • 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 limit and still carry next. 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 without next, 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.

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.

Terminal window
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.

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.

Terminal window
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.

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.