{"openapi":"3.1.0","info":{"title":"einvoice-api","description":"Official-validator attestations for hybrid e-invoices (Factur-X / ZUGFeRD / EN 16931).\n\n**A failed conformance verdict is a 200, not an error.** You are buying the\nattestation of the outcome; \"this invoice fails schematron\" is a successful call.\nErrors are reserved for calls that could not be completed.\n\n**A verdict is never invented.** If an engine did not run, its\n`engines.<name>.status` says so and the matching top-level field is `null`.\n`null` never means pass — test for `=== true`.\n\n**Rendered documents are self-checked before they are returned.** `/v1/render`\nruns the same official validators over its own output; a document that fails is\nnever handed back, and that call is not charged.\n\n**Pricing** (credits, prepaid, never expire): `validate` and `extract` are\nreads and cost 1 credit each — `extract` also shares `validate`'s free\nmonthly allowance. `render` and `convert` cost 2 credits each — both build a\ndocument (WeasyPrint plus two JVM validators) and neither has a free tier;\n`convert` is priced identically to `render` on purpose, since it is the same\nbuild step fed by a parsed source document instead of a caller's JSON body.","version":"0.1.0"},"paths":{"/health":{"get":{"summary":"Health","operationId":"health_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/healthz":{"get":{"summary":"Healthz","description":"Public engine-availability matrix (blueprint route table).\n\nThe null rule applied to the health surface: an engine whose binary is not\nrunnable reports available=false and the service reports \"degraded\". Saying\notherwise would be the same fabrication as a fake pass — and this is the\nendpoint an integrator checks before trusting a verdict.\n\nDistinct from /internal/selfcheck, which stays the deploy hook's gate and\nreturns the two raw booleans it was written against.","operationId":"healthz_v1_healthz_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthzResponse"}}}}}}},"/v1/me":{"get":{"summary":"Me","description":"Entitlement introspection. Free — it moves no credits.","operationId":"me_v1_me_get","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-minute rate limit, monthly cap, or free allowance exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/signup":{"post":{"summary":"Signup","description":"Self-serve free API key — email in, key out (shown ONCE).\n\nDeliberately UNAUTHENTICATED because it is how a stranger gets their first\nkey, so its only guard is an IP rate limit; the blast radius of abuse is\nbounded anyway by the free tier (50 validate runs/month per key). The account\nstarts on the 'free' plan; buying credits promotes it to 'paid' (see the\nStripe webhook). An existing e-mail returns its account and a fresh key —\ncreate_account keeps the existing plan, so this never downgrades a payer.","operationId":"signup_v1_signup_post","requestBody":{"content":{"application/json":{"schema":{"type":"object","title":"Payload"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/internal/selfcheck":{"get":{"summary":"Selfcheck","description":"Deploy hook gate: engines must be runnable before the image takes traffic.\n\nUnauthenticated on purpose — the deploy hook calls it before any key exists.\nIteration 5 adds two more booleans alongside the original engine pair:\nextract_selfcheck_ok (CII+UBL parse round-trip) and convert_selfcheck_ok\n(CII+UBL parse+map, plus a full build+self-check when the render stack is\nactually installed) — see app/services/selfcheck_probes.py for why each\nis safe to run on every poll (fixed samples, no network, the PDF build is\nthe same lru_cached call GET /v1/convert/try already makes).","operationId":"selfcheck_internal_selfcheck_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/validate/try":{"post":{"summary":"Validate Try","description":"No-key trial (blueprint §6) — the top of the funnel.\n\nSame engines, same honest engine statuses, no signup. Deliberately outside\nthe credit path: there is no account, so nothing to reserve, settle, or\nrecord in usage_events (whose account_id is NOT NULL). The IP cap replaces\nthe credit hold as the bound on cost, and is enforced in the same place the\nhold would be — before any engine runs.","operationId":"validate_try_v1_validate_try_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_validate_try_v1_validate_try_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"400":{"description":"The upload was empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"The request body exceeds the maximum size.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-minute rate limit, monthly cap, or free allowance exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"A dependency the request needs is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/extract/try":{"post":{"summary":"Extract Try","description":"No-key trial for EXTRACT — the extract counterpart of /v1/validate/try.\n\nDeliberately shares validate/try's trial pool (same `trial.consume`/\n`trial.trial_key`, keyed on IP+month only, not on endpoint): both are read\nendpoints against the same \"let a stranger see real output before signing\nup\" allowance, so a caller trying both in the same session draws down one\nbudget rather than getting two. render/try is the one exception, and stays\none, for the reason already given there (a fixed sample, different funnel).\n\nOutside the credit path like validate/try: there is no account, so nothing\nto reserve/settle/record. A parse failure (ExtractError) gives the trial\nback — same null rule as everywhere else, applied to a 422 instead of a\nnull verdict.","operationId":"extract_try_v1_extract_try_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_extract_try_v1_extract_try_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractResponse"}}}},"422":{"description":"Same as POST /v1/extract's 422 (code=unparseable_invoice) — this is the no-key trial, so nothing was ever billable in the first place.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"400":{"description":"The upload was empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"The request body exceeds the maximum size.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-minute rate limit, monthly cap, or free allowance exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"A dependency the request needs is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/render/try":{"get":{"summary":"Render Try","description":"No-key trial for GENERATION — the render counterpart of /v1/validate/try.\n\nRenders a FIXED curated EN16931 sample (never a caller's invoice) so a prospect sees\na real, self-checked Factur-X PDF without a key or signup. The sample is byte-stable\nand cached (sample_pdf's lru_cache), so per-hit cost is ~serving bytes; the per-IP\nburst limit — the same guard validate/try uses — is the abuse bound. We deliberately\ndo NOT consume the validate trial monthly cap: clicking the render demo must not spend\na prospect's validate allowance (different funnel, different meter).","operationId":"render_try_v1_render_try_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/convert/try":{"get":{"summary":"Convert Try","description":"No-key trial for CONVERT — the convert counterpart of /v1/render/try.\n\nMirrors render/try EXACTLY, not extract/try: convert runs the same\nWeasyPrint + two-JVM build render pays 2 credits for (build_invoice_pdf),\nso a trial that accepted a caller's own upload would give that compute\naway for the price of a rate limit. Instead this converts a FIXED curated\nCII sample (app/render/convert_sample.py) through the REAL parse -> map ->\nrender pipeline, cached the same way (lru_cache — built once, byte-stable\nthereafter). Deliberately does NOT touch the validate/extract trial pool,\nfor the same reason render/try does not: a different, more expensive\nfunnel with a different meter.","operationId":"convert_try_v1_convert_try_get","responses":{"200":{"description":"The SAME fixed sample document on every call (never a caller's upload) — the no-key trial for /v1/convert.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}},"headers":{"Content-Disposition":{"description":"attachment; filename=\"facturproof-convert-sample.pdf\"","schema":{"type":"string"}}}},"429":{"description":"Per-minute rate limit, monthly cap, or free allowance exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"A dependency the request needs is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/mcp":{"post":{"summary":"Mcp Endpoint","description":"MCP Streamable HTTP endpoint (blueprint layer 7).\n\nAuthentication is the same dependency every other authenticated route uses,\nso a bad key is a 401 here too — MCP is a door onto the product, not a way\naround its gate.","operationId":"mcp_endpoint_mcp_post","parameters":[{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/render":{"post":{"summary":"Render","description":"Render a hybrid Factur-X / ZUGFeRD PDF/A-3 from EN 16931 JSON (2 credits).\n\nThe Invoice model validates and RECOMPUTES every total, so a malformed or\nself-contradictory invoice is a 422 before any credit hold is taken. The\nmoney path (hold -> build -> self-check -> settle/refund) lives in\napp/pipeline.py, shared with validate so the two cannot drift apart.\n\nReturns the PDF itself rather than JSON: it is the artifact the customer\npaid for, and base64-wrapping it would double the transfer for nothing.","operationId":"render_v1_render_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}},"responses":{"200":{"description":"The rendered hybrid Factur-X / ZUGFeRD PDF/A-3 document.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}},"headers":{"Content-Disposition":{"description":"attachment; filename=\"<invoice_number>.pdf\"","schema":{"type":"string"}},"X-Credits-Remaining":{"description":"Credits left after this call.","schema":{"type":"integer"}}}},"422":{"description":"The invoice could not be accepted: a required field is missing, or the totals you supplied disagree with the ones computed from the lines (BR-CO-15). When code=profile_requirements, every unmet requirement is listed individually in the error's `missing` array. Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"402":{"description":"Not enough credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"This Idempotency-Key was already used for a different document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-minute rate limit, monthly cap, or free allowance exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Unexpected internal failure. Quote request_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"A dependency the request needs is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/validate":{"post":{"summary":"Validate","operationId":"validate_v1_validate_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_validate_v1_validate_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"400":{"description":"The upload was empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"402":{"description":"Not enough credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"This Idempotency-Key was already used for a different document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"The request body exceeds the maximum size.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-minute rate limit, monthly cap, or free allowance exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Unexpected internal failure. Quote request_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/extract":{"post":{"summary":"Extract","description":"Read a hybrid Factur-X/ZUGFeRD PDF/A-3, raw CII XML, or a UBL invoice into\nheader JSON (1 credit) — mirrors /v1/validate exactly (auth, size cap,\ncredit hold, idempotent replay); see app/pipeline.run_extraction.","operationId":"extract_v1_extract_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_extract_v1_extract_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExtractResponse"}}}},"422":{"description":"The upload could not be read as a hybrid Factur-X/ZUGFeRD PDF/A-3, raw CII XML, or a UBL invoice (code=unparseable_invoice). Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"400":{"description":"The upload was empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"402":{"description":"Not enough credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"This Idempotency-Key was already used for a different document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"The request body exceeds the maximum size.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-minute rate limit, monthly cap, or free allowance exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Unexpected internal failure. Quote request_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/convert":{"post":{"summary":"Convert","description":"Read an inbound e-invoice — hybrid PDF/A-3, raw CII XML, or a UBL\ninvoice — and RE-EMIT it as a compliant MINIMUM-profile Factur-X PDF\n(2 credits, same price as /v1/render: see app/pipeline.run_conversion for\nwhy — it is render's own build step, fed by a parse+map stage instead of\na caller's JSON body). See app/services/convert.py for why the output is\nalways Profile.MINIMUM.","operationId":"convert_v1_convert_post","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency-Key"}},{"name":"x-api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Api-Key"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_convert_v1_convert_post"}}}},"responses":{"200":{"description":"The re-emitted, Profile.MINIMUM hybrid Factur-X PDF/A-3 document — self-checked the same way /v1/render's output is (see app/pipeline.build_invoice_pdf).","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}},"headers":{"Content-Disposition":{"description":"attachment; filename=\"converted-invoice.pdf\"","schema":{"type":"string"}},"X-Credits-Remaining":{"description":"Credits left after this call.","schema":{"type":"integer"}}}},"422":{"description":"The source document could not be honestly re-emitted. Three distinct causes share this status, told apart by `code`: `unparseable_invoice` (could not be read at all — same reader as /v1/extract), `unconvertible_pdf_text` (a plain PDF's text layer was read but did not carry every MINIMUM-profile field — see the error's `missing_fields` and `found`), or `unconvertible_invoice` (it parsed, but a required field was missing or its own totals disagree with themselves, BR-CO-15). Nothing was charged for any of the three.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"400":{"description":"The upload was empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"402":{"description":"Not enough credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"This Idempotency-Key was already used for a different document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"413":{"description":"The request body exceeds the maximum size.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-minute rate limit, monthly cap, or free allowance exhausted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"500":{"description":"Unexpected internal failure. Quote request_id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"503":{"description":"A dependency the request needs is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"ApiKeyAuth":[]}]}}},"components":{"schemas":{"Address":{"properties":{"line1":{"type":"string","title":"Line1"},"line2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Line2"},"city":{"type":"string","title":"City"},"postcode":{"type":"string","title":"Postcode"},"country":{"type":"string","maxLength":2,"minLength":2,"title":"Country"}},"additionalProperties":false,"type":"object","required":["line1","city","postcode","country"],"title":"Address"},"Body_convert_v1_convert_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_convert_v1_convert_post"},"Body_extract_try_v1_extract_try_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_extract_try_v1_extract_try_post"},"Body_extract_v1_extract_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_extract_v1_extract_post"},"Body_validate_try_v1_validate_try_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_validate_try_v1_validate_try_post"},"Body_validate_v1_validate_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_validate_v1_validate_post"},"DeliverTo":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"line1":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Line1"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"postcode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Postcode"},"country":{"type":"string","maxLength":2,"minLength":2,"title":"Country"}},"additionalProperties":false,"type":"object","required":["country"],"title":"DeliverTo","description":"BG-13 delivery information — the part BR-IC-12 actually needs.\n\nOnly the country is required, and that is measured rather than assumed: the\nengine accepts an invoice whose parties have no street, city or postcode\n(increment 37), so demanding a full address here would refuse lawful\ndocuments. What it does insist on for an intra-community supply is the\ncountry the goods went TO — that is the whole point of the exemption."},"EngineHealth":{"properties":{"available":{"type":"boolean","title":"Available"}},"type":"object","required":["available"],"title":"EngineHealth"},"EngineInfo":{"properties":{"status":{"type":"string","title":"Status"},"version":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Version"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms"},"scenario":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scenario"}},"type":"object","required":["status"],"title":"EngineInfo"},"ExtractParty":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"vat_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vat Id"},"legal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legal Id"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"}},"type":"object","title":"ExtractParty","description":"Seller/buyer header fields (services/extract.py `_cii_party`/`_ubl_party`)."},"ExtractResponse":{"properties":{"format":{"type":"string","title":"Format"},"profile":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile"},"invoice_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Invoice Number"},"type_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Type Code"},"issue_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Issue Date"},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency"},"buyer_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Reference"},"seller":{"anyOf":[{"$ref":"#/components/schemas/ExtractParty"},{"type":"null"}]},"buyer":{"anyOf":[{"$ref":"#/components/schemas/ExtractParty"},{"type":"null"}]},"line_count":{"type":"integer","title":"Line Count","default":0},"tax_breakdown":{"items":{"$ref":"#/components/schemas/ExtractTaxRow"},"type":"array","title":"Tax Breakdown"},"totals":{"$ref":"#/components/schemas/ExtractTotals"}},"type":"object","required":["format"],"title":"ExtractResponse","description":"Response schema for POST /v1/extract and /v1/extract/try.\n\nMirrors the dict shape services/extract.extract_invoice() returns, typed so\nthe contract shows up in the OpenAPI schema instead of an opaque `object`.\nAmounts are kept as the STRING the source document carried (never coerced to\nfloat) — the same \"never launder a number\" discipline ValidateResponse\napplies to verdicts, applied here to money."},"ExtractTaxRow":{"properties":{"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"},"rate":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rate"},"basis":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Basis"},"tax":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tax"}},"type":"object","title":"ExtractTaxRow","description":"One BG-23 tax breakdown row."},"ExtractTotals":{"properties":{"tax_basis_total":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tax Basis Total"},"tax_total":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tax Total"},"grand_total":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Grand Total"},"due_payable":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Due Payable"}},"type":"object","title":"ExtractTotals"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthzResponse":{"properties":{"status":{"type":"string","title":"Status"},"engines":{"additionalProperties":{"$ref":"#/components/schemas/EngineHealth"},"type":"object","title":"Engines"}},"type":"object","required":["status","engines"],"title":"HealthzResponse"},"Invoice":{"properties":{"profile":{"$ref":"#/components/schemas/Profile"},"invoice_number":{"type":"string","title":"Invoice Number"},"issue_date":{"type":"string","format":"date","title":"Issue Date"},"type_code":{"type":"string","title":"Type Code","default":"380"},"currency":{"type":"string","title":"Currency","default":"EUR"},"buyer_reference":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Buyer Reference"},"seller":{"$ref":"#/components/schemas/Party"},"buyer":{"$ref":"#/components/schemas/Party"},"lines":{"items":{"$ref":"#/components/schemas/LineItem"},"type":"array","title":"Lines"},"payment":{"anyOf":[{"$ref":"#/components/schemas/Payment"},{"type":"null"}]},"deliver_to":{"anyOf":[{"$ref":"#/components/schemas/DeliverTo"},{"type":"null"}]},"refs":{"$ref":"#/components/schemas/References"},"notes":{"items":{"type":"string"},"type":"array","title":"Notes"},"locale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale"},"exemption_reasons":{"additionalProperties":{"type":"string"},"type":"object","title":"Exemption Reasons"},"claimed_grand_total":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Claimed Grand Total"},"tax_basis_total_input":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Tax Basis Total Input"},"tax_total_input":{"anyOf":[{"type":"number"},{"type":"string"},{"type":"null"}],"title":"Tax Total Input"},"tax_breakdown":{"items":{"$ref":"#/components/schemas/TaxSubtotal"},"type":"array","title":"Tax Breakdown"},"line_total":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Line Total","default":"0"},"tax_basis_total":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Tax Basis Total","default":"0"},"tax_total":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Tax Total","default":"0"},"grand_total":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Grand Total","default":"0"},"due_payable":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Due Payable","default":"0"}},"additionalProperties":false,"type":"object","required":["profile","invoice_number","issue_date","seller","buyer"],"title":"Invoice"},"LineItem":{"properties":{"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"quantity":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Quantity"},"unit_code":{"type":"string","title":"Unit Code","default":"C62"},"net_price":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Net Price"},"tax_category":{"$ref":"#/components/schemas/TaxCategory","default":"S"},"tax_rate":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Tax Rate","default":"0"}},"additionalProperties":false,"type":"object","required":["id","name","quantity","net_price"],"title":"LineItem"},"MeResponse":{"properties":{"plan":{"type":"string","title":"Plan"},"credits_remaining":{"type":"integer","title":"Credits Remaining"},"monthly_credits_used":{"type":"integer","title":"Monthly Credits Used"},"monthly_cap":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Monthly Cap"},"free_runs_used":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Free Runs Used"},"free_runs_limit":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Free Runs Limit"}},"type":"object","required":["plan","credits_remaining","monthly_credits_used"],"title":"MeResponse","description":"What the key holder is entitled to right now.\n\nExists so a customer can answer \"how many credits are left?\" without making\na billable call to read the X-Credits-Remaining header. It reports the\nledger's own numbers — never a cached or estimated figure, because a balance\nthat disagrees with billing is worse than no balance at all."},"Party":{"properties":{"name":{"type":"string","title":"Name"},"vat_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vat Id"},"tax_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tax Number"},"legal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Legal Id"},"address":{"$ref":"#/components/schemas/Address"},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email"}},"additionalProperties":false,"type":"object","required":["name","address"],"title":"Party"},"Payment":{"properties":{"means_code":{"type":"string","title":"Means Code","default":"58"},"iban":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Iban"},"bic":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bic"},"payee_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payee Name"},"terms_text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Terms Text"},"due_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Due Date"}},"additionalProperties":false,"type":"object","title":"Payment"},"Profile":{"type":"string","enum":["MINIMUM","BASIC","EN16931"],"title":"Profile"},"References":{"properties":{"purchase_order":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Purchase Order"},"contract":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Contract"},"delivery_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Delivery Date"}},"additionalProperties":false,"type":"object","title":"References"},"TaxCategory":{"type":"string","enum":["S","Z","E","AE","K","G"],"title":"TaxCategory","description":"BT-118 / UNCL5305, the subset we support."},"TaxSubtotal":{"properties":{"category":{"$ref":"#/components/schemas/TaxCategory"},"rate":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Rate"},"basis":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Basis"},"tax":{"anyOf":[{"type":"number"},{"type":"string"}],"title":"Tax"},"exemption_reason":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Exemption Reason"}},"additionalProperties":false,"type":"object","required":["category","rate","basis","tax"],"title":"TaxSubtotal","description":"BG-23 row — computed, never accepted from the client."},"ValidateResponse":{"properties":{"pdfa_pass":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Pdfa Pass"},"schematron_pass":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Schematron Pass"},"profile_detected":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Profile Detected"},"is_hybrid_einvoice":{"type":"boolean","title":"Is Hybrid Einvoice","default":false},"issues":{"items":{"$ref":"#/components/schemas/ValidationIssue"},"type":"array","title":"Issues"},"engines":{"additionalProperties":{"$ref":"#/components/schemas/EngineInfo"},"type":"object","title":"Engines"}},"type":"object","title":"ValidateResponse"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"ValidationIssue":{"properties":{"engine":{"type":"string","title":"Engine"},"severity":{"type":"string","title":"Severity","default":"error"},"rule":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rule"},"message":{"type":"string","title":"Message"},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location"}},"type":"object","required":["engine","message"],"title":"ValidationIssue"},"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["type","code","message","request_id"],"properties":{"type":{"type":"string","description":"Stable category; switch on this for behaviour.","enum":["auth_error","rate_limit_error","billing_error","validation_error","invalid_request_error","engine_error","idempotency_error"]},"code":{"type":"string","description":"Specific cause, e.g. insufficient_credits."},"message":{"type":"string"},"param":{"type":"string"},"missing":{"type":"array","items":{"type":"string"},"description":"Unmet profile requirements, one per entry (code=profile_requirements only)."},"missing_fields":{"type":"array","items":{"type":"string"},"description":"Required fields the PDF's text layer did not carry (code=unconvertible_pdf_text only)."},"found":{"type":"object","description":"Raw label:value pairs the text-layer parse DID read off the page (code=unconvertible_pdf_text only)."},"request_id":{"type":"string","description":"Also returned as X-Request-Id. Quote it to support."},"doc_url":{"type":"string"}}}}}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"}}}}