Export a range of this book as hledger journal files, verified by the pinned binary
const url = 'https://example.com/v1/entities/example/exports';const options = { method: 'POST', headers: {'Content-Type': 'application/json'}, body: '{"year":1,"from":"example","to":"example","overwrite":true}'};
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/exports \ --header 'Content-Type: application/json' \ --data '{ "year": 1, "from": "example", "to": "example", "overwrite": true }'Render the books as a journal anybody can read with hledger, and record that it was done.
This is the same export bin/haddon-export runs on the machine. It reads the store, renders
deterministically, stages the files, runs hledger check --strict on them under the pinned binary,
publishes them only if that passes, and then writes one export operation, a snapshot, the immutable
exports row and an export.completed event. A strict-check failure publishes nothing and records
nothing.
The reply is recorded in the same transaction as the export (export_book(also_record=...)), so a retry
under the same Idempotency-Key replays the original 201 instead of exporting again, and there is no
window in which the export is durable and its reply is not. Two exports of one entity are serialised,
because the directory they publish into is exclusively managed.
The work runs in a worker thread. Every other write here is a few SQLite statements; this one renders a
whole range and waits on a subprocess, which on the event loop would stop the process answering anything,
/healthz included, until it finished. The lock is taken inside that thread for the same reason.
A range with nothing posted in it is refused (422): an empty directory is not an export.
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”Which range of this book to render as hledger journal files.
The default is the whole journal, which is the exporter’s own default: a journal is continuous (issue 0002),
so there is no natural default year. year is only a calendar-year shorthand, exactly from the first
of January to the thirty-first of December, and may not be combined with either bound. Either bound may
be left open on its own, in which case it is filled from what is actually posted.
There is no reason. Every write that decides something takes one; an export decides nothing, it reads
the book and renders it (docs/design/decisions.md, “Chunk C2a”). What is recorded is the range, the
actor and the state that was rendered.
object
Replace an export already published for this range. True by default: the directory holds one export per range and is exclusively managed by this service, so there is nothing of anybody else’s to overwrite – an unmanaged file in it refuses the whole export either way
Responses
Section titled “Responses”Successful Response
What one export wrote: the immutable row, the state it was rendered from, and the files themselves.
object
The digest of everything rendered; one state is one snapshot
The inclusive first day actually covered (YYYY-MM-DD)
The inclusive last day actually covered (YYYY-MM-DD)
The directory the files were published into, relative to this entity’s export root
One published file: its name in the ledger directory, its digest and its length.
The same three fields the exports row stores, plus where to fetch it. The digest is the integrity
claim that these are the bytes hledger check --strict passed under the pinned binary, and the download
re-hashes the file against it rather than trusting the directory.
object
The file name, relative to this export’s ledger directory
Where these bytes are served from
Files, entries, postings, accounts, commodities and the source groups the range covers
object
The pinned hledger build that checked the output, and its verdict
object
The export.completed event’s sequence
object
Example generated
{ "entity_id": "example", "legal_name": "example", "base_currency": "example", "snapshot_seq": 1, "export_id": "example", "snapshot_id": "example", "operation_id": "example", "state_hash": "example", "manifest_sha256": "example", "range_start": "example", "range_end": "example", "ledger_dir": "example", "artifacts": [ { "path": "example", "sha256": "example", "bytes": 1, "href": "example" } ], "counts": {}, "verifier": {}, "event_seq": 1, "links": { "additionalProperty": "example" }}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": {} }}