Skip to main content

Create a Sender

Before you can send invoices through the Peppol network, you need to establish your organization as a "sender" (also known as a Legal Entity). This guide walks you through the complete setup process.

Overviewโ€‹

A sender in GoRoute represents your organization or your client's organization that will send e-invoices. Each sender has:

  • Peppol Identifier - A unique ID on the Peppol network (e.g., 0088:1234567890123)
  • Organization Details - Legal name, address, VAT number
  • Document Capabilities - Types of documents you can send
  • SMP Registration - Entry in the Service Metadata Publisher

Step 1: Gather Required Informationโ€‹

Before creating a sender, collect this information:

FieldDescriptionExample
Legal NameOfficial registered company nameAcme Corporation B.V.
VAT NumberTax identification numberNL123456789B01
Peppol SchemeIdentifier scheme for your country0106 (NL KvK)
Peppol IDYour identifier in the scheme12345678
CountryISO 3166-1 alpha-2 codeNL
AddressRegistered business addressHerengracht 123, Amsterdam

Common Peppol Identifier Schemesโ€‹

CountryScheme CodeDescription
Netherlands0106KvK (Chamber of Commerce)
Belgium0208Enterprise Number
Germany9930Leitweg-ID
France0009SIRET
Italy0211Codice Fiscale
Global0088GLN (EAN Location Number)

See Identifiers for the complete list.

Step 2: Your Organization Already Existsโ€‹

Earlier revisions of this page documented endpoints that do not exist

Until this revision, Steps 2 to 5 told you to call /peppol-api/organizations, /peppol-api/participants, /peppol-api/participants/lookup, /peppol-api/documents/send and /peppol-api/participants/{scheme:value}. None of those paths has ever existed โ€” they omit the /api/v1 part of the address, and two of them describe operations the API does not offer at all. Code written against them has never worked. The correct calls are below.

There is no customer-facing call that creates an organization. GoRoute creates it for you when your account request is approved, and your API key identifies it on every request after that. You never pass an organization ID.

If you do not have an organization yet, that is an onboarding step rather than an API step โ€” see Client Onboarding, which covers the account request, the welcome email and where to find your API key.

The organization records your legal name, country and VAT number. To read them, and to change your contact and address details, use Company Settings in the dashboard at app.goroute.ai, or GET and PATCH /api/v1/settings/organization โ€” see Organizations & Multi-Tenancy. The country is required when the organization is created, as an ISO 3166-1 alpha-2 code such as OM, and PATCH /api/v1/settings/organization does not change it โ€” see Organization country.

Step 3: Create the Participant, Then Publish Itโ€‹

Becoming a sender is two calls, not one, and it is worth understanding why:

  1. POST /api/v1/participants stores the participant record in GoRoute.
  2. POST /api/v1/participants/{participant_id}/register publishes that record to the Peppol SMP and SML, which is what makes you reachable on the network.

Creating the record does not publish it. A participant that is created but never registered is invisible to the rest of the network.

3a. Create the participant recordโ€‹

curl -X POST https://app.goroute.ai/peppol-api/api/v1/participants \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"scheme": "0106",
"identifier": "12345678",
"display_name": "Acme Corporation B.V.",
"country": "NL",
"contact_email": "invoices@acme.nl",
"document_types": [
"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice"
]
}'

There is no org_id field: your API key already says which organization this belongs to.

Response โ€” 201 Created (abbreviated; the full record is on Participant Registration):

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"scheme": "0106",
"identifier": "12345678",
"peppol_id": "0106:12345678",
"display_name": "Acme Corporation B.V.",
"country": "NL",
"status": "active",
"smp_registered": false,
"sml_registered": false,
"document_types": [
"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice"
]
}

Note what this response is not. There is no "status": "registered", no smp_url and no registered_at. smp_registered and sml_registered are booleans and both are false until step 3b succeeds.

3b. Publish it to the SMP and SMLโ€‹

The path parameter is the record's UUID, not your Peppol identifier

{participant_id} is the id from the response above โ€” a UUID such as 550e8400-e29b-41d4-a716-446655440000. It is not 0106:12345678. Passing the scheme:value identifier fails; every route under /api/v1/participants/{participant_id} parses this segment as a UUID.

curl -X POST https://app.goroute.ai/peppol-api/api/v1/participants/550e8400-e29b-41d4-a716-446655440000/register \
-H "X-API-Key: your-api-key"

The call takes no request body. Document types are chosen for you from the participant's country: AU and NZ participants get PINT A-NZ, everyone else gets BIS Billing 3.0.

Both calls need the participants:manage permission on your API key. Publishing an identifier beyond what your plan includes needs the SMP subscription and otherwise answers 402 SUBSCRIPTION_REQUIRED. Participant Registration covers the options, the full response and the failure cases.

Step 4: Verify Registrationโ€‹

Confirm the sender is visible on the Peppol network. This asks the network, not our database, so it is the honest check that step 3b worked:

curl -G https://app.goroute.ai/peppol-api/api/v1/participants/lookup \
--data-urlencode "peppol_id=0106:12345678" \
-H "X-API-Key: your-api-key"

The query parameter is peppol_id, in scheme:value form. (identifier is accepted as an older alias for the same thing.) Omitting the colon returns 400.

Response:

{
"found": true,
"participant_id": "0106:12345678",
"name": "Acme Corporation B.V.",
"country": "NL",
"capabilities": [
"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1"
],
"message": null
}

A participant that is not on the network returns 200 with "found": false and a message, not a 404. capabilities is a flat list of document type identifiers. This endpoint needs the participants:read permission. GET /api/v1/participants/lookup is the address; see Participant Registration for interpreting the result.

Step 5: Send Your First Invoiceโ€‹

Now you can send invoices from this sender. Sending is a JSON call โ€” the UBL document travels as a string inside the JSON body, and you do not POST raw XML:

curl -X POST https://app.goroute.ai/peppol-api/api/v1/documents \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"receiver_scheme": "9959",
"receiver_id": "987654321",
"document": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Invoice ...>"
}'

A successful send answers 202 Accepted with a transaction_id and a status. The address is POST /api/v1/documents; there is no documents/send path and there never has been. Sending an Invoice covers the full request, the response and how to track what happens next.

The AccountingSupplierParty in your invoice must match the registered sender:

<cac:AccountingSupplierParty>
<cac:Party>
<cbc:EndpointID schemeID="0106">12345678</cbc:EndpointID>
<cac:PartyName>
<cbc:Name>Acme Corporation B.V.</cbc:Name>
</cac:PartyName>
<!-- ... -->
</cac:Party>
</cac:AccountingSupplierParty>

Multi-Tenant Sendersโ€‹

If you are an integrator sending on behalf of multiple clients, each client is a separate organization with its own API key. You cannot create organizations yourself, and one key can never act for two of them โ€” see Organizations & Multi-Tenancy. What you automate is the participant set-up, once per client, using that client's key.

import requests

BASE_URL = "https://app.goroute.ai/peppol-api/api/v1"

UBL_INVOICE = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice"


def set_up_sender(api_key: str, company) -> dict:
"""Create the participant for one client, then publish it to the SMP."""
headers = {"X-API-Key": api_key, "Content-Type": "application/json"}

# 1. Create the record. The key decides the organization; there is no org_id.
created = requests.post(
f"{BASE_URL}/participants",
headers=headers,
json={
"scheme": company.peppol_scheme,
"identifier": company.peppol_id,
"display_name": company.legal_name,
"country": company.country,
"document_types": [UBL_INVOICE],
},
)
created.raise_for_status() # 201 Created
participant = created.json()

# 2. Publish it. The path parameter is the record's UUID, not scheme:value.
published = requests.post(
f"{BASE_URL}/participants/{participant['id']}/register",
headers={"X-API-Key": api_key},
)
published.raise_for_status()

return participant


for company in client_companies:
participant = set_up_sender(client_api_keys[company.id], company)
print(f"Registered {company.legal_name} as {participant['peppol_id']}")

Sender Capabilitiesโ€‹

Supported Document Typesโ€‹

By default, senders are registered for:

  • Invoice (BIS Billing 3.0)
  • Credit Note (BIS Billing 3.0)

You can add additional capabilities:

  • Order
  • Order Response
  • Dispatch Advice
  • Message Level Response

Adding Document Typesโ€‹

Update the participant record with PATCH /api/v1/participants/{participant_id}. As in step 3b, the path parameter is the record's UUID, and the list holds full document type identifiers rather than short names:

curl -X PATCH https://app.goroute.ai/peppol-api/api/v1/participants/550e8400-e29b-41d4-a716-446655440000 \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"document_types": [
"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice",
"urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2::CreditNote",
"urn:oasis:names:specification:ubl:schema:xsd:Order-2::Order"
]
}'
Updating the record is not the same as republishing it

PATCH changes what GoRoute stores. It does not change what the SMP advertises, so senders looking you up on the network still see the old set. Call POST /api/v1/participants/{participant_id}/register again afterwards to republish the service metadata.

Troubleshootingโ€‹

"Identifier already registered"โ€‹

Problem: The Peppol identifier is registered with another Access Point.

Solution:

  1. Contact the current AP to initiate a migration
  2. See Participant Migration

"Invalid identifier format"โ€‹

Problem: The identifier doesn't match the scheme's requirements.

Solution:

  • Check the scheme's format requirements
  • Remove any spaces or special characters
  • Ensure proper length (e.g., GLN must be exactly 13 digits)

"VAT number validation failed"โ€‹

Problem: The VAT number couldn't be verified.

Solution:

  • Verify the VAT number with VIES
  • Ensure country prefix matches country field
  • Check for typos

Best Practicesโ€‹

  1. Use production identifiers in production - Don't reuse test identifiers
  2. Verify before sending - Always confirm registration before first send
  3. Keep information updated - Update organization details when they change
  4. Test with sandbox first - Validate your integration before going live

Next Stepsโ€‹