Upload an invoice file
POST/api/v1/workspaces/:workspace_id/invoices/upload
Upload an invoice file (XML or PDF) for automatic parsing and import.
Supported formats:
- XML: UBL 2.1 Invoice/CreditNote, UN/CEFACT CII (EN16931 compliant)
- PDF: Factur-X / ZUGFeRD (PDF/A-3 with embedded XML)
The file is processed synchronously and the created invoice document is returned.
Pass lifecycle_state to create the invoice and enter the lifecycle in a single
call, instead of following up with PATCH /api/v1/invoices/{id}/transition. The
transition runs under the same rules as that endpoint. It is all-or-nothing: a
refused transition returns 422 and leaves no invoice behind. A file that needs
assisted extraction (a PDF with no embedded XML) cannot carry lifecycle_state
and is rejected with 422 - upload it alone, then transition once processing ends.
Request
Responses
- 201
- 202
- 401
- 403
- 422
The invoice created by parsing and importing the uploaded file's UBL, CII, or Factur-X content.
The file carries no structured invoice to parse - a PDF with no embedded XML, or an image - and was accepted for AI-assisted extraction in the background. No invoice exists yet, so the body is an acknowledgement and shares no field with the 201: branch on the status code, not on the body. The invoice is created later, if the extraction succeeds, and the invoice.created webhook is what announces it - upload_file_id identifies the run but no endpoint reports on it. Only a workspace whose subscribed offer allows unstructured deposit reaches this path; on the others the same file is refused with 422. lifecycle_state cannot be combined with such a file, since there would be nothing to transition.
The request carries no OAuth bearer token, or the token is invalid or expired.
The token's OAuth application has no access relationship with the requested workspace's tenant, or no such workspace exists.
The uploaded file could not be parsed or imported. The error body reports error: "unprocessable_entity" describing the failure. When the upload requested lifecycle_state: "deposited" and the Schematron gate refused it, code names which of the three causes did: schematron_fatal (fatal assertion - details carries the field-keyed map instead of its usual file key; fix those fields and retry), schematron_engine_unavailable (the engine owed a verdict and could not produce one - transient, retry, nothing is wrong with the invoice) or schematron_profile_unsupported (the reform admits no rule set for this profile - deterministic, send a different one). A requested lifecycle_state: "deposited" runs the same deposit guard as PATCH /invoices/{id}/transition, so its refusals are reported here too - among them a party that states no country, with details keyed on seller.address.country_code or buyer.address.country_code. Only the flux 1 obligation carries that presence rule; a sale whose buyer owes an e-reporting declaration (flux 10) is not refused for it. The refusal is all-or-nothing: no invoice is persisted.