Developers · Quickstart

Your first call, in three steps.

A test key without an account, one POST with a sample invoice, and the protocol that comes back. The key allows 10 requests and expires after one hour.

Run it here
  1. 01

    Get a test key

    now

    No account. 10 requests, valid for one hour. From the shell it is one line:

    curl -X POST https://www.invoicenavigator.eu/api/developers/instant-key
  2. 02

    Send a sample invoice

    next

    One call: validate, patch what is structural, re-validate, and sign an evidence pack. The sample is an XRechnung 3.0 UBL invoice missing several mandatory fields.

    curl
    curl -X POST https://www.invoicenavigator.eu/api/v2/validate-and-fix \
      -H "Authorization: Bearer sk_test_…" \
      -H "Content-Type: application/json" \
      -d "$(jq -n --rawfile xml invoice.xml '{xml: $xml, autoFix: true}')"
  3. 03

    Read the protocol

    next

    The response carries originalValid, fixedValid, fixesApplied, remainingIssues and, when the file changed, fixedXml.

From your code

The same call in JavaScript and Python

Put a permanent key in INVOICE_NAV_API_KEY. Keys are created on sign-up and listed under Dashboard → API; a sk_test_ key is issued next to every sk_live_ key.

JavaScript · Node 18+
import { readFileSync } from 'node:fs'

const res = await fetch('https://www.invoicenavigator.eu/api/v2/validate-and-fix', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.INVOICE_NAV_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    xml: readFileSync('invoice.xml', 'utf8'),
    fileName: 'invoice.xml',
    autoFix: true,
  }),
})

const { success, data, error } = await res.json()
if (!success) throw new Error(`${error.code}: ${error.message}`)

if (data.originalValid) console.log('already valid', data.validationRef)
else if (data.fixedValid) console.log('fixed', data.fixesApplied, 'changes')
else for (const i of data.remainingIssues) console.log(i.code, i.message)
Python · requests
import os, requests

with open("invoice.xml", encoding="utf-8") as f:
    xml = f.read()

r = requests.post(
    "https://www.invoicenavigator.eu/api/v2/validate-and-fix",
    headers={"Authorization": f"Bearer {os.environ['INVOICE_NAV_API_KEY']}"},
    json={"xml": xml, "fileName": "invoice.xml", "autoFix": True},
)
body = r.json()
if not body["success"]:
    raise SystemExit(f"{body['error']['code']}: {body['error']['message']}")

data = body["data"]
if data["originalValid"]:
    print("already valid", data["validationRef"])
elif data.get("fixedValid"):
    print("fixed", data["fixesApplied"], "changes")
else:
    for issue in data["remainingIssues"]:
        print(issue["code"], issue["message"])

Or install the typed client: npm install @invoicenavigator/sdk.

Reading the response

Four fields decide what you do next

originalValid
The file passed as sent. Nothing was changed; deliver it.
fixedValid
The file passes after structural patches. data.fixedXml is the file to deliver; fixesApplied says how many edits were made.
fixSummary.needsInput
Business data is missing (a buyer reference, a Leitweg-ID). POST the values to data._links.fixWithInput. Not offered on test keys.
remainingIssues
Codes that still fail after patching. Each carries code, severity and message; look the code up under /errors.

Every response is wrapped: { success, data, meta } on success, { success: false, error: { code, message } } on failure. Full contract in the API reference; validation only, without patching, is POST /v1/validate (reference).

Next