sevDesk
A self-hostable worker that validates the e-invoices sevDesk generates before they go out, and converts them to the syntax a counterparty needs.
Available
sevDesk generates e-invoices, but its API returns no validation result. The beliq sevDesk worker adds an independent check: it reads the e-invoices sevDesk produced, validates each one with Beliq, and converts it when a counterparty needs a different syntax. It runs on your own infrastructure, so your sevDesk token never leaves your environment.
On the sevDesk account the worker was tested against in October 2026, sevDesk’s API hands out each e-invoice as UN/CEFACT CII that declares the XRechnung 3.0 specification, so KoSIT’s XRechnung rules apply to it. One case from that account: sevDesk accepted an IBAN that is not a correct IBAN, and Beliq reported it as warning BR-DE-19.
- Package:
beliq-sevdeskon npm - Container:
ghcr.io/beliq-eu/beliq-sevdesk - Source: github.com/beliq-eu/beliq-sevdesk
What it does
Each poll lists the sevDesk invoices in one status (Open by default) whose invoice date lies within the poll window (30 days by default). For every listed invoice it has not processed yet, the worker pulls the invoice XML from sevDesk, sends it to Beliq’s validate endpoint through @beliq/sdk and classifies the invoice as valid, invalid or error (no verdict, or a conversion Beliq refused), then converts it through Beliq’s convert endpoint to every configured target format and writes the files to its output directory. The validate log line carries Beliq’s valid and the check depth the API states for it, or check depth not stated where it states none. An invoice whose result carries franceCtcBlockingRuleIds counts as invalid, also where Beliq’s verdict is valid. An invalid invoice is still sent for conversion, and a converted file is written when Beliq returns one.
The worker keeps the ids of the invoices it has processed in a state file, so no invoice is processed twice. An invoice that fails for a reason that can pass, such as an unreachable API or a spent quota, is tried again on the next poll, and the invoices after it are still processed. A normal sevDesk invoice has no XML: the worker counts it as skipped and does not ask again.
With --once it exits 1 when an invoice is invalid, a France CTC block included, and 3 when an invoice ended as error: an API error, or a document Beliq refused to validate or convert. 3 wins when both occur. Cron and CI can act on the code. It can also POST a report to a webhook.
What to expect
- Which invoices are listed. sevDesk filters on the invoice date, not on when the invoice was created or opened, so an invoice dated before the window is never listed.
SEVDESK_POLL_WINDOW_DAYS=0lists every invoice in the status on every poll. An Open invoice leaves the Open list when a part payment is booked on it. - Target formats. Measured with the tested account’s XML:
xrechnung(UBL declaring XRechnung 3.0),ubl,cii, andzugferdorfacturx. The last two come back as CII XML with that profile’s specification id, not as a PDF: a conversion does not wrap XML into a PDF. peppol-bisdoes not work. Peppol BIS Billing 3.0 needs a Peppol endpoint identifier for the seller and the buyer, sevDesk’s XML carries neither, and Beliq does not make one up. Beliq refuses the conversion withCONVERSION_LOSSY_FAILCLOSED, and the worker reports the invoice aserror.- Converted files are validated by Beliq, not by the worker. The worker validates the document sevDesk produced. Beliq validates each converted XML before it returns it and refuses a conversion whose result fails with
CONVERSION_OUTPUT_INVALID; the worker then reports a valid invoice aserror, and an invalid one staysinvalid. A conversion returns no verdict with the file, so validate a converted file yourself to read its warnings.
Configure
The worker is configured through the environment. The most used settings:
| Variable | Description |
|---|---|
SEVDESK_API_TOKEN |
Your sevDesk API token (Settings → Advanced → API) |
BELIQ_API_KEY |
Your Beliq API key |
SEVDESK_TARGET_FORMATS |
Comma-separated convert targets; empty means validate only |
SEVDESK_POLL_WINDOW_DAYS |
How many days back by invoice date to list; 0 lists every invoice |
SEVDESK_STATE_FILE |
The state file holding the ids of processed invoices |
SEVDESK_NOTIFY_WEBHOOK |
Optional URL for a JSON poll report |
The README lists every setting.
Run
export SEVDESK_API_TOKEN="..."
export BELIQ_API_KEY="blq_..."
docker run --rm \
-e SEVDESK_API_TOKEN -e BELIQ_API_KEY -e SEVDESK_TARGET_FORMATS=xrechnung \
-v "$PWD/out:/app/out" -v "$PWD/state:/app/state" \
ghcr.io/beliq-eu/beliq-sevdesk --once
Mount /app/state, or the state file is lost with the container and every run processes the whole window again, using Beliq quota for each invoice. Drop --once to run as a polling daemon; it stops cleanly on SIGTERM. The repository ships docker-compose, systemd, and GitHub Actions cron examples.
Upgrading from 0.2.x
Version 0.3.0 changed the state file from one number, the highest invoice id processed, to the list of processed ids. The first poll after the upgrade converts the old file; nothing needs to be done by hand. 0.2.x skipped an invoice that was still a draft while a newer one was processed, and the old file cannot tell such an invoice from a processed one, so the upgrade marks it as processed too. To have every invoice in the current window processed again, delete the state file before the first run. Each of those invoices then uses Beliq quota.
Boundary
The worker only validates and converts. It does not re-generate the invoice (sevDesk does that) or send it: transmission over Peppol or e-mail stays with sevDesk and your access point.
See also
- Validate reference: the endpoint the worker checks each invoice with.
- Convert reference: format families and lossy-path handling.