Skip to content

Validate Invoice Data

Pre-validate your invoice JSON before generating a PDF. This endpoint checks for errors without consuming quota or generating a PDF.

Endpoint

POST https://api.thelawin.dev/v1/validate

Purpose

Use this endpoint to:

  • Check for validation errors before calling /v1/generate
  • Detect the format that will be used (with format: "auto")
  • Get legal warnings for country-specific requirements
  • Avoid wasting quota on invalid invoices

Free Pre-Validation

This endpoint does NOT generate a PDF and does NOT count against your quota. Use it liberally!

Headers

HeaderRequiredDescription
Content-TypeYesapplication/json

No API Key Required

Pre-validation is free and does not require an API key.

Request Body

The request body is identical to /v1/generate:

json
{
  "format": "auto",
  "profile": "en16931",
  "template": "minimal",
  "invoice": {
    "number": "2026-001",
    "date": "2026-01-15",
    "seller": {
      "name": "Acme GmbH",
      "vat_id": "DE123456789",
      "city": "Berlin",
      "country": "DE"
    },
    "buyer": {
      "name": "Customer AG",
      "city": "München",
      "country": "DE"
    },
    "items": [{
      "description": "Consulting",
      "quantity": 8,
      "unit": "HUR",
      "unit_price": 150,
      "vat_rate": 19
    }]
  }
}

See Generate API Parameters for all available fields.

Response

Valid (200)

json
{
  "valid": true,
  "format": {
    "format_used": "zugferd",
    "profile": "EN16931",
    "version": "2.3",
    "format_reason": "Auto-detected based on seller.country=DE",
    "warnings": [
      {
        "code": "DE_B2B_XRECHNUNG_ALTERNATIVE",
        "message": "XRechnung format is available for German B2B/B2G transactions",
        "legal_basis": "§ 27 Abs. 38 UStG, E-Rechnungs-Verordnung",
        "severity": "info"
      }
    ]
  },
  "errors": []
}

Invalid (200)

json
{
  "valid": false,
  "format": {
    "format_used": "zugferd",
    "profile": "EN16931"
  },
  "errors": [
    "invoice.seller.vat_id: VAT ID required for ZUGFeRD EN16931 profile",
    "invoice.buyer.country: Invalid country code 'XX'",
    "invoice.items[0].unit_price: Unit price must be positive"
  ]
}

Response Fields

FieldTypeDescription
validbooleanWhether the invoice data is valid
formatobjectFormat detection result
format.format_usedstringDetected format (zugferd, xrechnung, etc.)
format.profilestringProfile name (e.g., EN16931)
format.versionstringFormat version (e.g., 2.3)
format.format_reasonstringWhy this format was chosen (for auto mode)
format.warningsarrayLegal warnings for country-specific requirements
errorsarrayValidation error messages (empty if valid)

Validation Rules

The validator checks for:

EN 16931 Requirements

  • Invoice number (unique identifier)
  • Invoice date (YYYY-MM-DD format)
  • Seller name and VAT ID
  • Buyer name and country
  • At least one line item
  • Valid unit codes (UN/ECE Rec 20)
  • Valid country codes (ISO 3166-1 alpha-2)

Format-Specific Requirements

ZUGFeRD/Factur-X:

  • seller.vat_id required for EN16931 profile

XRechnung:

  • leitweg_id required (format: <Behördenkennung>-<Empfänger>-<Leitweg>)
  • buyer_reference recommended

Peppol:

  • seller.peppol_id and buyer.peppol_id required
  • buyer_reference mandatory
  • Peppol ID format: <EAS code>:<identifier>

FatturaPA:

  • tipo_documento required (TD01, TD04, etc.)
  • seller.codice_fiscale and buyer.codice_fiscale required
  • buyer.codice_destinatario OR buyer.pec required

See Invoice Formats for complete requirements.

The API returns legal warnings for country-specific regulations:

Examples

IT_SDI_REQUIRED

json
{
  "code": "IT_SDI_REQUIRED",
  "message": "Italian invoices must be submitted via Sistema di Interscambio (SDI)",
  "legal_basis": "Art. 1, comma 3, D.Lgs. 127/2015",
  "severity": "warning"
}

DE_B2G_XRECHNUNG_MANDATORY

json
{
  "code": "DE_B2G_XRECHNUNG_MANDATORY",
  "message": "XRechnung format is mandatory for German B2G invoices since 2020-11-27",
  "legal_basis": "E-Rechnungs-Verordnung (ERechV), § 27 Abs. 38 UStG",
  "severity": "warning"
}

PEPPOL_NETWORK_REGISTRATION

json
{
  "code": "PEPPOL_NETWORK_REGISTRATION",
  "message": "Peppol invoices require sender/receiver registration in Peppol network",
  "legal_basis": "Peppol Transport Infrastructure Specifications",
  "severity": "info"
}

Usage Examples

curl

bash
curl -X POST https://api.thelawin.dev/v1/validate \
  -H "Content-Type: application/json" \
  -d '{
    "format": "auto",
    "invoice": {
      "number": "2026-001",
      "date": "2026-01-15",
      "seller": {
        "name": "Acme GmbH",
        "city": "Berlin",
        "country": "DE"
      },
      "buyer": {
        "name": "Customer AG",
        "city": "München",
        "country": "DE"
      },
      "items": [{
        "description": "Consulting",
        "quantity": 8,
        "unit": "HUR",
        "unit_price": 150
      }]
    }
  }'

JavaScript

javascript
async function validateInvoice(invoiceData) {
  const response = await fetch('https://api.thelawin.dev/v1/validate', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      format: 'auto',
      invoice: invoiceData
    })
  });

  const result = await response.json();

  if (result.valid) {
    console.log('✓ Valid invoice');
    console.log('Format:', result.format.format_used);
    if (result.format.warnings.length > 0) {
      console.warn('Warnings:', result.format.warnings);
    }
  } else {
    console.error('✗ Validation failed');
    console.error('Errors:', result.errors);
  }

  return result;
}

// Usage
const result = await validateInvoice({
  number: '2026-001',
  date: '2026-01-15',
  seller: { name: 'Acme GmbH', city: 'Berlin', country: 'DE' },
  buyer: { name: 'Customer AG', city: 'München', country: 'DE' },
  items: [{
    description: 'Consulting',
    quantity: 8,
    unit: 'HUR',
    unit_price: 150
  }]
});

Python

python
import requests

def validate_invoice(invoice_data):
    response = requests.post(
        'https://api.thelawin.dev/v1/validate',
        json={
            'format': 'auto',
            'invoice': invoice_data
        }
    )
    result = response.json()

    if result['valid']:
        print(f"✓ Valid invoice")
        print(f"Format: {result['format']['format_used']}")
        if result['format']['warnings']:
            print(f"Warnings: {result['format']['warnings']}")
    else:
        print(f"✗ Validation failed")
        for error in result['errors']:
            print(f"  - {error}")

    return result

# Usage
result = validate_invoice({
    'number': '2026-001',
    'date': '2026-01-15',
    'seller': {'name': 'Acme GmbH', 'city': 'Berlin', 'country': 'DE'},
    'buyer': {'name': 'Customer AG', 'city': 'München', 'country': 'DE'},
    'items': [{
        'description': 'Consulting',
        'quantity': 8,
        'unit': 'HUR',
        'unit_price': 150
    }]
})

Workflow

Recommended workflow for invoice generation:

mermaid
graph LR
    A[Build Invoice JSON] --> B[POST /v1/validate]
    B --> C{Valid?}
    C -->|Yes| D[POST /v1/generate]
    C -->|No| E[Fix Errors]
    E --> B
    D --> F[Save PDF]
  1. Build your invoice JSON
  2. Validate with /v1/validate
  3. Check for errors
  4. Fix any validation errors
  5. Generate with /v1/generate
  6. Save the resulting PDF

Benefits

BenefitDescription
FreeNo quota consumption, no API key required
FastNo PDF generation, instant response
Format DetectionKnow which format will be used
Legal WarningsGet country-specific compliance info
Error PreventionFix errors before wasting quota

Common Errors

Missing VAT ID

json
{
  "valid": false,
  "errors": [
    "invoice.seller.vat_id: VAT ID required for ZUGFeRD EN16931 profile"
  ]
}

Solution: Add seller.vat_id with format CC123456789 (e.g., DE123456789)

Invalid Country Code

json
{
  "valid": false,
  "errors": [
    "invoice.buyer.country: Invalid country code 'XX'"
  ]
}

Solution: Use ISO 3166-1 alpha-2 codes (e.g., DE, FR, IT)

Invalid Unit Code

json
{
  "valid": false,
  "errors": [
    "invoice.items[0].unit: Invalid unit code 'HOUR'"
  ]
}

Solution: Use UN/ECE unit codes (e.g., HUR for hours, DAY for days)

Further Reading

ZUGFeRD 2.3 & Factur-X 1.0 compliant