{"openapi":"3.1.0","info":{"title":"Attestwire API","version":"1.0.0","summary":"EN 16931 e-invoice validation and generation.","description":"Validate an invoice against EN 16931 and its national CIUS rules (XRechnung, Peppol BIS 3, Factur-X) and get back errors that teach the regulation: every failure carries the official rule id, the business term it constrains, what the rule requires, and how to fix it.","contact":{"name":"Attestwire","url":"https://attestwire.com"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://api.attestwire.com","description":"Production"}],"tags":[{"name":"Invoices","description":"Validation and generation. Metered."},{"name":"Keys","description":"Self-serve API key issuance."},{"name":"Billing","description":"Stripe-hosted subscription checkout."},{"name":"Meta","description":"Health and machine-readable descriptions."}],"security":[{"bearerAuth":[]}],"paths":{"/v1/validate":{"post":{"tags":["Invoices"],"operationId":"validateInvoice","summary":"Validate an invoice against EN 16931 business rules","description":"Counts as one document against your monthly allowance. A response with `valid: false` is a successful call — it still costs a document.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceInput"}}}},"responses":{"200":{"description":"Validation ran. Check `valid`.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Documents allowed this month."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Documents left this month."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time of the next reset (00:00 UTC on the 1st)."},"X-RateLimit-Period":{"schema":{"type":"string"},"description":"Current period, YYYY-MM."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationResult"}}}},"400":{"description":"Malformed JSON, or the body is not an invoice object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Monthly document allowance exhausted.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Documents allowed this month."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Documents left this month."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time of the next reset (00:00 UTC on the 1st)."},"X-RateLimit-Period":{"schema":{"type":"string"},"description":"Current period, YYYY-MM."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/generate":{"post":{"tags":["Invoices"],"operationId":"generateInvoice","summary":"Generate compliant invoice XML","description":"Validates first: a fatal rule failure returns 422 with the same teaching errors as /v1/validate, and does not consume a document. Supported profiles in this build: en16931, xrechnung-ubl, peppol-bis-3.","parameters":[{"name":"format","in":"query","required":false,"description":"Set to `xml` to receive the raw document as application/xml instead of a JSON envelope.","schema":{"type":"string","enum":["json","xml"],"default":"json"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceInput"}}}},"responses":{"200":{"description":"The generated document.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Documents allowed this month."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Documents left this month."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time of the next reset (00:00 UTC on the 1st)."},"X-RateLimit-Period":{"schema":{"type":"string"},"description":"Current period, YYYY-MM."}},"content":{"application/json":{"schema":{"type":"object","required":["xml","profile"],"properties":{"xml":{"type":"string","description":"The complete XML document."},"profile":{"type":"string","enum":["en16931","xrechnung-ubl","xrechnung-cii","facturx-en16931","peppol-bis-3"]},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/TeachingError"}}}}},"application/xml":{"schema":{"type":"string"}}}},"401":{"description":"Missing or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"The invoice does not satisfy the profile; nothing was generated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationResult"}}}},"429":{"description":"Monthly document allowance exhausted.","headers":{"X-RateLimit-Limit":{"schema":{"type":"integer"},"description":"Documents allowed this month."},"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Documents left this month."},"X-RateLimit-Reset":{"schema":{"type":"integer"},"description":"Unix time of the next reset (00:00 UTC on the 1st)."},"X-RateLimit-Period":{"schema":{"type":"string"},"description":"Current period, YYYY-MM."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Generation is not available in this build.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/keys":{"post":{"tags":["Keys"],"operationId":"createKey","summary":"Issue a free-tier API key","description":"No authentication. One free key per email address — the plaintext key is returned exactly once and is not recoverable afterwards.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","maxLength":254}}}}}},"responses":{"201":{"description":"Key created. Store `key` now; it is never shown again.","content":{"application/json":{"schema":{"type":"object","required":["key","tier","monthly_limit","created"],"properties":{"key":{"type":"string","examples":["aw_live_xxxxxxxx"]},"tier":{"type":"string","enum":["free","starter","scale"]},"monthly_limit":{"type":"integer"},"created":{"type":"string","format":"date-time"},"warning":{"type":"string"}}}}}},"400":{"description":"Missing or malformed email address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A free key already exists for this email address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many key requests from this IP address today.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/keys/rotate":{"post":{"tags":["Keys"],"operationId":"rotateKey","summary":"Replace the calling key with a new one","description":"Authenticated by the key being replaced — that bearer token IS the proof of ownership, and there is deliberately no email-triggered variant. No request body. Consumes no documents.\n\nEverything follows the new key atomically: tier and limit, the current period's usage (rotation does NOT reset your quota), any past_due flag, the original `created` date, and the Stripe subscription link — including billing events that were already in flight when you rotated.\n\nThe plaintext key is returned exactly once, and the old key answers 410 `key_rotated` from the moment this call succeeds. Deploy first, then rotate. Limited to 3 rotations per key per UTC day; the limit follows the key through a rotation, so chaining does not reset it. Two simultaneous calls cannot both succeed — the loser gets 409.\n\nIf you no longer HAVE the key there is no self-serve path: email hello@attestwire.com from the address on your Stripe billing receipt.","responses":{"200":{"description":"Rotated. Store `key` now; it is never shown again. The other fields are the continuity proof — same tier, same usage.","content":{"application/json":{"schema":{"type":"object","required":["key","tier","monthly_limit","period","used","rotated_at"],"properties":{"key":{"type":"string","examples":["aw_live_xxxxxxxx"]},"tier":{"type":"string","enum":["free","starter","scale"]},"monthly_limit":{"type":"integer"},"period":{"type":"string","examples":["2026-08"]},"used":{"type":"integer","description":"Documents already used this period, carried over from the old key."},"remaining":{"type":"integer"},"resets_at":{"type":"string","format":"date-time"},"created":{"type":["string","null"],"format":"date-time","description":"Original signup date, unchanged by rotation."},"rotated_at":{"type":"string","format":"date-time"},"rotations_remaining_today":{"type":"integer"},"warning":{"type":"string"}}}}}},"401":{"description":"Missing or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`rotation_already_done` — this key has already been rotated, or a concurrent rotate call won the race. The replacement was returned to that call.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"`key_rotated` — the key presented has itself been retired by an earlier rotation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rotation_rate_limited` — more than 3 rotations of this key today.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/usage":{"get":{"tags":["Keys"],"operationId":"getUsage","summary":"Current period usage for the calling key","description":"Free, and does not consume a document.","responses":{"200":{"description":"Usage for the current UTC month.","content":{"application/json":{"schema":{"type":"object","properties":{"tier":{"type":"string"},"period":{"type":"string","examples":["2026-08"]},"used":{"type":"integer"},"limit":{"type":"integer"},"remaining":{"type":"integer"},"resets_at":{"type":"string","format":"date-time"},"past_due":{"type":"boolean","description":"True when the last subscription payment failed and Stripe is still retrying. The paid allowance is UNCHANGED while this is true; only if every retry fails does the key drop to the free tier. This is the only in-band billing warning the API gives — poll it if you want to detect trouble programmatically."},"billing_warning":{"type":"string","description":"Human-readable explanation, present only when past_due is true."}}}}}},"401":{"description":"Missing or unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/billing/checkout":{"post":{"tags":["Billing"],"operationId":"createCheckout","summary":"Start a Stripe Checkout session to upgrade a key","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["key","plan"],"properties":{"key":{"type":"string","description":"The API key to upgrade."},"plan":{"type":"string","enum":["starter","scale"]}}}}}},"responses":{"200":{"description":"Redirect the customer to `url`.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"session_id":{"type":"string"}}}}}},"400":{"description":"Unknown plan, or malformed body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Unknown API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"This key already has a subscription. A second checkout would create a second subscription and bill for both plans; change plan in the Stripe billing portal instead, where the switch is prorated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Billing is not enabled on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Billing"],"operationId":"checkoutStatus","summary":"Whether billing is enabled on this deployment","security":[],"responses":{"405":{"description":"Billing is enabled; use POST to create a session.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Billing is not enabled on this deployment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/health":{"get":{"tags":["Meta"],"operationId":"health","summary":"Liveness and build capabilities","security":[],"responses":{"200":{"description":"Service is up.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"runtime":{"type":"string"},"generation":{"type":"boolean"},"billing":{"type":"boolean"}}}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Send your key as `Authorization: Bearer aw_live_...`."}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"Stable machine-readable code. Branch on this, not on `message`."},"message":{"type":"string","description":"Human-readable explanation."},"docs":{"type":"string","format":"uri"}}},"TeachingError":{"type":"object","description":"One rule failure, with enough context to fix the invoice without opening the specification.","required":["rule","field","severity","message","fix","docsUrl"],"properties":{"rule":{"type":"string","description":"Official rule id: BR-*, BR-DE-*, PEPPOL-EN16931-R*.","examples":["BR-DE-15"]},"field":{"description":"Business term(s) the rule constrains.","oneOf":[{"type":"string","examples":["BT-10"]},{"type":"array","items":{"type":"string"}}]},"severity":{"type":"string","enum":["fatal","warning"]},"message":{"type":"string"},"fix":{"type":"string"},"xpath":{"type":"string"},"docsUrl":{"type":"string","format":"uri"},"example":{"type":"string"}}},"ValidationResult":{"type":"object","required":["valid","profile","errors","warnings"],"properties":{"valid":{"type":"boolean","description":"True when there are no fatal errors. Warnings do not affect it."},"profile":{"type":"string","enum":["en16931","xrechnung-ubl","xrechnung-cii","facturx-en16931","peppol-bis-3"]},"errors":{"type":"array","items":{"$ref":"#/components/schemas/TeachingError"}},"warnings":{"type":"array","items":{"$ref":"#/components/schemas/TeachingError"}}}},"Party":{"type":"object","required":["name","address"],"properties":{"name":{"type":"string","description":"BT-27 (seller) / BT-44 (buyer)."},"legalName":{"type":"string","description":"BT-27 / BT-44 registered legal name, when it differs from the trading name."},"vatId":{"type":"string","description":"BT-31 / BT-48, country-prefixed.","examples":["DE123456789"]},"taxRegistrationId":{"type":"string","description":"BT-32 tax registration identifier (e.g. German Steuernummer)."},"legalRegistrationId":{"type":"string","description":"BT-30 / BT-47 legal registration identifier (e.g. HRB number)."},"address":{"type":"object","required":["city","postalCode","countryCode"],"properties":{"line1":{"type":"string","description":"BT-35 / BT-50."},"line2":{"type":"string","description":"BT-36 / BT-51."},"city":{"type":"string","description":"BT-37 / BT-52."},"postalCode":{"type":"string","description":"BT-38 / BT-53."},"countrySubdivision":{"type":"string","description":"BT-39 / BT-54 country subdivision (Bundesland, région, …)."},"countryCode":{"type":"string","description":"BT-40 / BT-55, ISO 3166-1 alpha-2.","examples":["DE"]}}},"electronicAddress":{"type":"object","description":"BT-34 / BT-49 — Peppol participant identifier. Required as a warning by PEPPOL-EN16931-R010/R020: without it the document cannot be addressed on the network.","required":["schemeId","value"],"properties":{"schemeId":{"type":"string","description":"Peppol EAS / ISO 6523 ICD code.","examples":["9930"]},"value":{"type":"string"}}},"contact":{"type":"object","description":"BG-6 / BG-9. For the seller under XRechnung, BR-DE-2/5/6/7 make all three of name, phone and email individually mandatory.","properties":{"name":{"type":"string","description":"BT-41 / BT-56."},"email":{"type":"string","description":"BT-43 / BT-58."},"phone":{"type":"string","description":"BT-42 / BT-57."}}}}},"PaymentInstructions":{"type":"object","description":"BG-16. Required for XRechnung profiles by BR-DE-1.","required":["meansCode"],"properties":{"meansCode":{"type":"string","description":"BT-81 UNTDID 4461: \"58\" SEPA credit transfer, \"30\" credit transfer, \"59\" SEPA direct debit, \"48\" card, \"57\" standing order, \"97\" clearing.","examples":["58"]},"meansName":{"type":"string","description":"BT-82 payment means text."},"iban":{"type":"string","description":"BT-84 payment account identifier."},"accountName":{"type":"string","description":"BT-85 payment account name."},"bic":{"type":"string","description":"BT-86 payment service provider identifier."},"remittanceInformation":{"type":"string","description":"BT-83 payment reference."}}},"DeclaredTotals":{"type":"object","description":"Optional caller-computed totals. Checked against ours under BR-CO-10/13/14/15. Generation always emits the computed values — these exist so you can catch a mismatch in your own accounting system before a tax authority does.","properties":{"lineExtensionAmount":{"type":"number","description":"BT-106."},"taxExclusiveAmount":{"type":"number","description":"BT-109."},"taxAmount":{"type":"number","description":"BT-110."},"taxInclusiveAmount":{"type":"number","description":"BT-112."},"payableAmount":{"type":"number","description":"BT-115."}}},"InvoiceLine":{"type":"object","required":["id","description","quantity","unitCode","unitPrice","vatCategory"],"properties":{"id":{"type":"string","description":"BT-126."},"description":{"type":"string","description":"BT-153 item name."},"longDescription":{"type":"string","description":"BT-154 item description, when it should differ from the name."},"quantity":{"type":"number","description":"BT-129."},"unitCode":{"type":"string","description":"BT-130, UN/ECE rec 20.","examples":["C62","HUR"]},"unitPrice":{"type":"number","description":"BT-146."},"baseQuantity":{"type":"number","description":"BT-149 price base quantity. Defaults to 1."},"vatCategory":{"type":"string","enum":["S","Z","E","AE","K","G","O"],"description":"BT-151, UNCL5305: S standard, Z zero-rated, E exempt, AE reverse charge, K intra-community, G export, O out of scope."},"vatRate":{"type":"number","description":"BT-152, percent."},"note":{"type":"string","description":"BT-127 line note."}}},"InvoiceInput":{"type":"object","required":["profile","invoiceNumber","issueDate","currency","seller","buyer","lines"],"properties":{"profile":{"type":"string","enum":["en16931","xrechnung-ubl","xrechnung-cii","facturx-en16931","peppol-bis-3"],"description":"Selects the rule set. Narrower profiles add rules; they never remove core ones."},"invoiceNumber":{"type":"string","description":"BT-1."},"issueDate":{"type":"string","format":"date","description":"BT-2, ISO 8601."},"invoiceTypeCode":{"type":"string","description":"BT-3 UNTDID 1001. Defaults to \"380\" (commercial invoice). BR-DE-17 restricts XRechnung to 326/380/381/384/389/875/876/877.","examples":["380"]},"dueDate":{"type":"string","format":"date","description":"BT-9 payment due date."},"currency":{"type":"string","description":"BT-5, ISO 4217.","examples":["EUR"]},"note":{"type":"string","description":"BT-22 free-text note."},"buyerReference":{"type":"string","description":"BT-10. Required by BR-DE-15 under XRechnung (Leitweg-ID)."},"orderReference":{"type":"string","description":"BT-13 purchase order reference."},"seller":{"$ref":"#/components/schemas/Party"},"buyer":{"$ref":"#/components/schemas/Party"},"lines":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/InvoiceLine"}},"paymentTerms":{"type":"string","description":"BT-20."},"payment":{"$ref":"#/components/schemas/PaymentInstructions"},"deliveryDate":{"type":"string","format":"date","description":"BT-72 actual delivery date. Required with BT-80 by BR-IC-11 for category K."},"deliverToCountryCode":{"type":"string","description":"BT-80 deliver-to country. Required by BR-IC-12 for intra-community supply."},"vatExemptionReasons":{"type":"object","description":"BT-120 exemption reason text per VAT category. Defaults are applied for AE/K/G/O; category E has no standard text, so BR-E-10 requires you to supply one.","additionalProperties":{"type":"string"}},"declaredTotals":{"$ref":"#/components/schemas/DeclaredTotals"}}}}}}