Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Attestwire Meta API
version: 0.1.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) 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. Invoices go in and come out in both EN 16931 syntaxes, UBL 2.1 and UN/CEFACT CII; the profile chooses the syntax on generate, and on validate the document''s root element does. The `facturx-en16931` profile is checked against core EN 16931 only — there are no Factur-X-specific rules. PDF: Factur-X and ZUGFeRD files are CII XML inside a PDF/A-3 container. POST /v1/validate and POST /v1/parse open one sent as `application/pdf`, on every plan, and read the XML attached to it; /v1/validate also reports on the PDF. Of the PDF itself, the attachment (its name, /AFRelationship and MIME type) and the XMP metadata are checked, and each problem is an AW-PDF-* finding, none of them fatal; PDF/A conformance itself, and whether the page agrees with the XML, are not checked. The API builds one in exactly one case, profile `facturx-en16931`: a Factur-X / ZUGFeRD PDF (EN 16931 profile), a page drawn from the invoice, written as PDF/A-3B, with the CII embedded as `factur-x.xml` — watermarked as a preview on the free plan, and without the watermark on a paid plan. `POST /v1/generate?format=pdf` returns it, `POST /v1/bundle?format=pdf` puts it in an evidence bundle beside the XML and a signed validation report, and `POST /v1/send` with `"format": "pdf"` e-mails it. Every other invoice the API writes is XML. Sending: `POST /v1/send` e-mails one invoice to one address on request; the API does not operate Peppol or French-platform transmission.'
contact:
name: Attestwire
url: https://attestwire.com
license:
name: Proprietary
servers:
- url: https://api.attestwire.com
description: Production
security:
- bearerAuth: []
tags:
- name: Meta
description: Health and machine-readable descriptions.
paths:
/v1/versions:
get:
tags:
- Meta
operationId: versions
summary: Engine and rule-set versions, and what each release added
description: 'What this deployment is running, and what changed when. Free, unauthenticated, and cacheable for five minutes.
THE QUESTION IT ANSWERS is "why did a document that passed last month start failing?". Pin the npm library and your CI is bit-identical forever; the hosted API always runs the current rule set, because a compliance service that froze its rules would be answering last quarter''s regulation. Point a weekly job at this endpoint and a rule id appearing in `rules_added` is your answer, without a support ticket.
Every metered response also carries a `provenance` block naming the engine version, rule set and profile that judged THAT document. This endpoint is the same information for the deployment as a whole, plus the release history.
`rules_added` is DERIVED from the engine''s own changelog rather than typed, and the derivation is published in the body beside it: an id is attributed to the oldest release whose notes name it, which is usually — not always — the release that started enforcing it. Read it as where to look first, not as a specification. `rule_id_count` is every rule id the engine checks, which is more than the release notes have ever named.'
security: []
responses:
'200':
description: The running engine, the KoSIT record, and the release history.
content:
application/json:
schema:
$ref: '#/components/schemas/Versions'
examples:
current:
summary: This deployment. `releases` is elided to the newest entry here and complete in the response.
value:
engine: '@attestwire/en16931'
engine_version: 0.14.1
ruleset: en16931@0.14.1
rule_id_count: 321
kosit_conformance:
recorded: '2026-09-24'
validator: 1.6.3
configuration: 3.0.2
configuration_published: '2026-08-31'
releases:
- version: 0.14.1
date: '2026-10-04'
rules_added:
- BR-AE-08
- BR-E-08
- BR-G-08
- BR-IC-08
- BR-Z-08
derivation: Each rule id is attributed to the oldest release of @attestwire/en16931 whose changelog names it (an abbreviated sibling such as "BR-DEC-19/-20" counts), and ids the compiled engine does not actually check are dropped. A short list of corrections overrides this where the notes name an id before it existed, for example as deferred or as a renamed id. It is a record of when a rule id entered the release notes, which is usually but not always the release that started enforcing it. Treat it as "look here first", not as a specification.
'500':
description: Unhandled fault while reading the version document. Never expected; the body carries no detail on purpose (a fault message can quote the offending value). Retry, and email hello@attestwire.com if it persists.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
examples:
internal_error:
summary: Unhandled fault
value:
error: internal_error
message: Something broke on our side. Try again.
/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
required:
- ok
- runtime
- generation
- billing
- billing_configured
- billing_reachable
- engine_version
- deploy_tag
- metering
- period
properties:
ok:
type: boolean
runtime:
type: string
examples:
- cloudflare-workers
generation:
type: boolean
billing:
description: 'Whether card payment is live. `true` means a real card will be charged; `"test-mode"` means checkout works but against the provider''s sandbox; `false` means POST /v1/billing/checkout answers 503.
UNCHANGED, including the name: it is what it has always been, and the two fields below are additions rather than a rename. What it does NOT mean is that the provider is answering — see `billing_reachable`.'
oneOf:
- type: boolean
- type: string
enum:
- test-mode
- misconfigured
billing_configured:
type: boolean
description: Whether the billing credentials are present on this deployment. This is the question `billing` has always actually answered, said plainly.
billing_reachable:
type:
- boolean
- 'null'
description: 'Whether the payment provider answered a cheap, read-only, side-effect-free call. `null` means unconfigured, or not yet checked on this instance — which is not the same as unreachable and is never reported as `false`.
Cached for five minutes and refreshed in the background, so this endpoint never waits on it. `billing_configured: true` with `billing_reachable: false` is a revoked key or a provider outage: the state in which checkout answers 502 and nobody can buy anything.'
engine_version:
type: string
description: The version of @attestwire/en16931 this deployment runs — the same string every `provenance` block and GET /v1/versions report, from the same source.
examples:
- 0.14.1
deploy_tag:
type:
- string
- 'null'
description: The git short sha this Worker version was deployed from. `null` on an untagged upload. Lets an operator answer "is production behind main" from the health probe alone.
examples:
- cce4396
metering:
type: string
description: How the document counter is kept. Operational detail, not a contract.
examples:
- durable-object-sqlite
period:
type: string
description: The billing period this request fell in, YYYY-MM. Matches `X-RateLimit-Period` on a metered call, and is the cheapest way to confirm the reset has happened.
examples:
- 2026-08
examples:
up:
summary: This deployment, generating and billing live.
value:
ok: true
runtime: cloudflare-workers
generation: true
billing: true
billing_configured: true
billing_reachable: true
engine_version: 0.14.1
deploy_tag: cce4396
metering: durable-object-sqlite
period: 2026-08
components:
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
upgrade_url:
type: string
format: uri
description: 'Present on `quota_exceeded` only. The page where a human buys a bigger plan. It is a plain link, not a checkout transaction: nothing is bought and no billing state is created by reading it. Absent on every other error code.'
examples:
- https://attestwire.com/pricing#upgrade
xml_code:
type: string
description: 'Present when POST /v1/validate or POST /v1/parse (or /v1/bundle, or /v1/send''s `document`) was sent a document that could not be read and the engine''s reader raised an error. It is that reader''s own stable code, echoed unchanged, and it is finer-grained than `error`: a whole family of well-formedness defects shares `error: "xml_malformed"` and each member carries its own `xml_code`. For a PDF the reader is the PDF one, and the codes start `pdf_` or `facturx_` — the name of the field predates PDFs. Absent when the file was recognised on sight as something else (an HTML page, a ZIP archive), where the reader raised nothing. The list below is a sample, not the full set, and it is deliberately open — the reader gains codes as it learns to refuse more, and a caller that switches exhaustively on this field will break when it does. Branch on `error` first and on this only if you need the detail.'
examples:
- unsupported_syntax
- xml_doctype_forbidden
- xml_entity_forbidden
- xml_too_deep
- xml_too_many_elements
- xml_too_many_attributes
- xml_unbalanced
- xml_mixed_content
- xml_duplicate_attribute
- xml_bad_comment
- facturx_no_xml_attachment
- facturx_xml_encoding
- pdf_no_startxref
- pdf_stream_too_large
Release:
type: object
description: One released version of the rule engine.
required:
- version
- date
- rules_added
properties:
version:
type: string
examples:
- 0.14.1
date:
type: string
format: date
rules_added:
type: array
description: The rule ids this release first named in the changelog. Derived, never typed — see `derivation` on the enclosing document. Empty for a release that named none.
items:
type: string
examples:
- BR-13
Versions:
type: object
description: 'The body of GET /v1/versions: the running engine, the KoSIT record, and one entry per release of the engine.'
required:
- engine
- engine_version
- ruleset
- rule_id_count
- kosit_conformance
- releases
- derivation
properties:
engine:
type: string
examples:
- '@attestwire/en16931'
engine_version:
type: string
examples:
- 0.14.1
ruleset:
type: string
examples:
- en16931@0.14.1
rule_id_count:
type: integer
description: Every rule id the compiled engine checks — which is more than the release notes have ever named, so this will not equal the sum of the `rules_added` arrays below.
kosit_conformance:
$ref: '#/components/schemas/KositConformance'
releases:
type: array
description: Newest first.
items:
$ref: '#/components/schemas/Release'
derivation:
type: string
description: How `rules_added` is derived, published in the body so nobody over-reads the list. Prose; do not parse it.
KositConformance:
type: object
description: 'When the official KoSIT validator was last run against this engine''s committed fixtures, and with what.
**NOTHING IS SENT TO KoSIT WHEN YOU CALL US.** This is a dated record of a check we ran on release, not a per-call verification, and it says nothing about YOUR document.'
required:
- recorded
- validator
- configuration
- configuration_published
properties:
recorded:
type: string
format: date
description: The date of the last recorded full fixture run.
examples:
- '2026-09-24'
validator:
type: string
description: Version of the KoSIT standalone validator used.
examples:
- 1.6.3
configuration:
type: string
description: Version of the XRechnung validator configuration used.
examples:
- 3.0.2
configuration_published:
type: string
format: date
description: The date that configuration was published by KoSIT.
examples:
- '2026-08-31'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'Send your key as `Authorization: Bearer aw_live_...`. The public demo key, `Authorization: Bearer demo`, works on POST /v1/validate, POST /v1/generate and POST /v1/recipient only, with no account: 6 requests a minute and 30 documents a day per client IP, its PDF always watermarked, and every other route answers `403 demo_key_not_allowed`.'
externalDocs:
description: Hand-written docs, the quickstart, and the per-rule pages at attestwire.com/rules/BR-*. This deployment also serves /llms.txt for agents and an MCP endpoint at /mcp, neither of which is describable in OpenAPI.
url: https://api.attestwire.com/docs