> ## Documentation Index
> Fetch the complete documentation index at: https://docs.e-invoice.be/llms.txt
> Use this file to discover all available pages before exploring further.

# Validation During Development

> Test your invoice JSON before creating documents

## Overview

The `/api/validate/json` endpoint is essential for development. It validates your invoice JSON and ensures it can be converted to valid UBL BIS Billing 3.0 format **before** you create any documents.

<Warning>
  You cannot create documents with invalid JSON. The API will reject invoices that don't meet UBL BIS Billing 3.0 standards. Always validate during development to catch errors early.
</Warning>

## Why Validate?

* **No document creation**: Validation doesn't create any records - it's risk-free testing
* **Fast feedback**: Get instant validation results without creating documents
* **Detailed errors**: Receive specific field-level error messages
* **UBL compliance**: Ensures your JSON converts to valid UBL BIS Billing 3.0 XML
* **Save API calls**: Fix errors before attempting to create documents

## Basic Validation

Use `POST /api/validate/json` with your invoice data:

```bash theme={null}
curl -X POST "https://api.e-invoice.be/api/validate/json" \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
  "document_type": "INVOICE",
  "invoice_id": "INV-2024-001",
  "invoice_date": "2024-10-24",
  "due_date": "2024-11-23",
  "currency": "EUR",
  "vendor_name": "Your Company BVBA",
  "vendor_tax_id": "BE0123456789",
  "vendor_address": "Main Street 123, 1000 Brussels, Belgium",
  "customer_name": "Customer Company NV",
  "customer_tax_id": "BE0987654321",
  "customer_address": "Customer Lane 456, 2000 Antwerp, Belgium",
  "items": [
    {
      "description": "Professional Services - Consulting services for October 2024",
      "quantity": "10",
      "unit_price": "100.00",
      "unit_code": "C62",
      "tax_rate": "21.00"
    }
  ]
}'
```

<Warning>
  The example above will fail validation! This is intentional - it demonstrates why validation is critical. See below for the errors and how to fix them.
</Warning>

## Response Types

### Error Response (Real Example)

When validation fails, you'll receive detailed UBL compliance errors. Here's what the example above returns:

```json theme={null}
{
  "id": "b55354b0-5c69-489b-a8f7-44be7d5bdd6b",
  "file_name": "b55354b0-5c69-489b-a8f7-44be7d5bdd6b.xml",
  "is_valid": false,
  "issues": [
    {
      "message": "Belgian enterprise number MUST be stated in the correct format.",
      "type": "error",
      "rule_id": "PEPPOL-COMMON-R043",
      "flag": "fatal"
    },
    {
      "message": "[BR-S-08]-For each different value of VAT category rate (BT-119) where the VAT category code (BT-118) is \"Standard rated\", the VAT category taxable amount (BT-116) in a VAT breakdown (BG-23) shall equal the sum of Invoice line net amounts (BT-131)...",
      "type": "error",
      "rule_id": "BR-S-08",
      "flag": "fatal"
    },
    {
      "message": "Invoice line net amount MUST equal (Invoiced quantity * (Item net price/item price base quantity) + Sum of invoice line charge amount - sum of invoice line allowance amount",
      "type": "error",
      "rule_id": "PEPPOL-EN16931-R120",
      "flag": "fatal"
    }
  ]
}
```

**This is exactly why the validation endpoint exists!** These UBL BIS Billing 3.0 compliance errors would prevent your invoice from being sent. Let's fix them.

### Fixed Example

Here's a corrected version that passes validation:

```bash theme={null}
curl -X POST "https://api.e-invoice.be/api/validate/json" \
     -H "Authorization: Bearer YOUR_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{
  "document_type": "INVOICE",
  "invoice_id": "INV-2024-001",
  "invoice_date": "2024-10-24",
  "due_date": "2024-11-23",
  "currency": "EUR",
  "vendor_name": "Your Company BVBA",
  "vendor_tax_id": "BE0897290877",
  "vendor_address": "Main Street 123, 1000 Brussels, Belgium",
  "customer_name": "Customer Company NV",
  "customer_tax_id": "BE0817331995",
  "customer_address": "Customer Lane 456, 2000 Antwerp, Belgium",
  "items": [
    {
      "description": "Professional Services - Consulting services for October 2024",
      "quantity": "10",
      "unit_price": "100.00",
      "unit_code": "C62",
      "tax_rate": "21.00",
      "amount": "1000.00"
    }
  ]
}'
```

**What changed:**

1. **Valid Belgian VAT numbers** with correct mod97 checksums:
   * `BE0897290877` (vendor) instead of `BE0123456789`
   * `BE0817331995` (customer) instead of `BE0987654321`
2. **Added `amount` field** to line items: `"1000.00"` (quantity × unit\_price)

### Success Response

When your JSON is valid, you'll receive confirmation along with the generated UBL XML:

```json theme={null}
{
  "id": "9eec0b03-4649-4ae4-9c4c-323ebb6d53e8",
  "file_name": "9eec0b03-4649-4ae4-9c4c-323ebb6d53e8.xml",
  "is_valid": true,
  "issues": [],
  "ubl_document": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Invoice xmlns=\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2\"..."
}
```

**Response fields:**

* `id`: Validation session identifier
* `file_name`: Generated XML filename
* `is_valid`: `true` when validation passes
* `issues`: Empty array when no errors
* `ubl_document`: The generated UBL BIS Billing 3.0 XML (truncated above for readability)

**Success means:**

* Your JSON is valid and UBL-compliant
* You can see the exact UBL XML that will be generated
* You're ready to create the document using `POST /api/documents/` with the same JSON payload

<Tip>
  The `ubl_document` field shows you exactly what XML will be sent via Peppol. This is useful for debugging or understanding how your JSON maps to UBL BIS Billing 3.0.
</Tip>

## Validating UBL XML

Use `POST /api/validate/ubl` to validate an existing UBL BIS Billing 3.0 XML file.

<Warning>
  **This endpoint expects `multipart/form-data` with a single `file` field — NOT a raw XML body.** The most common integration mistake is sending the XML as the request body with `Content-Type: application/xml`, which returns a 422 with `{"detail":[{"type":"missing","loc":["body","file"],"msg":"Field required"}]}`. See [common mistake](#common-mistake-raw-xml-body) below.
</Warning>

### Request Contract

|                   |                                                                                      |
| ----------------- | ------------------------------------------------------------------------------------ |
| **Method**        | `POST`                                                                               |
| **URL**           | `https://api.e-invoice.be/api/validate/ubl`                                          |
| **Authorization** | `Bearer YOUR_API_KEY`                                                                |
| **Content-Type**  | `multipart/form-data` (set automatically by your HTTP client when you attach a file) |
| **Body**          | Form field `file` containing the UBL XML file (binary upload)                        |

### Copy-Paste Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.e-invoice.be/api/validate/ubl" \
       -H "Authorization: Bearer YOUR_API_KEY" \
       -F "file=@invoice.xml"
  ```

  ```javascript Node.js theme={null}
  const fs = require('fs');
  const FormData = require('form-data');
  const axios = require('axios');

  const form = new FormData();
  form.append('file', fs.createReadStream('./invoice.xml'));

  const response = await axios.post(
    'https://api.e-invoice.be/api/validate/ubl',
    form,
    {
      headers: {
        ...form.getHeaders(),
        Authorization: `Bearer ${process.env.E_INVOICE_API_KEY}`,
      },
    }
  );

  console.log(response.data);
  ```

  ```python Python theme={null}
  import os
  import requests

  with open('invoice.xml', 'rb') as f:
      response = requests.post(
          'https://api.e-invoice.be/api/validate/ubl',
          headers={'Authorization': f"Bearer {os.environ['E_INVOICE_API_KEY']}"},
          files={'file': ('invoice.xml', f, 'application/xml')},
      )

  print(response.json())
  ```

  ```csharp C# / .NET theme={null}
  using System.Net.Http;
  using System.Net.Http.Headers;

  using var http = new HttpClient();
  http.DefaultRequestHeaders.Authorization =
      new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("E_INVOICE_API_KEY"));

  using var form = new MultipartFormDataContent();
  var fileBytes = await File.ReadAllBytesAsync("invoice.xml");
  var fileContent = new ByteArrayContent(fileBytes);
  fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
  // IMPORTANT: the form field MUST be named "file"
  form.Add(fileContent, "file", "invoice.xml");

  var response = await http.PostAsync(
      "https://api.e-invoice.be/api/validate/ubl",
      form
  );

  Console.WriteLine(await response.Content.ReadAsStringAsync());
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init('https://api.e-invoice.be/api/validate/ubl');
  curl_setopt_array($ch, [
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_HTTPHEADER => [
          'Authorization: Bearer ' . getenv('E_INVOICE_API_KEY'),
      ],
      CURLOPT_POSTFIELDS => [
          'file' => new CURLFile('invoice.xml', 'application/xml', 'invoice.xml'),
      ],
  ]);

  echo curl_exec($ch);
  curl_close($ch);
  ```
</CodeGroup>

### Response

**Success (valid UBL):**

```json theme={null}
{
  "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
  "file_name": "invoice.xml",
  "is_valid": true,
  "issues": []
}
```

**Validation errors:**

```json theme={null}
{
  "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
  "file_name": "invoice.xml",
  "is_valid": false,
  "issues": [
    {
      "message": "Invoice total amount mismatch",
      "type": "error",
      "rule_id": "BR-CO-15",
      "flag": "fatal",
      "schematron": "PEPPOL-EN16931"
    }
  ]
}
```

### Common mistake: raw XML body

If you set `Content-Type: application/xml` and send the XML as the raw request body (e.g. `--data-binary @invoice.xml`), you'll get this 422 response:

```json theme={null}
{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "file"],
      "msg": "Field required",
      "input": null
    }
  ]
}
```

The fix: switch to `multipart/form-data` with a `file` form field (as shown in the examples above).

| Wrong                                                                       | Right                                                                     |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `-H "Content-Type: application/xml" --data-binary @invoice.xml`             | `-F "file=@invoice.xml"`                                                  |
| `requests.post(url, data=xml, headers={'Content-Type': 'application/xml'})` | `requests.post(url, files={'file': open('invoice.xml', 'rb')})`           |
| `axios.post(url, xml, { headers: { 'Content-Type': 'application/xml' } })`  | `axios.post(url, formData, { headers: formData.getHeaders() })`           |
| `new StringContent(xml, ..., "application/xml")`                            | `new MultipartFormDataContent { { fileContent, "file", "invoice.xml" } }` |

### When to Use UBL Validation

Use `POST /api/validate/ubl` when you:

* **Have existing UBL XML files** to verify before sending (e.g. generated by your ERP)
* **Are migrating** from another Peppol Access Point with pre-generated UBL documents
* **Need to validate** UBL files from external sources before posting them to `/api/documents/ubl`
* **Want to double-check** the XML produced by `/api/validate/json` (the `ubl_document` field in that response)

<Tip>
  If you're creating invoices from JSON, prefer `/api/validate/json` — it validates the same UBL rules and returns the generated UBL alongside, so you don't need a separate UBL validation call.
</Tip>

## Common Validation Errors

### Invalid Peppol ID Format

```json theme={null}
{
  "field": "vendor_tax_id",
  "message": "Invalid Peppol ID format. Expected: scheme:identifier"
}
```

**Fix**: Use the correct format `scheme:identifier`

* Belgian companies: `0208:0123456789` (CBE number - VAT without 'BE' prefix)
* See [Peppol schemes](https://docs.peppol.eu/edelivery/codelists/v9.4/Peppol%20Code%20Lists%20-%20Participant%20identifier%20schemes%20v9.4.html)

### Invalid Tax Rate

```json theme={null}
{
  "field": "items[0].tax_rate",
  "message": "Invalid tax rate format. Must be a percentage string"
}
```

**Fix**: Use string format for tax rates:

* Standard rate: `"21.00"`
* Reduced rate: `"6.00"`
* Zero rated: `"0.00"`

### Missing Required Fields

```json theme={null}
{
  "field": "vendor_address",
  "message": "Field is required for UBL compliance"
}
```

**Fix**: Ensure all required fields are present:

* Vendor/customer name, tax ID, and address
* Invoice ID, invoice date, currency
* At least one item with description, quantity, unit price, and tax rate

### Invalid Date Format

```json theme={null}
{
  "field": "invoice_date",
  "message": "Invalid date format. Use ISO 8601: YYYY-MM-DD"
}
```

**Fix**: Use the format `YYYY-MM-DD`, e.g., `2024-10-24`

### Invalid Currency Code

```json theme={null}
{
  "field": "currency",
  "message": "Unsupported currency code"
}
```

**Fix**: Use supported ISO 4217 codes: EUR, USD, GBP, JPY, CHF, CAD, AUD, NZD, CNY, INR, SEK, NOK, DKK, SGD, HKD

## Development Workflow

### 1. Build Your Invoice JSON

Start with a template or build your invoice object:

```javascript theme={null}
const invoice = {
  document_type: 'INVOICE',
  invoice_id: 'INV-2024-001',
  invoice_date: '2024-10-24',
  currency: 'EUR',
  // ... rest of invoice data
};
```

### 2. Validate First

Always validate before attempting to create:

```javascript theme={null}
const validation = await api.post('/api/validate/json', invoice);

if (!validation.data.is_valid) {
  console.error('Validation errors:', validation.data.issues);
  // Fix errors and try again
  return;
}
```

### 3. Create Document

Only after validation passes, create the document:

```javascript theme={null}
const document = await api.post('/api/documents/', invoice);
```

## Complete Development Example

```javascript theme={null}
const axios = require('axios');

const api = axios.create({
  baseURL: 'https://api.e-invoice.be',
  headers: {
    'Authorization': `Bearer ${process.env.E_INVOICE_API_KEY}`,
    'Content-Type': 'application/json'
  }
});

async function validateAndCreateInvoice(invoiceData) {
  try {
    // Step 1: Validate the JSON
    console.log('Validating invoice JSON...');
    const validation = await api.post('/api/validate/json', invoiceData);

    if (!validation.data.is_valid) {
      console.error('❌ Validation failed:');
      validation.data.issues.forEach(issue => {
        console.error(`  - ${issue.rule_id}: ${issue.message}`);
      });
      return null;
    }

    console.log('✓ Invoice JSON is valid');

    // Step 2: Create the document
    console.log('Creating invoice...');
    const document = await api.post('/api/documents/', invoiceData);
    console.log('✓ Invoice created:', document.data.id);

    return document.data;

  } catch (error) {
    console.error('Error:', error.response?.data || error.message);
    return null;
  }
}

// Example usage - using valid data
const invoiceData = {
  document_type: 'INVOICE',
  invoice_id: 'INV-2024-001',
  invoice_date: '2024-10-24',
  due_date: '2024-11-23',
  currency: 'EUR',
  vendor_name: 'Your Company BVBA',
  vendor_tax_id: 'BE0897290877',  // Valid Belgian VAT with mod97 checksum
  vendor_address: 'Main Street 123, 1000 Brussels, Belgium',
  customer_name: 'Customer Company NV',
  customer_tax_id: 'BE0817331995',  // Valid Belgian VAT with mod97 checksum
  customer_address: 'Customer Lane 456, 2000 Antwerp, Belgium',
  items: [
    {
      description: 'Professional Services - Consulting services for October 2024',
      quantity: '10',
      unit_price: '100.00',
      unit_code: 'C62',
      tax_rate: '21.00',
      amount: '1000.00'  // Required: quantity × unit_price
    }
  ]
};

validateAndCreateInvoice(invoiceData);
```

## Testing Strategy

### During Development

Use `/api/validate/json` liberally:

1. **Test edge cases**: Validate unusual scenarios (zero amounts, multiple currencies, etc.)
2. **Test all document types**: Validate invoices, credit notes, and debit notes
3. **Iterate quickly**: Fix errors and re-validate without creating documents
4. **Build test suites**: Create automated validation tests

### Before Production

1. Validate representative samples of all invoice types
2. Test with real customer Peppol IDs
3. Verify all tax categories and currency codes you'll use
4. Test complex scenarios (allowances, charges, multiple line items)

## Best Practices

<AccordionGroup>
  <Accordion title="Validate Early and Often">
    Don't wait until production. Validate during development to catch issues early:

    * Test each new invoice template
    * Validate after schema changes
    * Include validation in CI/CD pipelines
  </Accordion>

  <Accordion title="Handle Validation Errors Gracefully">
    ```javascript theme={null}
    if (!validation.data.is_valid) {
      // Log detailed errors
      logger.error('Invoice validation failed', {
        invoice_id: invoiceData.invoice_id,
        issues: validation.data.issues
      });

      // Notify developers/users
      notifyError(validation.data.issues);

      // Don't attempt to create the document
      return;
    }
    ```
  </Accordion>

  <Accordion title="Cache Validation Results">
    If you're generating invoices from templates, validate the template once and cache the result:

    ```javascript theme={null}
    const templateCache = new Map();

    function validateTemplate(templateName, templateData) {
      if (templateCache.has(templateName)) {
        return templateCache.get(templateName);
      }

      const isValid = validateInvoiceJSON(templateData);
      templateCache.set(templateName, isValid);
      return isValid;
    }
    ```
  </Accordion>

  <Accordion title="Use a Sandbox Company">
    Test validation extensively with a sandbox company before going live:

    * A sandbox company sends via email instead of Peppol, so testing is safe
    * Use the production API: `https://api.e-invoice.be`
    * Create a sandbox company from [app.e-invoice.be](https://app.e-invoice.be)
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Create Invoices" icon="file-invoice" href="/guides/creating-invoices">
    Create and send validated invoices
  </Card>

  <Card title="Lookup Participants" icon="magnifying-glass" href="/guides/lookup-participants">
    Find valid Peppol IDs
  </Card>

  <Card title="Set Up Webhooks" icon="webhook" href="/essentials/webhooks">
    Get notified about events
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference">
    Explore all endpoints
  </Card>
</CardGroup>
