Verified export bundles¶
Use the web app or authenticated HTTP API to export document versions,
retained email PDFs, attachment originals, Markdown text, and page images.
Use docbank export or the native MCP export tools
for original files selected by exact document-version identity.
Docbank freezes the selection and role receipts before writing the archive. A later edit to a saved query, tag, document head, or processing result does not change an admitted plan.
In the web app, choose Export for selected documents or a whole frozen query. A completed mailbox import also offers Export completed collection. Review the frozen counts, start the export, then download its verified ZIP. Closing the drawer does not cancel an admitted job. Reopen it in the same browser session to reconnect. Original files keep their original bytes. The bundle does not redact or sanitize them.
Export original files from the CLI¶
Create a JSON request with two caller-chosen UUIDv4 operation IDs and 1–1,000 document versions. Obtain the node ID, version ID, SHA-256, and size from the document's receipts. This synthetic request selects one empty original:
{
"source_operation_id": "11111111-1111-4111-8111-111111111111",
"plan_operation_id": "22222222-2222-4222-8222-222222222222",
"members": [{
"node_id": 12,
"version_id": "33333333-3333-4333-8333-333333333333",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"size": 0
}]
}
Replace the synthetic identities with your own receipts. Save the request as
selection.json, then run:
docbank export preview --request selection.json --json
docbank export start <plan-id> --fingerprint <fingerprint> --operation-id <job-id> --json
docbank export status <job-id> --json
docbank export download <job-id> ./originals.zip
docbank export release <job-id>
Review the preview before starting. It freezes the selection, reports planned
original and metadata bytes, and returns a plan ID and fingerprint. Supply those
values to start with a new job UUIDv4. Start returns immediately. Use status
to observe progress. Download requires completed. Use cancel <job-id> to
request cancellation of active work. Exiting the CLI does not cancel it.
All export commands accept --json. Preview returns the plan, start/status return
the job, and download returns the verified archive receipt after saving the file.
Cancel returns {"job_id":"…","accepted":true}, and release returns
{"job_id":"…","released":true}. Reading the status of a failed job still
succeeds. Inspect its state and failure.
The request file is limited to 1 MiB. Unknown fields and invalid values are
usage errors checked before daemon access. IDs must be canonical UUIDv4 strings,
hashes lowercase hexadecimal, node IDs positive, and sizes nonnegative. An
optional nonnegative revision checks the node revision. Zero or omission adds
no precondition. Distinct retained versions of one node are allowed, but
duplicate node/version pairs are not.
The daemon checks the complete identity and size. It never substitutes the current version. Names and paths are frozen at preview time. Only the selected originals are exported: no descendants, attachments, or renditions are added.
You can explicitly select a published attachment as an ordinary original. Omitting it excludes its separate file and manifest row. An exported original email still contains every MIME part in its original bytes; selecting only the parent does not redact its attachments.
Keep the request and operation IDs for retries. Repeat exactly the same request
after a lost response. Changed input under an existing ID conflicts. After a
long interruption, inspect the known job ID or repeat start, rather than
replaying preview: the source's ten-minute replay window can expire while its
job remains available.
Download streams to a private stage beside the destination, verifies the ZIP
against the job fingerprint and receipts, then publishes the complete file.
The parent directory must exist and be outside Docbank's data directory. An
existing destination is preserved unless you pass --overwrite. Replacement
happens only after verification. Pre-publication failures leave it untouched.
An error after publication reports that the verified file is already saved.
Download does not release the retained job automatically.
Inspect a retained plan¶
Keep the plan ID returned by preview. Read its saved header and any unavailable outputs without creating another preview:
docbank export show-plan <plan-id> --json
docbank export problems <plan-id> --json
docbank export problems <plan-id> --after 50 --json
show-plan --json returns the complete saved plan. Human output labels
expires_at as the admission deadline: a retained job can keep the header
readable after that deadline, but a new start is refused. Reads extend neither
admission nor retention.
CLI, MCP, and other API-key clients share the same owner. They cannot inspect a
plan owned by a browser session. An unknown or removed plan returns not found.
An expired retained record returns export_expired. For a new export, create a
fresh preview with new operation IDs. It captures a new selection, not the old
snapshot. Use status to inspect an existing job.
problems --json returns plan_id, fingerprint, after, next, total, and
items. Each item identifies the node, version, role, reason, and optional
attachment part_path. Pass the returned nonzero next as --after. Zero
means the last page. Each call returns one page. Human output includes the next
command. Offsets range from zero to 2,600,000. An offset equal to the total
returns an empty final page. An offset beyond the total conflicts.
The details describe the frozen plan, even after source documents change. Totals count problems, not documents. They include a separate problem for each requested attachment role whose inventory is unavailable. Originals-only CLI and MCP previews normally have no unavailable outputs, while richer plans created through the same API-key owner can have them.
Pages contain at most 50 items and must fit within 64 KiB of encoded JSON.
A larger page fails with export_limit instead of returning partial data.
The HTTP problems route has the same cap and no option for a smaller page,
so switching clients or releasing jobs cannot make that page readable.
Free a finished job slot¶
All CLI calls and other API-key clients share two retained job slots. After two
completed exports, another start returns export_limit until a job is released
or expires and is cleaned up. Without release, completed jobs remain for 24 hours.
Run docbank export release <job-id> after saving the archive or deciding it is
no longer needed. The API equivalent is DELETE /api/v1/exports/jobs/{id}.
Release accepts completed, failed, or canceled jobs and returns HTTP 204. It
removes that job's retained archive and status, frees its slot, and leaves local
downloads and source documents unchanged. Active jobs must be canceled first.
An active download or unused ticket blocks release with export_retained.
Unused tickets expire after two minutes. Release does not interrupt a download.
Even download && release can briefly return export_retained while the server
finishes releasing the download lease. Retry release after a short delay.
If release is interrupted after removing the archive, download returns
410 export_expired with a hint to retry release and free the retained slot.
If release fails, retry it. If its response was lost, a not-found status confirms
the job is gone. Repeated release returns not found. Do not reuse a released
job's operation ID: its replay protection has been removed.
Release does not remove source and plan records before their original admission deadlines or another job's retention. The global cap of 32 source/plan records still applies: 16 fresh previews can fill it within ten minutes even if every job is released. Review the limits below when scheduling batches.
Email PDFs and attachments¶
Choose Include retained body PDFs, find the retained recipes, and select the paper size and runtime recipe. Generate missing PDFs through the email PDF workflow before exporting. Batch export uses retained outputs. It does not start rendering or silently substitute another recipe.
The email attachment choices are:
| Choice | Downloaded outputs |
|---|---|
| Body PDF only | One separate PDF for each available selected message. |
| Include original attachments | Body PDFs and unchanged original attachment files. |
| Originals + separate qualified PDFs | Body PDFs, attachment originals, and separately retained PDFs for supported children. |
Only nested EML children with a qualified retained email-PDF receipt currently support the separate-PDF option. Other formats remain originals with declared unavailable PDF entries. An attached original PDF is not relabeled as a rendered derivative. Attachment contents are never appended to a body PDF.
The default stops planning if a requested output or attachment inventory is unavailable. Allow declared unavailable outputs permits a partial export. Review its unavailable-item pages before starting. A complete empty inventory is valid. A missing or partial inventory is not an empty one. Known occurrences remain in the manifest even when their requested output is unavailable. Counts distinguish messages, attachment occurrences, actual PDF files and pages, shared outputs, and unavailable outputs or inventories.
Every selected occurrence gets its own output by default. Share exact duplicate outputs keeps every source and attachment receipt while pointing identical outputs at an earlier file. Sharing requires equal original message bytes, the same attachment position, and equal output bytes and recipes. Equal subjects or Message-ID values do not establish duplicates. ID-based output paths keep repeated subjects separate.
Create and download a bundle¶
POST /api/v1/exports/sourceswith anoperation_idUUID and exactly one source kind:explicit,nodes,query,saved_query,snapshot,mailbox_collection, orupload. Explicit members containnode_id,version_id,sha256, andsize. An optionalrevisionis a precondition. Distinct historical versions of the same document are allowed.POST /api/v1/exports/planswith a newoperation_id, the returnedsource_idandmember_hash, androles. Each role policy selectsoriginal,text,pages,email_pdf,attachment_original, orattachment_pdf. Missing requested roles fail planning unless that policy setsallow_unavailable: true.POST /api/v1/exports/jobswith a newoperation_id,plan_id, and the plan'sfingerprint. The daemon queues a durable job.- Read
GET /api/v1/exports/jobs/{id}or its/eventsNDJSON stream. Every stream record is a current-state delivery with a monotonic sequence, not a replay of missing events. A stream lasts at most 30 seconds. Reconnect with?after=<sequence>. Onlycompletedcarries an archive receipt. POST /api/v1/exports/jobs/{id}/downloadwith{}or a safebasename. Docbank rechecks the actual archive and returns a two-minute, one-use download URL and receipt. Verify the complete downloaded ZIP against the receipt's SHA-256 and size.
Repeat the same operation UUID and request after a lost response. Changed
input under an existing UUID conflicts. An interrupted source resolution
cannot silently rerun a changed query. Start a new operation instead.
Use POST /api/v1/exports/jobs/{id}/cancel with {} to cancel active work.
No request accepts a server destination path.
For a saved query, send saved_query_id and saved_query_revision. For a
snapshot, send snapshot_id and member_hash before its cache expires.
Queries use the same frozen population service as workspace queries.
Snapshot-observed revisions are not implicit revision preconditions.
For mailbox_collection, send collection_id. Only a completed import is
accepted. Membership comes from its imported receipt targets, not the live
collection or later document heads.
Text roles use retained, verified sanitized Markdown and record the actual
processing profile, attachment, and build. Set profile_fingerprint to select
one profile. Page roles select a complete retained page-image recipe and
include its physical frames and recipe. Set recipe_sha256 to pin that
selection. Export does not start rendering. Without a selector, the lowest
available profile or complete recipe fingerprint is selected and frozen. An
unavailable role has no file and no placeholder.
Email PDF roles require exactly one profile_fingerprint or recipe_sha256.
A profile is source-specific, while a recipe can cover multiple messages.
Discover body recipes with GET /api/v1/exports/sources/{id}/email-pdf-recipes
after sealing the source. This advisory response is bounded to 50 recipes and
fails if exceeded. When attachments are included, the body PDF must match
that attachment set's decoded generation. A missing match is an unavailable
output, so the selected partial-export policy applies. Without attachments,
multiple decoded generations for the selected recipe conflict instead of
choosing one arbitrarily.
Attachment roles pin the publication and each child occurrence. If more than
one publication exists for a parent, the plan must supply a publications
entry containing version_id and operation_id. The API accepts at most 1,000
such selections. It never chooses the latest publication.
The drawer's Find attachment sets button lists these choices, including
empty sets. API callers use
GET /api/v1/exports/sources/{id}/attachment-publications?after=0 after sealing
the source. Each page contains up to 50 choices and a next cursor.
GET /api/v1/exports/plans/{id}/problems?after=0 returns up to 50 unavailable
details, with a next cursor until every detail is accounted for.
Set duplicate_policy: "collapse_exact_content" only to share duplicate
outputs. Omitting it preserves separate outputs. In metadata.csv, a shared
output's role_path points to the file it reuses. With volume packaging, that
path is inside the corresponding numbered ZIP.
Upload more than 1,000 members¶
Create an upload source with total and member_hash. The member hash is
SHA-256 over UTF-8 node_id:version_id\n records sorted by numeric node ID,
then version ID. Upload zero-based chunks to
PUT /api/v1/exports/sources/{id}/chunks/{index} with {"members":[...]}.
Each chunk has 1,000 members except the final remainder. An identical chunk
retry succeeds, and changed content conflicts. Seal with
POST /api/v1/exports/sources/{id}/seal and {}. Missing chunks, duplicate
exact members, and count or hash mismatches prevent sealing.
Retention and deletion¶
Sealed sources and plans each have a ten-minute admission window. An accepted job can run for up to two hours. A completed archive stays in owner-private storage for 24 hours. New tickets can retrieve it during that period. Using a ticket does not delete the retained archive. Cleanup removes expired records and files after active download leases finish.
An active or completed job also keeps its plan readable through
GET /api/v1/exports/plans/{id}. The returned plan keeps its original admission
deadline and fingerprint. Retention does not allow new jobs after that deadline.
During retention, destructive version pruning, permanent deletion, and purge
of selected retained derivatives return export_retained with an expiry.
Selected attachment publications are also retained until export cleanup
releases their plan authority. Ordinary document replacement remains available.
Failed and canceled work releases its protection through cleanup after the
remaining admission and short terminal-record retention windows. Protection
ends when cleanup removes the retained records, not at the expiry timestamp
alone.
A daemon restart fences old worker claims and rebuilds an interrupted ZIP from the same stored plan. Portable backup includes sealed source and plan authority plus its retained blob closure. Restoring a backup preserves those receipts and expiries, but does not restore runnable export handles, browser credentials, tickets, private paths, or completed archive files.
Archive contract and limits¶
docbank-bundle-v1 uses deterministic, uncompressed ZIP/ZIP64. Generated ASCII
paths identify each document version and role. Original names and paths stay
in metadata. bundle.json contains the plan and document receipts, and
metadata.csv provides a spreadsheet projection with formula cells escaped.
The JSON keeps exact metadata values.
Attachment occurrences follow their parent as bounded continuation rows.
Each row records the original message, selected publication, part position,
child identity, output status, and any retained PDF receipt. Available PDF
receipts bind source, decoded generation, renderer recipe, body, output
hash/size, and page count. A collapsed role keeps that receipt and references
the earlier physical output with reuse_of. Downstream consumers can use these
ordered receipts. The ordinary export performs no numbering, stamping, or PDF
concatenation.
SHA256SUMS covers every role file, metadata.csv, and bundle.json. It
excludes itself. The separate receipt hashes the complete ZIP, avoiding a
checksum cycle. Verification checks local and central ZIP records, exact file
membership, paths, sizes, CRCs, SHA-256 values, metadata, and the plan fingerprint
before publication. Compressed entries, symlinks, duplicate or unsafe paths,
prefixes, trailing data, and incomplete streams are rejected.
Email exports default to bounded volumes. A single browser download contains
numbered volumes/000001.zip files plus the complete parent metadata and
reconciliation manifest. Each child ZIP has its own checksums and volume.json
binding the parent fingerprint, volume number, first output, output count, and
payload bytes. Parent verification checks every declared output against its
child volume, so swapping valid volumes fails reconciliation.
The API enables this packaging with volume_limits, for example
{"roles":1000,"role_bytes":536870912}. Both limits may be lowered. Neither
may exceed 1,000 outputs or 512 MiB of payload per volume. Fixed metadata and
ZIP overhead add at most 1 MiB per volume. No output is split across volumes.
An individual output above the selected payload bound fails planning. Omitting
volume_limits produces the flat archive. Plan and progress counts report
physical output files, while a completed receipt counts outer archive entries.
An export admits at most 100,000 document-version members, 300,000 role files,
and 50 GiB of role bytes. Limits also include 64 KiB per parent or attachment
continuation row, at most 400,000 such rows, 512 MiB total archive metadata, a
64 MiB ZIP directory, and a 52 GiB archive. Within the metadata allowance,
bundle.json is capped at 256 MiB and metadata.csv at 128 MiB. Checksums use
the remaining allowance.
Page export also limits stored page geometry to 32 KiB and budgets 32 KiB for
page receipts, estimated as 1 KiB plus recipe JSON per page. These bounds can
exclude documents with a few dozen pages. Exceeding either bound fails planning
unless the pages policy sets allow_unavailable: true. That records the whole
page role as unavailable and omits its images and frames. Other resource limits
still reject the plan.
There are at most 32 retained sources and plans combined, eight retained jobs
globally, and two per owner. Completed, failed, and canceled jobs count until
release or cleanup removes them. All clients using the daemon API key share the
master owner and its two-job allowance. Over-limit work fails without
truncating the selection.