Archiv & Export
Das GoBD-Archiv lesen und Exporte erzeugen – DATEV und GoBD-Exportpakete.
GoBD archive access and export jobs (DATEV, GoBD export packages).
Operationen
-
GET/exports/datevDATEV-Format Buchungsstapel (EXTF CSV) export of issued documents for a period -
GET/invoices/{id}/archive-dossierThe GoBD compliance dossier for a document: every archived object (xml_original, pdf_hybrid, validation_report and sendeprotokoll), ordered by kind. Metadata only, no bytes (the single-artifact download is the browse surface). RLS-scoped to the caller's entity/mode — a cross-entity request returns an empty dossier, never a leak. A not-yet-archived document (or one without a Sendeprotokoll yet) returns fewer objects, not an error. -
GET/entities/{id}/archive-documentsBrowse a entity's archived documents (cursor-paginated) -
GET/entities/{id}/archive-documents/{documentId}/artifacts/{kind}Download one archived artifact (synchronous stream) -
GET/entities/{id}/purge-requestsThe entity's active pre-deletion purge request -
POST/entities/{id}/purge-requestsCreate a purge request — approval -
POST/entities/{id}/purge-requests/{jobId}/approvalsApprove a purge request — approval -
POST/entities/{id}/export-jobsRequest an async export of an entity's archive -
GET/export-jobs/{id}Poll an export job's status -
GET/export-jobs/{id}/downloadDownload a built export bundle -
POST/export-jobs/{id}/confirm-deliveryConfirm a checksum-verified export download
DATEV-Format Buchungsstapel (EXTF CSV) export of issued documents for a period
GET /exports/datev
Read-only, deterministic, repeatable. Books documents by status IN (issued, paid, void) and issue_date in [from, to] — void documents ARE included (a void does not retroactively un-issue a document; its own economic reversal must itself exist as a separate 381/384 document, which then also exports). EUR-only and bounded to one fiscal year per request (see the 422 responses). Mapping-config gaps or an invalid Belegfeld 1 never produce a partial/misbooked file — they 422 with no bytes returned and nothing recorded as exported. An empty period still returns 200 with a valid empty Stapel (header + column row, zero data rows) and a log row with documentCount=0. Response headers never include caching directives beyond Cache-Control: no-store — the file contains complete customer PII + revenue data for the period.
- Erforderlicher Scope
- keiner
- Zugang
- nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt
(
oidc-only)
Parameter
| Parameter | In | Typ | Beschreibung |
|---|---|---|---|
from erforderlich | Query | string (date) | ISO date (YYYY-MM-DD), inclusive. Belegdatum source. |
to erforderlich | Query | string (date) | ISO date (YYYY-MM-DD), inclusive. |
mappingVersion | Query | integer | Pinned DATEV mapping-config version. Defaults to latest stored, or the version-0 built-in SKR default if nothing is stored. Bereich: ≥ 0 |
Antworten
-
200EXTF Buchungsstapel file (valid empty Stapel when the period has no documents)
-
X-Datev-Spec-VersionstringPinned DATEV format spec version string (
spec.tsSPEC_VERSION) -
X-Mapping-VersionintegerThe mapping-config version actually used (0 = built-in default)
-
X-Content-Sha256stringSHA-256 of the response body, hex-encoded
-
Cache-Controlstring
text/csvstring (binary)Beispiel-Response charset=windows-1252; CRLF line endings -
-
400Malformed
from/to/mappingVersionquery parametersapplication/jsonValidationError -
404The requested
mappingVersiondoes not exist -
422Machine-readable export rejection — no file produced, no log row written
application/problem+jsonDatevExportProblem
The GoBD compliance dossier for a document: every archived object (xml_original, pdf_hybrid, validation_report and sendeprotokoll), ordered by kind. Metadata only, no bytes (the single-artifact download is the browse surface). RLS-scoped to the caller's entity/mode — a cross-entity request returns an empty dossier, never a leak. A not-yet-archived document (or one without a Sendeprotokoll yet) returns fewer objects, not an error.
GET /invoices/{id}/archive-dossier
- Erforderlicher Scope
-
archive:read - Zugang
- API-Schlüssel oder angemeldete Sitzung
Parameter
| Parameter | In | Typ | Beschreibung |
|---|---|---|---|
id erforderlich | Pfad | string (uuid) |
Antworten
-
200The document's dossier (possibly empty / incomplete).
application/jsonobject
Browse a entity's archived documents (cursor-paginated)
GET /entities/{id}/archive-documents
The entity-scoped, metadata-only, cursor-paginated archive browse list. One row per DOCUMENT (grouped over its its archived objects), newest-DATED first (by issueDate/Rechnungsdatum), with artifact-kind chips and a lazily-derived integrityStatus. Distinct from the async the export jobs ZIP flow: browse is "one page now", export is "everything, verifiable offline, later". RLS-scoped to the caller's entity/mode; the path :id (the entity) must equal the resolved context entity (403 entity_mismatch). Reads are open to any active member — a pure read over WORM data (only deletion is dual-control-gated). integrityStatus derives from the verification job's audit trail: failed if any object has a verification_failed newer than the entity's latest scope-level verification_ok, ok if covered with no later failure, else unverified (drawer/lazy field — the clean verdict is scope-level, not per-row).
- Erforderlicher Scope
-
archive:read - Zugang
- API-Schlüssel oder angemeldete Sitzung
Parameter
| Parameter | In | Typ | Beschreibung |
|---|---|---|---|
id erforderlich | Pfad | string (uuid) | The entity to browse. Must equal the caller's active entity. |
cursor | Query | string | Opaque keyset cursor from a prior page's |
limit | Query | integer | Page size (1–100, default 50). Standard: 50 Bereich: ≥ 1, ≤ 100 |
kind | Query | string | Narrow to documents having this artifact kind (chips then show only it). Closed enum — a typo is rejected, not silently empty. Werte: "xml_original", "pdf_hybrid", "validation_report", "sendeprotokoll", "attachment", "signature" |
from | Query | string (date-time) | Inclusive lower bound on the issue date / Rechnungsdatum ( |
to | Query | string (date-time) | Inclusive upper bound on the issue date / Rechnungsdatum ( |
Antworten
-
200A page of archived documents (metadata only).
application/jsonArchiveDocumentPage -
400The query string was refused. Three distinguishable facts share this status, and a client should branch on the
errorcode rather than on the status:unknown_query_parameter— the request named one or more query parameters this deployment does not define, andparameterslists their names. Nothing was filtered and nothing was written. This is a DEPLOY-ORDER signal: it is what a client released ahead of the API sees, and it replaces the older behaviour in which an unrecognised parameter was silently ignored and the request answered200 OKwith the UNFILTERED collection — indistinguishable, to the caller, from the filter having been applied.validation_error— a parameter this endpoint does define carried an unusable value (including a required one being absent or too short).issues[]names the parameter and what was wrong with it.invalid_cursor— on cursor-paginated endpoints, the pagination cursor is malformed. No response on this path ever echoes a submitted VALUE; only parameter names appear.application/jsonUnknownQueryParameterError | ValidationError -
403The path entity id is not the caller's active entity (
entity_mismatch).
Download one archived artifact (synchronous stream)
GET /entities/{id}/archive-documents/{documentId}/artifacts/{kind}
Streams exactly one archived object's bytes — the byte- identical original the archiver stored, from the ObjectStore under its stored object key. Not the async export path. RLS-scoped: a cross-entity / wrong-mode / absent artifact is a 404, indistinguishable from "not found" (no leak). Cache-Control: no-store (PII-dense content). No re-render, no read-path hash re-check.
- Erforderlicher Scope
-
archive:read - Zugang
- API-Schlüssel oder angemeldete Sitzung
Parameter
| Parameter | In | Typ | Beschreibung |
|---|---|---|---|
id erforderlich | Pfad | string (uuid) | The entity. Must equal the caller's active entity. |
documentId erforderlich | Pfad | string (uuid) | |
kind erforderlich | Pfad | string | Werte: "xml_original", "pdf_hybrid", "validation_report", "sendeprotokoll", "attachment", "signature" |
Antworten
-
200The artifact bytes.
-
Cache-ControlstringAlways
no-store(PII-dense). -
Content-Dispositionstringattachment; filename="<documentId>-<kind>.<ext>".
application/octet-streamstring (binary) -
-
403The path entity id is not the caller's active entity (
entity_mismatch). -
404No such archived artifact for this (document, kind) under the caller's scope.
The entity's active pre-deletion purge request
GET /entities/{id}/purge-requests
Returns the entity's active pre_deletion_export job in the approval tail (export_delivered/approved_1/approved_2), or { data: null } if none is in flight. The path :id must equal the active entity.
- Erforderlicher Scope
- keiner
- Zugang
- nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt
(
oidc-only)
Parameter
| Parameter | In | Typ | Beschreibung |
|---|---|---|---|
id erforderlich | Pfad | string (uuid) |
Antworten
-
200The active purge request (or null).
application/jsonobject -
403The path entity id is not the caller's active entity.
Create a purge request — approval
POST /entities/{id}/purge-requests
Approval #1 of the export-gated, dual-control deletion workflow. References a pre_deletion_export job that MUST be in export_delivered state (a premature request → 409 export_not_delivered — the anti-rubber-stamp export gate). On success the job moves export_delivered → approved_1, recording the requester as the first approver. Owner/admin only. Execution never happens here — only the retention-sweep worker executes, on approved_2.
- Erforderlicher Scope
- keiner
- Zugang
- nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt
(
oidc-only)
Parameter
| Parameter | In | Typ | Beschreibung |
|---|---|---|---|
id erforderlich | Pfad | string (uuid) |
Request-Body erforderlich
application/json object
Antworten
-
201Purge request created (job now
approved_1).application/jsonExportJob -
400The request body is not valid JSON (
invalid_json) or fails validation (validation_error, naming each problem inissues).application/jsonValidationError -
403Insufficient role, or path entity mismatch.
-
404Export job not found (or not visible under the caller's scope).
-
409export_not_delivered(premature),wrong_purpose(not a pre_deletion_export), oralready_in_approval.application/jsonobject
Approve a purge request — approval
POST /entities/{id}/purge-requests/{jobId}/approvals
Approval #2 — the SECOND, DIFFERENT owner/admin's approval. A same-actor second approval is rejected with 409 dual_control_same_actor (the anti-rubber-stamp dual-control guarantee: approved_1_by != approved_2_by, enforced in the service AND defended in SQL). On success the job moves approved_1 → approved_2; the retention-sweep worker then executes it (deleting ONLY objects past retain_until with no legal_hold, and purging the entity). Owner/admin only.
- Erforderlicher Scope
- keiner
- Zugang
- nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt
(
oidc-only)
Parameter
| Parameter | In | Typ | Beschreibung |
|---|---|---|---|
id erforderlich | Pfad | string (uuid) | |
jobId erforderlich | Pfad | string (uuid) |
Antworten
-
200Second approval recorded (job now
approved_2).application/jsonExportJob -
403Insufficient role, or path entity mismatch.
-
404No pre-deletion purge request found for this job under the caller's scope.
-
409dual_control_same_actor(same actor as approval #1) ornot_pending_approval(not awaiting a second approval).application/jsonobject
Request an async export of an entity's archive
POST /entities/{id}/export-jobs
Creates an export job and returns its id IMMEDIATELY (202 — genuinely async, NOT a synchronous download). A background worker builds a checksum-verified ZIP (original XML+PDF per document, validation reports, sendeprotokolle, audit trail JSONL+CSV) + a manifest.json whose sha256 set verifies every file OFFLINE, uploads both, and sets ready. Poll GET /export-jobs/:id; download GET /export-jobs/:id/download; then POST /export-jobs/:id/confirm-delivery with the manifest hash to flip to export_delivered. Entity/mode come from the resolved context; the path :id must equal the active entity (403 entity_mismatch). mode-scoped — a live export never bundles test (sandbox) data. Honors the optional Idempotency-Key header so a retried request is a replay, not a second build job.
- Erforderlicher Scope
- keiner
- Zugang
- nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt
(
oidc-only)
Parameter
| Parameter | In | Typ | Beschreibung |
|---|---|---|---|
id erforderlich | Pfad | string (uuid) | The entity to export. Must equal the caller's active entity. |
Idempotency-Key | Header | string | Optional client-supplied key that makes this mutating request safe to retry. The FIRST request runs and its outcome is recorded keyed by Länge: max. 255 |
Request-Body optional
application/json object
Antworten
-
202Job created (state
requested); the worker builds it asynchronously. On an Idempotency-Key replay, the recorded response.application/jsonExportJob -
400The request body is not valid JSON (
invalid_json) or fails validation (validation_error, naming each problem inissues).application/jsonValidationError -
403The path entity id is not the caller's active entity (
entity_mismatch). -
409Idempotency-Key conflict.
IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_BODY— the key was already used with a different request body;REQUEST_IN_PROGRESS— the first request bearing this key is still in flight (retryable).application/jsonobject
Poll an export job's status
GET /export-jobs/{id}
Returns the job's state + a ready flag (true once the bundle is built and downloadable). The client stepper polls this: requested → (export_pending, ready:false — building) → (export_pending, ready:true — downloadable) → export_delivered. RLS-scoped: a cross-entity/mode id is a 404.
- Erforderlicher Scope
- keiner
- Zugang
- nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt
(
oidc-only)
Parameter
| Parameter | In | Typ | Beschreibung |
|---|---|---|---|
id erforderlich | Pfad | string (uuid) |
Antworten
-
200The job.
application/jsonExportJob -
404No such export job under the caller's scope.
Download a built export bundle
GET /export-jobs/{id}/download
Streams the built export ZIP (Cache-Control: no-store — PII-dense). 409 export_not_ready if the worker hasn't built it yet; 404 for a cross-entity/mode or unknown job.
- Erforderlicher Scope
- keiner
- Zugang
- nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt
(
oidc-only)
Parameter
| Parameter | In | Typ | Beschreibung |
|---|---|---|---|
id erforderlich | Pfad | string (uuid) |
Antworten
-
200The export ZIP.
application/zipstring (binary) -
404No such export job under the caller's scope.
-
409The bundle is not built yet (
export_not_ready).
Confirm a checksum-verified export download
POST /export-jobs/{id}/confirm-delivery
Flips a built job to export_delivered ONLY if the posted manifestSha256 matches the stored manifest_sha256 — the concrete "delivered means checksum-verified download, not 'the job ran'" mechanism A WRONG hash is rejected 409 with state UNCHANGED (not a rubber stamp). This delivered state is the deletion gate.
- Erforderlicher Scope
- keiner
- Zugang
- nur mit angemeldeter Sitzung – ein API-Schlüssel wird abgelehnt
(
oidc-only)
Parameter
| Parameter | In | Typ | Beschreibung |
|---|---|---|---|
id erforderlich | Pfad | string (uuid) |
Request-Body erforderlich
application/json object
Antworten
-
200Delivered — state is now
export_delivered.application/jsonExportJob -
400The request body is not valid JSON (
invalid_json) or fails validation (validation_error, naming each problem inissues).application/jsonValidationError -
404No such export job under the caller's scope.
-
409Wrong hash (
manifest_hash_mismatch, state unchanged) or the bundle is not built yet (export_not_ready).
Datenmodelle
Jedes Datenmodell dieser Gruppe einmal, mit allen Feldern – auch den verschachtelten. Verschachtelte Objekte sind aufklappbar; ein benannter Typ verweist auf seinen eigenen Abschnitt.
ValidationError object
A refused request body. error is validation_error when the body was read and failed validation (each problem is listed in issues), or invalid_json when the body is not valid JSON at all (then issues is absent). An unparsable body is answered 400 invalid_json on every operation that takes a JSON body. The submitted body is never echoed.
| Feld | Typ | Beschreibung | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
error | string | |||||||||||||
issues | Array<object> | Felder
|
Verwendet von:
GET /exports/datev , GET /entities/{id}/archive-documents , POST /entities/{id}/purge-requests , POST /entities/{id}/export-jobs , POST /export-jobs/{id}/confirm-delivery
DatevExportProblem object
GET /exports/datev guard/derivation/serialization rejection. items shape varies by code: WJ_BOUNDARY_CROSSED carries none; NON_EUR_UNSUPPORTED carries {invoiceId, currency}; UNMAPPED_ITEMS carries {kind: revenueRate|debitorCustomer, taxRatePercent(+vatCategory)|customerId, invoiceIds} (the revenueRate item names the EN-16931 VAT category); INVALID_BELEGFELD carries {value, invoiceIds}; ROW_LIMIT_EXCEEDED carries {rowCount, limit, suggestion}; UNSUPPORTED_CORRECTION_CHAIN carries {correctionInvoiceId, originalInvoiceId, originalInvoiceTypeCode} — a 384 correction whose referenced original is itself a 384 (a chained restatement), which the DATEV export does not book.
| Feld | Typ | Beschreibung |
|---|---|---|
type | string | |
title | string | |
status | integer | |
detail | string | |
code erforderlich | string | Werte: "WJ_BOUNDARY_CROSSED", "NON_EUR_UNSUPPORTED", "UNMAPPED_ITEMS", "INVALID_BELEGFELD", "ROW_LIMIT_EXCEEDED", "UNSUPPORTED_CORRECTION_CHAIN" |
items erforderlich | Array<object> | Offending items (unmapped rates/customers, invoice ids, field values) — see the |
Verwendet von:
GET /exports/datev
ArchiveObjectMeta object
One archived compliance artifact. Metadata only — the object bytes are streamed by the browse/download surface, never here.
| Feld | Typ | Beschreibung |
|---|---|---|
id | string (uuid) | |
documentId | string (uuid) | |
kind | string | Werte: "xml_original", "pdf_hybrid", "validation_report", "sendeprotokoll", "attachment", "signature" |
sha256 | string | Hex sha256 of the archived bytes. |
sizeBytes | integer (int64) | |
contentType | string | |
retentionClass | string | Werte: "beleg_8y", "buch_10y" |
retainUntil | string (date) | WORM retain-until (Dec 31 of issue year + retention years). |
legalHold | boolean | |
locked | boolean | True once a compliance-mode Object Lock was applied. |
nonWorm | boolean | True for a degraded (non-WORM) archive — the lock could not be applied. |
archivedAt | string (date-time) |
Verwendet von:
GET /invoices/{id}/archive-dossier
ArchiveDocumentPage object
A cursor-paginated page of the period-document view ({ data, nextCursor, pendingCount }, keyset over (issue_date, document_id) DESC — the same envelope as the webhook delivery + audit logs, plus pendingCount).
| Feld | Typ | Beschreibung |
|---|---|---|
data erforderlich | Array<ArchiveDocumentListItem> | |
nextCursor erforderlich | string | null | Opaque cursor for the next page; |
pendingCount erforderlich | integer | Count of issued-but-not-yet-archived documents across the whole |
Verwendet von:
GET /entities/{id}/archive-documents
UnknownQueryParameterError object
The request named query parameters this endpoint does not define. A list endpoint refuses them rather than ignoring them, so a caller can never mistake an UNFILTERED collection for a filtered one.
| Feld | Typ | Beschreibung |
|---|---|---|
error erforderlich | string | Werte: "unknown_query_parameter" |
parameters erforderlich | Array<string> | The unrecognised parameter NAMES, deduplicated and sorted. Bounded at ten entries, and a name longer than 64 characters is omitted — a parameter name is caller-supplied text, and this response never reflects unbounded caller input. The array may therefore be EMPTY while the refusal still stands: the refusal is decided by the request, not by the length of this list. |
message | string | |
requestId | string |
Verwendet von:
GET /entities/{id}/archive-documents
ExportJob object
An async export job. ready is the poll flag the client stepper reads (true once the bundle is built + downloadable). Metadata only — the bundle bytes are streamed by /export-jobs/:id/download.
| Feld | Typ | Beschreibung |
|---|---|---|
id erforderlich | string (uuid) | |
entityId | string (uuid) | null | NULL only for control-plane whole-tenant jobs. |
mode erforderlich | Mode | |
state erforderlich | string | Werte: "requested", "export_pending", "export_delivered", "approved_1", "approved_2", "executed", "failed" |
purpose erforderlich | string | Werte: "self_serve_export", "pre_deletion_export" |
ready erforderlich | boolean | True once the bundle is built and downloadable (manifest present, not failed). |
manifestSha256 | string | null | sha256 of manifest.json; post it to confirm-delivery. |
deliveredAt | string (date-time) | null | |
lastError | string | null | Short reason when state is |
createdAt erforderlich | string (date-time) | |
updatedAt erforderlich | string (date-time) |
Verwendet von:
GET /entities/{id}/purge-requests , POST /entities/{id}/purge-requests , POST /entities/{id}/purge-requests/{jobId}/approvals , POST /entities/{id}/export-jobs , GET /export-jobs/{id} , POST /export-jobs/{id}/confirm-delivery
ArchiveDocumentListItem object
One DOCUMENT on the period-document browse list — every issued document in the window, whether already archived or still pending archival, metadata only. archivalStatus branches the two: a pending row has archivedAt, integrityStatus and retention null and artifactKinds empty (no object bytes exist yet). artifactKinds are the chips for archived rows; the object bytes are streamed by the single-artifact download.
| Feld | Typ | Beschreibung | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
documentId erforderlich | string (uuid) | |||||||||||||
documentNumber | string | null | The invoice number (null before assignment). | ||||||||||||
documentType erforderlich | string | UNTDID 1001 code, e.g. 380. | ||||||||||||
archivalStatus erforderlich | string |
Werte: "archived", "pending" | ||||||||||||
issueDate erforderlich | string (date) | The invoice's issue date / Rechnungsdatum ( | ||||||||||||
issuedAt | string (date-time) | null | RFC3339 finalize instant ( | ||||||||||||
archivedAt erforderlich | string (date-time) | null | Earliest archive instant for the document; null when archivalStatus=pending. | ||||||||||||
artifactKinds erforderlich | Array<string> | |||||||||||||
integrityStatus erforderlich | string | null | Lazily derived from the verification job's audit trail. Werte: "ok", "unverified", "failed" | ||||||||||||
retention erforderlich | object | null | Null when archivalStatus=pending. Felder
|
Verwendet von:
GET /entities/{id}/archive-documents
Mode string
Mode echo ("unmissable live/test indicator"): the row's own live/test mode, echoed on every scoped-resource representation so a live vs. test record is unmissable. Read from the record's mode column (never re-derived from the request credential); live for legacy rows created before mode existed.
- Werte: "live", "test"
Beispiel: "live"
Verwendet von:
GET /entities/{id}/purge-requests , POST /entities/{id}/purge-requests , POST /entities/{id}/purge-requests/{jobId}/approvals , POST /entities/{id}/export-jobs , GET /export-jobs/{id} , POST /export-jobs/{id}/confirm-delivery
Nach oben · Alle Ressourcengruppen · OpenAPI-Spezifikation (YAML)