Skip to content

Export a range of this book as hledger journal files, verified by the pinned binary

POST
/v1/entities/{entity_id}/exports
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.

entity_id
required
Entity Id

The entity this path is relative to

string

The entity this path is relative to

Media typeapplication/json
ExportSubmission

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
year
Any of:
integer
>= 1 <= 9999
from
Any of:
string
to
Any of:
string
overwrite
Overwrite

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

boolean
default: true

Successful Response

Media typeapplication/json
ExportRun

What one export wrote: the immutable row, the state it was rendered from, and the files themselves.

object
entity_id
required
Entity Id
string
legal_name
required
Legal Name
string
base_currency
required
Base Currency
string
snapshot_seq
required
Snapshot Seq
integer
export_id
required
Export Id
string
snapshot_id
required
Snapshot Id
string
operation_id
required
Operation Id
string
state_hash
required
State Hash

The digest of everything rendered; one state is one snapshot

string
manifest_sha256
required
Manifest Sha256
string
range_start
required
Range Start

The inclusive first day actually covered (YYYY-MM-DD)

string
range_end
required
Range End

The inclusive last day actually covered (YYYY-MM-DD)

string
ledger_dir
required
Ledger Dir

The directory the files were published into, relative to this entity’s export root

string
artifacts
required
Artifacts
Array<object>
ExportArtifact

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
path
required
Path

The file name, relative to this export’s ledger directory

string
sha256
required
Sha256
string
bytes
required
Bytes
integer
href
required
Href

Where these bytes are served from

string
counts
required
Counts

Files, entries, postings, accounts, commodities and the source groups the range covers

object
key
additional properties
any
verifier
required
Verifier

The pinned hledger build that checked the output, and its verdict

object
key
additional properties
any
event_seq
required
Event Seq

The export.completed event’s sequence

integer
links
required
Links
object
key
additional properties
string

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

Media typeapplication/json
ErrorEnvelope
object
error
required
ErrorBody
object
code
required
Code
string
message
required
Message
string
retryable
required
Retryable
boolean
details
required
Details
Array<object>
object
key
additional properties
any
request_id
Any of:
string
next_action
Any of:
object
key
additional properties
any

Example generated

{
"error": {
"code": "example",
"message": "example",
"retryable": true,
"details": [
{}
],
"request_id": "example",
"next_action": {}
}
}

Unauthorized

Media typeapplication/json
ErrorEnvelope
object
error
required
ErrorBody
object
code
required
Code
string
message
required
Message
string
retryable
required
Retryable
boolean
details
required
Details
Array<object>
object
key
additional properties
any
request_id
Any of:
string
next_action
Any of:
object
key
additional properties
any

Example generated

{
"error": {
"code": "example",
"message": "example",
"retryable": true,
"details": [
{}
],
"request_id": "example",
"next_action": {}
}
}

Forbidden

Media typeapplication/json
ErrorEnvelope
object
error
required
ErrorBody
object
code
required
Code
string
message
required
Message
string
retryable
required
Retryable
boolean
details
required
Details
Array<object>
object
key
additional properties
any
request_id
Any of:
string
next_action
Any of:
object
key
additional properties
any

Example generated

{
"error": {
"code": "example",
"message": "example",
"retryable": true,
"details": [
{}
],
"request_id": "example",
"next_action": {}
}
}

Not Found

Media typeapplication/json
ErrorEnvelope
object
error
required
ErrorBody
object
code
required
Code
string
message
required
Message
string
retryable
required
Retryable
boolean
details
required
Details
Array<object>
object
key
additional properties
any
request_id
Any of:
string
next_action
Any of:
object
key
additional properties
any

Example generated

{
"error": {
"code": "example",
"message": "example",
"retryable": true,
"details": [
{}
],
"request_id": "example",
"next_action": {}
}
}

Conflict

Media typeapplication/json
ErrorEnvelope
object
error
required
ErrorBody
object
code
required
Code
string
message
required
Message
string
retryable
required
Retryable
boolean
details
required
Details
Array<object>
object
key
additional properties
any
request_id
Any of:
string
next_action
Any of:
object
key
additional properties
any

Example generated

{
"error": {
"code": "example",
"message": "example",
"retryable": true,
"details": [
{}
],
"request_id": "example",
"next_action": {}
}
}

Precondition Failed

Media typeapplication/json
ErrorEnvelope
object
error
required
ErrorBody
object
code
required
Code
string
message
required
Message
string
retryable
required
Retryable
boolean
details
required
Details
Array<object>
object
key
additional properties
any
request_id
Any of:
string
next_action
Any of:
object
key
additional properties
any

Example generated

{
"error": {
"code": "example",
"message": "example",
"retryable": true,
"details": [
{}
],
"request_id": "example",
"next_action": {}
}
}

Unprocessable Content

Media typeapplication/json
ErrorEnvelope
object
error
required
ErrorBody
object
code
required
Code
string
message
required
Message
string
retryable
required
Retryable
boolean
details
required
Details
Array<object>
object
key
additional properties
any
request_id
Any of:
string
next_action
Any of:
object
key
additional properties
any

Example generated

{
"error": {
"code": "example",
"message": "example",
"retryable": true,
"details": [
{}
],
"request_id": "example",
"next_action": {}
}
}

Internal Server Error

Media typeapplication/json
ErrorEnvelope
object
error
required
ErrorBody
object
code
required
Code
string
message
required
Message
string
retryable
required
Retryable
boolean
details
required
Details
Array<object>
object
key
additional properties
any
request_id
Any of:
string
next_action
Any of:
object
key
additional properties
any

Example generated

{
"error": {
"code": "example",
"message": "example",
"retryable": true,
"details": [
{}
],
"request_id": "example",
"next_action": {}
}
}

Service Unavailable

Media typeapplication/json
ErrorEnvelope
object
error
required
ErrorBody
object
code
required
Code
string
message
required
Message
string
retryable
required
Retryable
boolean
details
required
Details
Array<object>
object
key
additional properties
any
request_id
Any of:
string
next_action
Any of:
object
key
additional properties
any

Example generated

{
"error": {
"code": "example",
"message": "example",
"retryable": true,
"details": [
{}
],
"request_id": "example",
"next_action": {}
}
}