Propose a booking
This is the Docbooks core. Product-wise: the customer hands you a receipt; you want a posting proposal, not a pile of OCR fields. You POST the file to Docbooks only. Architecturally Docbooks runs the analysis behind the scenes — it forwards your Bearer token, which must carry docmind.capture — and then books.
You get 202 with a bookingJobId in QUEUED status. Wait for BOOKING_FINALISED, then GET. Do not POST the file to Docmind first.
/api/docbooks/v1/workspaces/{workspace_id}/bookingsTwo things must be true before the first booking. The workspace must be
provisioned against an extraction workspace (unprovisioned →
409), and it must have an effective chart of accounts,
its own or its organisation's (none → 400). Use the Docbooks workspace
id from onboarding, not a Docmind workspaceId. Chart of accounts and
settings · Combine analysis and
booking.
Keep the original file bytes. Booking is a multipart POST to Docbooks — never a Docmind documentId.
Authentication
AuthorizationstringheaderrequiredBearer token with scopes docbooks.bookings and capture (docmind.capture /
deprecated idp.capture). Submit requires both on either path, even the
pre-extracted one; the token is only forwarded to the extractor on the file path.
Path
workspace_idstring (ULID)pathrequiredDocbooks workspace.
Multipart
Send exactly one of file or extraction.
filefilebodyPDF or image to book. Docbooks forwards it to the extractor for OCR and extraction.
Over the upload limit (200 MB) is 413 FILE_TOO_LARGE; anything other than a PDF or
image is 415 UNSUPPORTED_MEDIA_TYPE.
extractionstring (JSON)bodyA Docmind DocumentResponse you already hold, as JSON. Docbooks parses it and skips
docmind entirely — no upload, no OCR wait. An invalid or unbookable extraction is a
400. Sending both parts, or neither, is 400 INVALID_SUBMISSION.
metadatastring (JSON)bodyrequiredJSON object: required actor.extRef; optional extRef; optional
processingInstruction (split.enabled, capture forwarded to Docmind as
options, optional settings override).
After 202
Wait for BOOKING_FINALISED, then:
/api/docbooks/v1/bookings/{booking_job_id}The GET needs the same two scopes as submit — Docbooks reads the extraction from Docmind with your token. Optional query include=extraction embeds the raw extraction on each record.
The report carries a job-level status and one BookingRecord per document in data — one for a single upload, several when the upload was split.
status | What it means | What you do |
|---|---|---|
QUEUED | Accepted, not started. | Wait for the webhook. |
RUNNING | Extraction or generation in flight. | Wait for the webhook. |
COMPLETED | Balanced booking, no open questions. | Post it, or show it for a one-click confirm. |
REVIEW_REQUIRED | A booking exists but something needs a human — see reviewReasons. | Route to review. |
FAILED | No booking was produced — see error. | Do not retry blindly; fix the input or the chart. |
Each record also carries the same states in its own status, plus reviewReasons (empty when COMPLETED) and reasoning (confidence 0–1, explanation, appliedRules). Postings get a status and vatAccountable of their own, so one weak posting does not hold back its siblings.
A posting's bookingText reads [document type] [sender] [MM.YY] [largest line item] — the document type in German (Rechnung, Gutschrift, Beleg), the line item cut at its first ( — and leaves out any part the document lacks. Its vatCode comes from the revenue and expense accounts it books, plus the VAT leg under vatPosting: EXTRA; a Kreditoren or bank counter leg never decides it. The code is UNKNOWN when those accounts carry no code, when they carry conflicting codes, or when a NONE account books a document that carries VAT — in the last two cases the posting's reasoning.explanation says why.
discarded lists the posting groups that did not make it into postings — empty when nothing was dropped. Each entry has a reason (AMOUNT_UNDETERMINED, VAT_LEG_SHAPE_INVALID, LEGS_UNRESOLVED, LOW_CONFIDENCE) and either the proposedLegs that never resolved to an account on your chart, or the posting that scored below the confidence floor. Show it in the review lane so no part of the document disappears silently. A non-empty discarded always holds the booking at REVIEW_REQUIRED — even when the remaining postings balance and reconcile — with error.code POSTING_GROUP_DISCARDED unless a stronger reason already applies.
Every booking is reconciled against the document's own total. When that total cannot be read, the record is REVIEW_REQUIRED with error.code TOTAL_UNVERIFIABLE; when the postings do not add up to it, AGGREGATE_MISMATCH.
List bookings:
GET /api/docbooks/v1/workspaces/{workspace_id}/bookings (pagination
envelope; each item is a full job report).
If split is enabled on the booking metadata, only the parent job fires the webhook. Children appear on the GET report.