Referencev0.3.1
API reference
Every operation, rendered from the contract. Point a client generator or an agent at openapi.yaml and you have the same thing in OpenAPI 3.1.
https://api.einvoicing.devAuthentication
An API key, presented as Authorization: Bearer <key>. Get the first
one with a sign-in. See "Getting a key".
The format is einv_live_ or einv_test_, then an 8-character
public id, then _, then 32 random characters and a 6-character
CRC32 checksum:
^einv_(live|test)_[a-z0-9]{8}_[A-Za-z0-9]{38}$. The prefix makes a
leaked key recognisable on sight. The checksum lets secret scanners
confirm a match without asking us. einv_live_ plus the public id
is the key's prefix, the only part ever shown again.
Storage. The secret is shown exactly once, when the key is created. It is kept only as an HMAC, so a copy of the database is not enough to use or confirm a key. Keys can be revoked one at a time, can carry an expiry, and an account can hold several.
Test keys (einv_test_) call every product operation with real
results but are never metered, which suits CI. They cannot manage
keys or billing.
Keys are not scoped in this version. Every product operation is a read of public network data or a stateless transformation of the caller's own document, so there is nothing to partition yet.
Operations
Validation
Check a Peppol document against the official rules.
Conversion
Turn a JSON invoice into a valid Peppol UBL document.
Participants
Find out whether a business can receive Peppol invoices.
Rulesets
The rule versions this API validates against.
Sign-in
Get an account and a key with a proof-of-work challenge and an emailed one-time code. No password, no UI.
Keys
Create, list and revoke API keys.
Account
The account, its plan and its usage.
Billing
Links to Stripe-hosted pages for starting or managing a subscription.
How this API behaves
einvoicing.dev is a headless API for developers building invoicing into software ahead of the UK's 2029 e-invoicing mandate, and for the Peppol markets that already require it. It does three things:
- Validates a Peppol BIS Billing 3.0 document against the official rules and explains every finding in plain English, with the fix.
- Converts a clean JSON invoice into a valid Peppol BIS Billing 3.0 UBL document, computing totals and the VAT breakdown for you.
- Looks up whether a business can receive Peppol invoices, and which document types it accepts.
It is not a Peppol Access Point and does not send or receive documents over the network. It sits in front of whichever Access Point you use.
Peppol is a trademark of OpenPeppol AISBL. einvoicing.dev is independent and is not affiliated with or endorsed by OpenPeppol.
Decisions this contract makes
- Nothing you send is stored. Documents and invoices are processed
in memory and discarded when the response is written. Only counts are
kept, for metering. That is why no
GETexists for a validation or a conversion: there is nothing to fetch, and the contract says so by having no route rather than by a promise in a privacy policy. - An invalid invoice is an answer, not an error. Validating a
document that breaks the rules returns
200withvalid: false. The request succeeded; the invoice did not. Error statuses are reserved for requests that could not be answered. - 400 means the body could not be parsed; 422 means it parsed and was
wrong. Malformed XML or JSON is a
400. A well-formed body that is not something this API accepts is a422. - Money is a decimal string, never a number.
"1000.00", not1000.0. JSON numbers are binary floating point in most parsers, and an invoice is the last place to find that out. - Totals and the VAT breakdown are computed, never supplied. Most
rejected invoices fail on arithmetic (
BR-CO-*). Deriving the totals from the lines removes that whole class of failure. - Absence from the network is an answer too. Looking up a business
that is not registered on Peppol returns
200withregistered: false, not404. - A conversion never returns an invalid document. If the invoice
cannot produce one, the answer is
422, with every problem mapped to the JSON field that caused it and the rule it broke.
Getting a key
There is no dashboard. An account and its first key come from a one-time code sent by email:
POST /v1/sign-in-challengesfor an ALTCHA proof-of-work challenge, and solve it. It takes well under a second with any ALTCHA solver.POST /v1/sign-inswith an email address and the solved challenge.POST /v1/sign-ins/{sign_in_id}/confirmationwith the code from the email. The response carries the new key.
The CLI's einvoicing login does all three. The same two calls add a key
to an existing account, which is also how access is recovered if
every key is lost. Whenever a key is added to an existing account, the
account's email address is told.
There are no passwords anywhere in this API. Keys are managed with
/v1/keys, and plans are changed through Stripe-hosted pages that
/v1/billing/* hands you a link to.
Errors
Every error response is application/problem+json per RFC 9457. Branch
on type, which is stable; never on title or detail, which are
prose and may change.
type (under https://www.einvoicing.dev/problems/) |
Status |
|---|---|
malformed-body |
400 |
unauthenticated |
401 |
allowance-exhausted |
402 |
live-key-required |
403 |
not-found |
404 |
method-not-allowed |
405 |
already-subscribed |
409 |
no-subscription |
409 |
sign-in-expired |
410 |
payload-too-large |
413 |
unsupported-media-type |
415 |
unsupported-document |
422 |
unknown-ruleset |
422 |
invalid-participant-id |
422 |
invalid-invoice |
422 |
invalid-code |
422 |
invalid-challenge |
422 |
invalid-request |
422 |
rate-limited |
429 |
internal-error |
500 |
upstream-unavailable |
502 |
sign-ins-unavailable |
503 |
validator-unavailable |
503 |
request-failed |
the status of any other refusal |
Retries
POST /v1/validations and POST /v1/conversions are pure functions of
their body. They change nothing on the server, so retrying them is
always safe and neither takes an Idempotency-Key. A retry is metered
as a new request.
Operations that mint a key cannot be replayed, by design. Replaying
one would mean storing the secret so it could be handed back, and a key
the server can show you twice is a key a database dump can show someone
else. If a response carrying a secret is lost, the key still appears in
GET /v1/keys, without its secret. Revoke it and create another.
Retrying a billing session request returns a fresh link, which does no harm.
Limits
429 means slow down. 402 means the Free plan's monthly allowance is
used up. Paid plans are metered past their allowance and never see
402: refusing a customer's invoices because a counter ticked over is
the wrong failure for a compliance product.
Test keys are never metered and never see 402. They are rate-limited
more tightly, and they cannot manage keys or billing (403).
Sign-in has limits of its own, because it is the one door that needs no key:
- Every sign-in spends a solved proof-of-work challenge. A challenge works once and expires after five minutes. Difficulty rises automatically while sign-in volume is unusually high.
- One code per address per minute. Asking again sooner answers
429withRetry-After. - One open sign-in per address. Requesting a new code cancels the
previous one, which then answers
410. - A global cap on sign-in emails per hour. At the cap, new sign-ins
answer
503(sign-ins-unavailable) until volume falls. Existing keys are unaffected.