{"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`, `extract`, `consistency`\nand `vat/verify` are reads and cost 1 credit each — `extract` also shares\n`validate`'s free monthly allowance. `render` and `convert` cost 2 credits each —\nboth build a document (WeasyPrint plus two JVM validators) and neither has a free\ntier; `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.\n\n`vat/verify` verifies an EU VAT id against the official EU VIES service and returns\na signed Ed25519 evidence certificate; pass `requester_vat` (your own EU VAT id) to\nembed VIES's OFFICIAL consultation number in the certificate. Anyone can verify a\ncertificate offline against the key at `/v1/vat/attestation-key`. `/v1/vat/verify/try`\nis the no-key trial (verdict only, no certificate).","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/consistency/try":{"post":{"summary":"Consistency Try","description":"No-key trial for CONSISTENCY — the tamper-check counterpart of\n/v1/extract/try. Shares the SAME IP+month trial pool (trial.consume /\ntrial.trial_key) as validate/try and extract/try: all three are anonymous\nreads drawing on one \"let a stranger see real output before signing up\"\nallowance, so trying several in a session spends one budget, not three.\n\nA parse failure (ExtractError) returns the trial — the same null rule\neverywhere else applies, here to a 422. check_consistency is pure Decimal\nmaths over the parsed header (no engine, no I/O), so a consistency trial\ncosts no more than an extract trial and adds no new failure mode.","operationId":"consistency_try_v1_consistency_try_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_consistency_try_v1_consistency_try_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsistencyResponse"}}}},"422":{"description":"Same as /v1/consistency's 422 (code=unparseable_invoice) — this is the no-key trial, so nothing was ever billable.","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/vat/verify/try":{"get":{"summary":"Vat Verify Try","description":"No-key trial for VAT VERIFICATION — checks an EU VAT id against the official\nEU VIES service and returns valid/invalid plus the registered name/address.\n\nRate-limited per IP like render/try (its OWN scope, does NOT consume the\nvalidate/extract trial pool): a lookup is a single external EU call, so a\nper-IP burst limit is the right abuse bound. Honest on failure — if VIES is\ndown or gives no clean verdict we return 503 'vat_service_unavailable', never\na fabricated 'valid' (the null rule, applied to an external dependency). The\nreceive-side companion to the France mandate: a business must trust a\nsupplier's VAT id before it can reclaim the tax.","operationId":"vat_verify_try_v1_vat_verify_try_get","parameters":[{"name":"vat","in":"query","required":true,"schema":{"type":"string","description":"VAT id, e.g. FR12345678901","title":"Vat"},"description":"VAT id, e.g. FR12345678901"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"The VAT id was malformed (code=malformed_vat). No-key trial, nothing billable.","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":"The EU VIES service was unavailable, or the trial allowance could not be measured (code=vat_service_unavailable / trial unavailable). No-key trial.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/v1/vat/attestation-key":{"get":{"summary":"Vat Attestation Key","description":"The Ed25519 PUBLIC key that signs VAT-verify attestations, so anyone can\nverify an evidence certificate OFFLINE (no account, no call to us). Raw\n32-byte key, base64. 503 while signing is unconfigured.","operationId":"vat_attestation_key_v1_vat_attestation_key_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/vat/verify":{"post":{"summary":"Vat Verify","description":"Verify an EU VAT id against the official VIES service and return the verdict\nPLUS a signed Ed25519 evidence certificate (1 credit). Paid counterpart of the\nfree /v1/vat/verify/try: the credit buys the ATTESTATION — an offline-verifiable\nproof (against the key at /v1/vat/attestation-key) that FacturProof asserted this\nvalid/invalid verdict at this time — not the bare lookup. Money path (hold ->\nVIES -> sign -> settle/refund) lives in app/pipeline.run_vat_verify; the null\nrule applies (malformed id -> refunded 422, VIES outage -> refunded 503).\n\nPass requester_vat (your own EU VAT id) to obtain VIES's OFFICIAL consultation\nnumber inside the signed certificate — the strongest evidence, cross-checkable\nwith the tax authority. A malformed or VIES-rejected requester is a refunded 422\n(your input), never a charge.","operationId":"vat_verify_v1_vat_verify_post","parameters":[{"name":"vat","in":"query","required":true,"schema":{"type":"string","description":"VAT id, e.g. FR12345678901","title":"Vat"},"description":"VAT id, e.g. FR12345678901"},{"name":"requester_vat","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional: YOUR EU VAT id. When supplied and valid, VIES issues an official consultation number (legal proof of the check), included inside the signed attestation.","title":"Requester Vat"},"description":"Optional: YOUR EU VAT id. When supplied and valid, VIES issues an official consultation number (legal proof of the check), included inside the signed attestation."},{"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"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VatVerifyResponse"}}}},"422":{"description":"The VAT id or requester_vat could not be accepted, told apart by `code`: `malformed_vat` (not a 2-letter country code + number), `malformed_requester` (requester_vat is malformed), or `invalid_requester` (the EU VIES service rejected your requester_vat — it must be a currently valid EU VAT id to get an official consultation number). 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":"The EU VIES service was unavailable and no clean verdict could be obtained (code=vat_service_unavailable). A verdict is never fabricated; nothing was charged — retry shortly.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}},"security":[{"ApiKeyAuth":[]}]}},"/v1/vat/batch":{"post":{"summary":"Vat Batch","description":"Verify a LIST of EU VAT ids in one call (VAT-Watch). Billed per WELL-FORMED id\n(N ids -> N credits); a malformed id is reported but not charged, a VIES outage\nrefunds the whole batch (503), and only real valid/invalid verdicts are billed —\nthe same null rule as /v1/vat/verify, applied to a batch. Verdict-only: for a\nsingle SIGNED certificate (+ optional consultation number) use /v1/vat/verify.\nMoney path (reserve N -> lookup -> settle/refund) is app/pipeline.run_vat_batch.","operationId":"vat_batch_v1_vat_batch_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/BatchVerifyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchVerifyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/vat/watches":{"get":{"summary":"Vat Watches List","description":"List the supplier VATs this account monitors (VAT-Watch). Free — moves no\ncredits. `limit` is the subscription entitlement (0 = no active subscription),\n`used` the number of active watches.","operationId":"vat_watches_list_v1_vat_watches_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/WatchListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Vat Watches Create","description":"Register (or re-arm) a supplier VAT for nightly monitoring (VAT-Watch).\n\nRequires an active subscription (accounts.vat_watch_limit > 0). A NEW watch is\nrefused with 402 once the active count reaches the plan's limit, but RE-ARMING an\nalready-active id (updating its webhook) is always allowed and consumes no slot.\nMoves NO credits — the flat monthly subscription is the meter, not per-call\ncredits. Set webhook_url + webhook_secret to receive signed change-alerts.","operationId":"vat_watches_create_v1_vat_watches_post","parameters":[{"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/WatchCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchItem"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Vat Watches Delete","description":"Stop monitoring a supplier VAT. Idempotent — deleting an unknown id returns 200\nwith deleted=false, never a 404. Frees a slot against the subscription limit.","operationId":"vat_watches_delete_v1_vat_watches_delete","parameters":[{"name":"vat_id","in":"query","required":true,"schema":{"type":"string","description":"The watched VAT id to stop monitoring","title":"Vat Id"},"description":"The watched VAT id to stop monitoring"},{"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/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"}}}}}}},"/v1/eudr/geojson/try":{"post":{"summary":"Eudr Geojson Try","description":"No-key trial for EUDR geolocation PRE-FLIGHT — validate the GeoJSON an operator\nwill attach to a Due Diligence Statement (DDS) BEFORE submitting it to EU TRACES.\n\nPOST the GeoJSON as the raw JSON request body. Returns {valid, feature_count,\ngeometry_types, total_area_ha, findings[]} — the geometry contract TRACES enforces\n(FeatureCollection shape, WGS84 coordinate ranges, closed polygon rings) plus the\nregulation's point-vs-polygon rule (a Point only for a plot < 4 ha). Keyless and\nfree: the check is a LOCAL computation with no external call, so it is bounded by a\nper-IP rate limit (and the global body-size limit) — it does NOT burn the VIES trial\npool. The full DDS envelope (operator, HS codes, producer country) is a later layer:\nvalid:true here means the GEOMETRY will not be rejected, not that the whole statement\nis complete. Honest by construction — an unparseable body is a 422, never a fake pass.","operationId":"eudr_geojson_try_v1_eudr_geojson_try_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/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/consistency":{"post":{"summary":"Consistency","description":"Read an inbound e-invoice (hybrid PDF/A-3, CII XML, or UBL) and return its\nheader PLUS internal-consistency findings — the receive-side tamper / mis-issue\ncheck (1 credit, priced as extract). Paid-only; mirrors /v1/extract's money\npath (auth, size cap, credit hold, idempotent replay). See\napp/pipeline.run_consistency.","operationId":"consistency_v1_consistency_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_consistency_v1_consistency_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsistencyResponse"}}}},"422":{"description":"The inbound invoice could not be read (code=unparseable_invoice — the same reader as /v1/extract). 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"},"AttestationCert":{"properties":{"payload":{"type":"object","title":"Payload"},"alg":{"type":"string","title":"Alg"},"key_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Key Id"},"signature":{"type":"string","title":"Signature"}},"type":"object","required":["alg","signature"],"title":"AttestationCert","description":"The signed Ed25519 evidence certificate carried on a VAT-verify response.\n\n`payload` is the exact canonical object that was signed — verify it offline\nagainst the public key at /v1/vat/attestation-key. It is kept as a free dict on\npurpose: the payload can gain fields (e.g. consultation_number) without a schema\nchange silently dropping them from a generated client's deserializer."},"BatchVerifyItem":{"properties":{"input":{"type":"string","title":"Input"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code"},"vat_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vat Number"},"valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Valid"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["input"],"title":"BatchVerifyItem","description":"One VAT id's result in a batch. `error` is set (and valid is null) for an id\nwe could not check — e.g. malformed_vat — never charged, never a fabricated verdict."},"BatchVerifyRequest":{"properties":{"vat_ids":{"items":{"type":"string"},"type":"array","title":"Vat Ids"}},"type":"object","title":"BatchVerifyRequest","description":"Request body for POST /v1/vat/batch: the VAT ids to verify."},"BatchVerifyResponse":{"properties":{"results":{"items":{"$ref":"#/components/schemas/BatchVerifyItem"},"type":"array","title":"Results"},"total":{"type":"integer","title":"Total"},"valid_count":{"type":"integer","title":"Valid Count"},"invalid_count":{"type":"integer","title":"Invalid Count"},"malformed_count":{"type":"integer","title":"Malformed Count"},"credits_charged":{"type":"integer","title":"Credits Charged"}},"type":"object","required":["results","total","valid_count","invalid_count","malformed_count","credits_charged"],"title":"BatchVerifyResponse","description":"Response schema for POST /v1/vat/batch (VAT-Watch). One verdict per id plus\nan aggregate. Billed per WELL-FORMED id: `credits_charged` == the number of ids\nthat were actually sent to VIES (malformed ids are reported but not charged)."},"Body_consistency_try_v1_consistency_try_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_consistency_try_v1_consistency_try_post"},"Body_consistency_v1_consistency_post":{"properties":{"file":{"type":"string","format":"binary","title":"File"}},"type":"object","required":["file"],"title":"Body_consistency_v1_consistency_post"},"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"},"ConsistencyFinding":{"properties":{"code":{"type":"string","title":"Code"},"message":{"type":"string","title":"Message"},"severity":{"type":"string","title":"Severity"}},"type":"object","required":["code","message","severity"],"title":"ConsistencyFinding","description":"One internal-consistency finding (see services/extract_consistency)."},"ConsistencyResponse":{"properties":{"invoice":{"$ref":"#/components/schemas/ExtractResponse"},"is_consistent":{"type":"boolean","title":"Is Consistent"},"findings":{"items":{"$ref":"#/components/schemas/ConsistencyFinding"},"type":"array","title":"Findings"},"attestation":{"anyOf":[{"$ref":"#/components/schemas/AttestationCert"},{"type":"null"}]}},"type":"object","required":["invoice","is_consistent"],"title":"ConsistencyResponse","description":"Response schema for POST /v1/consistency.\n\nThe extracted header PLUS the arithmetic/identity checks a receiver's own\nsystem would recompute (BR-CO-15 totals, BG-23 breakdown reconciliation,\nBR-CO-17 per-category tax, VAT-id presence). `is_consistent` is False iff any\nfinding is `error` severity — the receive-side tamper / mis-issue signal the\nFrance 1-Sep-2026 mandate makes necessary. Findings NEVER mutate the extracted\nnumbers; they only report what disagrees, same null-rule discipline as the\nverdict endpoints."},"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"}]},"payment_iban":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Payment Iban"},"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"},"attestation":{"anyOf":[{"$ref":"#/components/schemas/AttestationCert"},{"type":"null"}]}},"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"},"attestation":{"anyOf":[{"$ref":"#/components/schemas/AttestationCert"},{"type":"null"}]}},"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"},"VatVerifyResponse":{"properties":{"input":{"type":"string","title":"Input"},"country_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country Code"},"vat_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vat Number"},"valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Valid"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address"},"request_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Request Date"},"consultation_number":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Consultation Number"},"checked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Checked At"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"attestation":{"anyOf":[{"$ref":"#/components/schemas/AttestationCert"},{"type":"null"}]}},"type":"object","required":["input"],"title":"VatVerifyResponse","description":"Response schema for POST /v1/vat/verify.\n\nThe official EU VIES verdict PLUS a signed evidence certificate. `valid` follows\nthe same null rule as every verdict here — it is only ever a real true/false from\nVIES; an outage is a 503, never a fabricated verdict. `consultation_number` is\npresent only when requester_vat was supplied and VIES issued its OFFICIAL one.\n`attestation` is null only when signing is unconfigured — never an unsigned\ncertificate that would look authoritative but prove nothing."},"WatchCreateRequest":{"properties":{"vat_id":{"type":"string","title":"Vat Id"},"requester_vat":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requester Vat"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url"},"webhook_secret":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Secret"}},"type":"object","required":["vat_id"],"title":"WatchCreateRequest","description":"Request body for POST /v1/vat/watches — register/re-arm one supplier VAT to\nmonitor. `webhook_url`/`webhook_secret` are where signed change-alerts are POSTed\n(HMAC-SHA256 over the body in X-FacturProof-Signature). Only vat_id is required."},"WatchItem":{"properties":{"id":{"type":"integer","title":"Id"},"vat_id":{"type":"string","title":"Vat Id"},"requester_vat":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Requester Vat"},"webhook_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Webhook Url"},"active":{"type":"boolean","title":"Active","default":true},"last_valid":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Last Valid"},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name"},"last_checked_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Checked At"}},"type":"object","required":["id","vat_id"],"title":"WatchItem","description":"One monitored supplier VAT. Deliberately OMITS webhook_secret — a secret is\nwrite-only, never echoed back. last_* is the most recent VIES snapshot (null until\nthe nightly poller first checks it)."},"WatchListResponse":{"properties":{"watches":{"items":{"$ref":"#/components/schemas/WatchItem"},"type":"array","title":"Watches"},"used":{"type":"integer","title":"Used"},"limit":{"type":"integer","title":"Limit"}},"type":"object","required":["watches","used","limit"],"title":"WatchListResponse","description":"Response for GET /v1/vat/watches. `used`/`limit` are the entitlement counters —\n`limit` is the subscription's watch_limit (0 = no active subscription)."},"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"}}}}