Load one statement: an account, a period, an opening and closing balance, lines, and the document
const url = 'https://example.com/v1/entities/example/statements';const options = { method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{"account":"example","commodity":"example","period_start":"example","period_end":"example","opening":"-12.00","closing":"-12.00","lines":[{"dt":"example","description":"example","amount":"-12.00","currency":"example","reference":"example","raw":"example","source_key":"example"}],"document":{"sha256":"example","filename":"example","media_type":"example","size_bytes":1,"blob_path":"","external_ref":"example"},"locator":"example","page":1,"reason":"example","id":"example"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://example.com/v1/entities/example/statements \ --header 'Content-Type: application/json' \ --data '{ "account": "example", "commodity": "example", "period_start": "example", "period_end": "example", "opening": "-12.00", "closing": "-12.00", "lines": [ { "dt": "example", "description": "example", "amount": "-12.00", "currency": "example", "reference": "example", "raw": "example", "source_key": "example" } ], "document": { "sha256": "example", "filename": "example", "media_type": "example", "size_bytes": 1, "blob_path": "", "external_ref": "example" }, "locator": "example", "page": 1, "reason": "example", "id": "example" }'Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”The entity this path is relative to
The entity this path is relative to
Request Bodyrequired
Section titled “Request Bodyrequired”A statement: one account, one period, an opening and a closing balance, a list of lines, and the document.
There is no file format here. Haddon parses nothing: whatever the bank produced (CSV, XLSX, PDF, a pasted table) is read outside the ledger and submitted as normalised lines. The ledger enforces what a parser must not be trusted with, and refuses the whole submission if any of it fails:
- the lines must sum to
closingminusopening; - every line’s
dtmust fall insideperiod_start..period_end; - the currency must be declared, on the statement and on every line, and they must agree;
- the same document cannot be loaded twice (by
document.sha256).
opening is checked against the closing balance already recorded for the period ending the day before,
and a disagreement is refused. When there is no such record the statement is still accepted, and the
response says opening_verified: false with an opening_basis naming why. It is never assumed to be 0.
object
Account path (preferred) or stable account id
First day of the statement period (YYYY-MM-DD)
Last day of the statement period; inclusive, and free to cross a year
Signed decimal amount in whole commodity units, as a string; never a JSON number. Converted by the commodity’s declared precision, so more decimal places than the currency has is refused rather than rounded.
Signed decimal amount in whole commodity units, as a string; never a JSON number. Converted by the commodity’s declared precision, so more decimal places than the currency has is refused rather than rounded.
One normalised statement line.
currency is required and must equal the statement’s commodity: the ledger declares currency, it never
infers it.
object
The line’s value date (YYYY-MM-DD); must fall inside the declared period
Signed decimal amount in whole commodity units, as a string; never a JSON number. Converted by the commodity’s declared precision, so more decimal places than the currency has is refused rather than rounded.
The original document, by reference. The ledger records this and does not fetch it.
sha256 is the hash of the bytes the agent read, and the key that makes a second load of the same
statement a refusal. Give exactly one of blob_path (bytes this service holds) and external_ref
(bytes held elsewhere).
object
SHA-256 of the original document, 64 lowercase hex characters
Size of the original document in bytes
Where the original document is kept
Where in the document the closing balance is printed
Why this statement is being loaded; kept in the record
Responses
Section titled “Responses”Successful Response
The reply to loading or replacing a statement: the statement, and the action that recorded it.
The read routes use StatementResponse, which has no audit_seq because a read has no action behind
it. Hence a separate model rather than an optional field.
object
object
Signed integer micro-units (millionths) as a base-10 string; never a JSON number.
Signed integer micro-units (millionths) as a base-10 string; never a JSON number.
True only when the opening equals a recorded prior closing
Which of the three opening cases this is
Signed integer micro-units (millionths) as a base-10 string; never a JSON number.
The original document as a reader sees it: what identifies it, and whether it can be opened here.
There is deliberately no blob_path (removed in issue 0007): a server-local path is no use to a reviewer
and discloses where the service keeps files. storage says instead whether the bytes are available:
stored means GET /documents/{id}/content will hand them over, external means only
external_ref exists and the document is held elsewhere.
Nothing replaces this statement
object
The statement.loaded / statement.replaced action’s event sequence
Example
{ "statement": { "opening_micros": "-12000000", "closing_micros": "-12000000", "lines_micros": "-12000000" }}Bad Request
object
Example generated
{ "error": { "code": "example", "message": "example", "retryable": true, "details": [ {} ], "request_id": "example", "next_action": {} }}Unauthorized
object
Example generated
{ "error": { "code": "example", "message": "example", "retryable": true, "details": [ {} ], "request_id": "example", "next_action": {} }}Forbidden
object
Example generated
{ "error": { "code": "example", "message": "example", "retryable": true, "details": [ {} ], "request_id": "example", "next_action": {} }}Not Found
object
Example generated
{ "error": { "code": "example", "message": "example", "retryable": true, "details": [ {} ], "request_id": "example", "next_action": {} }}Conflict
object
Example generated
{ "error": { "code": "example", "message": "example", "retryable": true, "details": [ {} ], "request_id": "example", "next_action": {} }}Precondition Failed
object
Example generated
{ "error": { "code": "example", "message": "example", "retryable": true, "details": [ {} ], "request_id": "example", "next_action": {} }}Unprocessable Content
object
Example generated
{ "error": { "code": "example", "message": "example", "retryable": true, "details": [ {} ], "request_id": "example", "next_action": {} }}Internal Server Error
object
Example generated
{ "error": { "code": "example", "message": "example", "retryable": true, "details": [ {} ], "request_id": "example", "next_action": {} }}Service Unavailable
object
Example generated
{ "error": { "code": "example", "message": "example", "retryable": true, "details": [ {} ], "request_id": "example", "next_action": {} }}