Skip to content
einvoicing.dev

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.

Base URLOpenAPI 3.1.0
https://api.einvoicing.dev

Authentication

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 GET exists 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 200 with valid: 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 a 422.
  • Money is a decimal string, never a number. "1000.00", not 1000.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 200 with registered: false, not 404.
  • 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:

  1. POST /v1/sign-in-challenges for an ALTCHA proof-of-work challenge, and solve it. It takes well under a second with any ALTCHA solver.
  2. POST /v1/sign-ins with an email address and the solved challenge.
  3. POST /v1/sign-ins/{sign_in_id}/confirmation with 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 429 with Retry-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.