Skip to main content

Overview

Before sending an e-invoice via Peppol, you need to know your customer’s Peppol ID and verify they can receive invoices. The e-invoice.be API provides two distinct lookup methods, each with different data sources and use cases:
  1. /api/lookup - Direct SMP lookup (real-time, always accurate)
  2. /api/lookup/participants - Peppol Directory search (broader search, may be incomplete)
Understanding the difference between these endpoints is crucial for reliable participant verification.

What is a Peppol ID?

A Peppol ID is a unique identifier for organizations registered on the Peppol network. It consists of:
  • Scheme: The identifier scheme (e.g., 0208 for Belgian companies)
  • Identifier: The actual ID
Format: scheme:identifier0208:0123456789
Belgian Peppol IDs use the CBE number (Crossroads Bank for Enterprises number), which is equivalent to the Belgian VAT number without the ‘BE’ prefix. For example, if the VAT number is BE0123456789, the Peppol ID is 0208:0123456789 (numbers only).
Organizations must be registered with a Peppol Access Point to receive e-invoices. If a customer is not registered, they cannot receive invoices via Peppol.

/api/lookup - Direct SMP Lookup

What It Does

Performs an exact, real-time lookup directly against the Service Metadata Publisher (SMP) using a specific identifier. This queries the authoritative source for participant registration information.

Key Characteristics

  • Always accurate: Queries the SMP in real-time (a-la-minute)
  • Exact match required: Requires precise Peppol ID
  • Authoritative data: Returns the current registration status directly from the SMP
  • 100% reliable: Always shows registered participants

When to Use

  • Before sending invoices: Verify a recipient can receive documents
  • Exact identifier known: You have the CBE number or Peppol ID
  • Need certainty: Must confirm current registration status
  • Production validation: Pre-flight checks before document transmission

Request

The endpoint requires a Peppol ID in the format <scheme>:<id>. For Belgian companies, use scheme 0208 followed by the 10-digit CBE/BTW number.
curl -X GET "https://api.e-invoice.be/api/lookup?peppol_id=0208:1018265814" \
     -H "Authorization: Bearer YOUR_API_KEY"

Response

Registered participant:
{
  "peppol_id": "0208:1018265814",
  "name": "E-INVOICE BV",
  "country_code": "BE",
  "identifiers": [
    {
      "scheme": "0208",
      "value": "1018265814"
    }
  ],
  "registered": true
}
Not registered:
{
  "detail": "Participant not found"
}

What It Does

Searches the official Peppol Directory database, which contains participant information that access points have voluntarily published. This is a proxy for the public Peppol Directory.

Key Characteristics

  • Search functionality: Find participants by name or partial identifier
  • Directory-based: Only shows participants whose access points publish to the directory
  • May be incomplete: Not all registered participants appear in the directory
  • Discovery tool: Useful for finding participants when exact ID is unknown

Important Limitation

Not all registered Peppol participants appear in the Directory. Publishing to the Peppol Directory is optional, not mandatory. An access point may choose not to synchronize participant data with the directory, even though those participants are fully registered and can receive invoices.

When to Use

  • Discovery: Search for participants by company name
  • Browsing: Explore registered participants in a country
  • Fuzzy search: Find participants without knowing exact identifiers
  • Autocomplete features: Suggest participants as users type

When NOT to Use

  • Validation before sending: Use /api/lookup instead for accurate verification
  • Confirming registration: Directory absence doesn’t mean unregistered
  • Production checks: Not reliable for pre-send validation

Request

curl -X GET "https://api.e-invoice.be/api/lookup/participants?query=E-INVOICE&country_code=BE" \
     -H "Authorization: Bearer YOUR_API_KEY"

Query Parameters

ParameterRequiredDescriptionExample
queryYesSearch term (name or identifier)E-INVOICE
country_codeNoFilter by country (ISO 3166-1 alpha-2)BE

Response

{
  "results": [
    {
      "name": "E-INVOICE BV",
      "country_code": "BE",
      "identifiers": [
        {
          "scheme": "0208",
          "value": "1018265814"
        }
      ]
    },
    {
      "name": "Example Company BVBA",
      "country_code": "BE",
      "identifiers": [
        {
          "scheme": "0208",
          "value": "0123456789"
        }
      ]
    }
  ]
}

Comparison

Feature/api/lookup/api/lookup/participants
Data sourceSMP (real-time)Peppol Directory (database)
Accuracy100% accurateMay be incomplete
Query typeExact matchFuzzy search
Use caseValidationDiscovery
SpeedFast (single lookup)Moderate (database search)
ReliabilityAlways shows registered participantsMay miss registered participants
Required inputExact Peppol IDPartial name or identifier

Common Peppol ID Schemes

Different countries use different identifier schemes:
CountryScheme CodeIdentifier TypeFormat Example
Belgium0208CBE number (VAT without ‘BE’)0208:0123456789
Netherlands0106KVK number0106:12345678
Germany0204VAT number0204:DE123456789
France0009SIRET0009:12345678901234
UK0088GLN0088:1234567890123
Norway0192Organization number0192:123456789
Sweden0007Organization number0007:1234567890
Denmark0184CVR number0184:12345678
International0088Global Location Number0088:1234567890123
For a complete list of Peppol identifier schemes, see the official Peppol code list.

Integration Examples

Direct Lookup by CBE Number

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 lookupCustomer(cbeNumber) {
  try {
    // Construct Peppol ID (Belgian example)
    // Note: For Belgium, use CBE number without 'BE' prefix
    // If you have VAT number BE0123456789, use just 0123456789
    const peppolId = `0208:${cbeNumber}`;

    const response = await api.get('/api/lookup', {
      params: { peppol_id: peppolId }
    });

    if (response.data.registered) {
      console.log('✓ Customer can receive e-invoices via Peppol');
      console.log('  Name:', response.data.name);
      console.log('  Peppol ID:', response.data.peppol_id);
      return response.data;
    }

  } catch (error) {
    if (error.response?.status === 404) {
      console.log('✗ Customer is not registered on Peppol network');
      console.log('  They cannot receive e-invoices electronically');
      return null;
    }
    console.error('Error looking up customer:', error.response?.data || error.message);
    return null;
  }
}

// Example usage
await lookupCustomer('1018265814');  // CBE number without 'BE' prefix

Discovery + Validation Pattern

Combine both endpoints for the best user experience:
async function findAndValidateCustomer(companyName, cbeNumber = null) {

  // Step 1: If we have exact CBE number, use direct lookup
  if (cbeNumber) {
    console.log(`Looking up CBE ${cbeNumber} directly...`);
    try {
      const peppolId = `0208:${cbeNumber}`;
      const response = await api.get('/api/lookup', {
        params: { peppol_id: peppolId }
      });

      if (response.data.registered) {
        console.log('✓ Found via direct SMP lookup');
        console.log('  Name:', response.data.name);
        return response.data;
      }
    } catch (error) {
      if (error.response?.status === 404) {
        console.log('✗ Not registered on Peppol network');
        return null;
      }
      console.error('Direct lookup failed:', error.message);
      return null;
    }
  }

  // Step 2: Search directory by name
  console.log(`Searching directory for "${companyName}"...`);
  try {
    const searchResponse = await api.get('/api/lookup/participants', {
      params: {
        query: companyName,
        country_code: 'BE'
      }
    });

    if (!searchResponse.data.results || searchResponse.data.results.length === 0) {
      console.log('⚠ No results in Peppol Directory');
      console.log('  Note: This does not mean the company is unregistered.');
      console.log('  Some access points do not publish to the directory.');
      console.log('  If you have the CBE number, use direct lookup instead.');
      return null;
    }

    console.log(`Found ${searchResponse.data.results.length} participant(s) in directory:`);
    searchResponse.data.results.forEach((p, i) => {
      const peppolId = `${p.identifiers[0].scheme}:${p.identifiers[0].value}`;
      console.log(`  ${i + 1}. ${p.name} (${peppolId})`);
    });

    // Step 3: Validate the first result via direct lookup
    const firstResult = searchResponse.data.results[0];
    const firstPeppolId = `${firstResult.identifiers[0].scheme}:${firstResult.identifiers[0].value}`;
    console.log(`\nValidating ${firstResult.name} via direct SMP lookup...`);

    const validateResponse = await api.get('/api/lookup', {
      params: { peppol_id: firstPeppolId }
    });

    if (validateResponse.data.registered) {
      console.log('✓ Confirmed registration via SMP');
      return validateResponse.data;
    }

  } catch (error) {
    console.error('Search failed:', error.message);
    return null;
  }
}

// Example usage

// With exact CBE number (preferred)
await findAndValidateCustomer('E-INVOICE', '1018265814');

// With company name only (discovery)
await findAndValidateCustomer('E-INVOICE');

Pre-Flight Check Workflow

Before creating an invoice, validate the recipient:
async function createInvoiceWithValidation(invoiceData) {
  try {
    // 1. Validate recipient is on Peppol network
    const customerPeppolId = invoiceData.customer.party_legal_entity.company_id;

    console.log('Checking if customer can receive e-invoices...');
    const validation = await api.get('/api/lookup', {
      params: { peppol_id: customerPeppolId }
    });

    if (!validation.data.registered) {
      console.error('❌ Customer cannot receive e-invoices via Peppol');
      console.error('   Peppol ID:', customerPeppolId);
      console.error('   Please use alternative delivery method (email, PDF)');
      return null;
    }

    console.log('✓ Customer can receive e-invoices');

    // 2. Validate invoice JSON
    console.log('Validating invoice JSON...');
    const jsonValidation = await api.post('/api/validate/json', invoiceData);

    if (!jsonValidation.data.valid) {
      console.error('❌ Invoice validation failed');
      jsonValidation.data.errors.forEach(error => {
        console.error(`   - ${error.field}: ${error.message}`);
      });
      return null;
    }

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

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

    // 4. Send via Peppol
    console.log('Sending via Peppol...');
    const result = await api.post(`/api/documents/${document.data.id}/send`);
    console.log('✓ Invoice sent:', result.data.state);

    return document.data;

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

Handling Unregistered Customers

If a customer is not on the Peppol network:
  1. Inform them: Let them know about Peppol e-invoicing benefits
  2. Alternative delivery: Send PDF invoices via email
  3. Register with e-invoice.be: Customers can sign up at e-invoice.be

Example: Fallback Logic

async function sendInvoiceToCustomer(invoiceData) {
  const peppolId = invoiceData.customer.party_legal_entity.company_id;

  try {
    // Check if customer is on Peppol
    const validation = await api.get('/api/lookup', {
      params: { peppol_id: peppolId }
    });

    if (validation.data.registered) {
      // Send via Peppol
      return await sendViaPeppol(invoiceData);
    }
  } catch (error) {
    // Fallback to email with PDF
    console.log('Customer not on Peppol, sending PDF via email');
    return await sendPdfViaEmail(invoiceData);
  }
}

Best Practices

Before sending invoices, use /api/lookup with the exact Peppol ID:
// ✓ Correct: Direct lookup for validation
const peppolId = `0208:${cbeNumber}`;
try {
  const validation = await api.get('/api/lookup', {
    params: { peppol_id: peppolId }
  });

  if (validation.data.registered) {
    await createAndSendInvoice(invoiceData);
  }
} catch (error) {
  // Participant not found
  console.log('Customer not registered on Peppol');
}

// ✗ Wrong: Using directory search for validation
const search = await api.get('/api/lookup/participants', {
  params: { query: cbeNumber }
});
// May return no results even if registered!
The Peppol Directory is excellent for discovery, not validation:
// Use case: User types company name in autocomplete
async function autocompleteSearch(userInput) {
  const results = await api.get('/api/lookup/participants', {
    params: {
      query: userInput,
      country_code: 'BE'
    }
  });

  // Display results as suggestions
  return results.data.results || [];
}

// When user selects a result, validate via direct lookup
async function onCustomerSelected(identifier) {
  const peppolId = `${identifier.scheme}:${identifier.value}`;

  try {
    const participant = await api.get('/api/lookup', {
      params: { peppol_id: peppolId }
    });
    return participant.data;
  } catch (error) {
    return null;
  }
}
Cache participant lookups to reduce API calls:
const participantCache = new Map();
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours

async function lookupWithCache(peppolId) {
  const cached = participantCache.get(peppolId);
  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }

  try {
    const response = await api.get('/api/lookup', {
      params: { peppol_id: peppolId }
    });

    const data = response.data;
    participantCache.set(peppolId, {
      data,
      timestamp: Date.now()
    });

    return data;
  } catch (error) {
    return null;
  }
}
Different countries use different schemes. Build a mapping:
const PEPPOL_SCHEMES = {
  'BE': '0208',  // Belgian CBE (numbers only, no 'BE' prefix)
  'NL': '0106',  // Dutch KVK
  'DE': '0204',  // German VAT
  'FR': '0009',  // French SIRET
  'UK': '0088',  // UK GLN
};

function buildPeppolId(countryCode, identifier) {
  const scheme = PEPPOL_SCHEMES[countryCode];
  if (!scheme) {
    throw new Error(`Unknown country: ${countryCode}`);
  }
  return `${scheme}:${identifier}`;
}
Explain why directory search may not find participants:
const searchResults = await api.get('/api/lookup/participants', {
  params: { query: companyName }
});

if (!searchResults.data.results || searchResults.data.results.length === 0) {
  console.log(`
    No results found in Peppol Directory for "${companyName}".

    This does not necessarily mean they are unregistered.
    Some Peppol access points do not publish participant data
    to the directory, even though those participants can receive
    invoices.

    If you have the customer's CBE number or Peppol ID, use that
    for a direct lookup instead.
  `);
}
Don’t assume a participant is unregistered if not in directory:
async function lookupCustomer(cbeNumber) {
  const peppolId = `0208:${cbeNumber}`;

  try {
    // Try direct lookup
    const response = await api.get('/api/lookup', {
      params: { peppol_id: peppolId }
    });

    if (response.data.registered) {
      return {
        found: true,
        data: response.data,
        source: 'SMP'
      };
    }
  } catch (error) {
    // Not registered or error
    return {
      found: false,
      message: 'Customer not registered on Peppol network',
      suggestion: 'Ask customer to register at https://app.e-invoice.be'
    };
  }
}

Real-World Scenario

Directory vs. SMP Lookup

Some Peppol access points register participants but do not publish all participant data to the Peppol Directory. What happens:
  • A participant is fully registered with their access point
  • They can send and receive invoices via Peppol
  • Their registration is in the SMP (authoritative source)
  • But their access point has not synchronized data with the directory
Results: Via /api/lookup (Direct SMP):
{
  "peppol_id": "0208:xxxxxxxxxx",
  "name": "Example Company BV",
  "country_code": "BE",
  "identifiers": [
    {
      "scheme": "0208",
      "value": "xxxxxxxxxx"
    }
  ],
  "registered": true
}
Found - The participant is registered Via /api/lookup/participants (Peppol Directory):
{
  "results": []
}
Not found - Not in the directory database

The Takeaway

The participant is fully registered and can receive invoices, but does not appear in directory searches. This demonstrates why /api/lookup must be used for validation before sending invoices.

Technical Details

Why the Difference Exists

  • SMP registration is required for Peppol participation
  • Directory publication is optional for access points
  • Some access points prioritize privacy and don’t publish participant lists
  • Others may have technical reasons for not synchronizing with the directory

Data Freshness

EndpointData AgeUpdates
/api/lookupReal-timeImmediate (queries SMP directly)
/api/lookup/participantsCachedPeriodic (synced from Peppol Directory)

Integration Checklist

When implementing participant lookup in your application:
  • Use /api/lookup for all validation before sending invoices
  • Use /api/lookup/participants only for discovery and search features
  • Never rely on directory search absence as proof of non-registration
  • Always validate directory search results via direct SMP lookup
  • Provide clear feedback when directory search returns no results
  • Cache direct lookup results (with appropriate TTL)
  • Handle cases where CBE number is known vs. only company name
  • Test with known participants from different access points

Next Steps