API

Validate documents from your pipeline. API keys ship with the beta (join here) — the same engine powers the free validator page, no key needed there.

POST /v1/validate — validate one document

curl -X POST https://api.normdrift.com/v1/validate \
  -H "Authorization: Bearer rk_live_…" \
  -F "file=@invoice.xml"

Optional ?vesid= to force a ruleset (e.g. de.xrechnung:ubl-invoice:3.0.2); PDF Factur-X are auto-routed by profile. Max size 20 MB. Every 200 response carries X-RateLimit-Remaining (your monthly quota left).

Response

{
  "verdict": "fail",
  "ruleset": "eu.peppol.bis3:invoice:2026.5",
  "duration_ms": 131,
  "truncated": false,
  "findings": [
    {
      "rule_code": "BR-CO-16",
      "severity": "error",
      "message": "[BR-CO-16]-Amount due for payment (BT-115) = …",
      "location": "/:Invoice[1]/cac:LegalMonetaryTotal[1]"
    }
  ]
}

POST /v1/runs — re-validate your whole corpus

curl -X POST https://api.normdrift.com/v1/runs \
  -H "Authorization: Bearer rk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"project_public_id":"<shown on your project page>"}'

Returns 202 {"run_id":"run_…","docs_total":12}. One run at a time per project (409 run_in_progress); 422 empty_corpus if no documents. The run compares against your baseline and fires your alert channels on regressions.

GET /v1/rulesets — what we track

curl -H "Authorization: Bearer rk_live_…" https://api.normdrift.com/v1/rulesets

Current and upcoming rulesets per format family, with effective_date. Public variant without key: GET /v1/public/releases (adds the latest upstream releases detected).

Status badge

![e-invoicing](https://api.normdrift.com/badge/<project_public_id>.svg)

SVG badge of your last run (pass / fail / running), cached 5 min. The public id is on your project page — it is unguessable, that is its only protection: treat it like a semi-secret.

GitHub Action

name: e-invoicing compliance
on:
  push: { branches: [main] }
  schedule: [{ cron: "0 7 * * 1" }]   # weekly — catches ruleset releases
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Validate sample invoice
        run: |
          curl -sf -X POST https://api.normdrift.com/v1/validate \
            -H "Authorization: Bearer ${{ secrets.NORMDRIFT_KEY }}" \
            -F "file=@samples/invoice.xml" | tee result.json
          test "$(jq -r .verdict result.json)" = "pass"
      - name: Re-validate reference corpus
        run: |
          curl -sf -X POST https://api.normdrift.com/v1/runs \
            -H "Authorization: Bearer ${{ secrets.NORMDRIFT_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{"project_public_id":"'"${{ vars.NORMDRIFT_PROJECT }}"'"}'

Errors

StatusSlugMeaning
400unknown_format · unknown_vesid · file_missingBad request — not detectable as UBL/CII/PDF, or vesid not served
402api_requires_paid_planFree plan has no API quota — upgrade to Pro
413file_too_largeOver 20 MB
429quota_exceededMonthly quota exhausted — resets on the 1st (UTC)
502validator_unavailableEngine outage — not counted against your quota, retry
503{"warming":true}Engine warming up — not counted, retry in a few seconds