Skip to content
Log in

Send invoices to Chorus Pro

In France, all invoices to public-sector clients must be submitted electronically via Chorus Pro, the government’s official B2G e-invoicing portal (Decree No. 2016-1478). Since April 2020, this applies to every central, regional and local administration—whether you’re a French company or an international supplier.

This guide shows you how to use the B2Brouter API to:

  1. Set up your company account (staging & production).
  2. Lookup public entities by SIRET in the B2Brouter Directory.
  3. Create customer and organizational-unit contacts.
  4. Generate, send, and track your invoices via Chorus Pro.
  5. Download the exact XML file submitted and acknowledge.
  • French company with a valid TVA/SIRET number.
  • Testing environment (staging)
    • Register at app-staging.b2brouter.net to try out the API.
    • Once registered, open a Support Ticket in staging to request your API key and permissions.
  • Production integration & eDocExchange subscription
  1. Log in to your B2Brouter account.
  2. Go to the Developers tab.
  3. Select API Keys.
  4. Click on the clipboard icon to retrieve your API token.

To invoice via Chorus Pro, both the issuing company and the client (the public entity) must be identified with a SIRET (cin_scheme="0009"), never with a SIREN:

  • Issuing company: if the company’s country is fr, cin_value must hold a non-blank SIRET and cin_scheme must equal 9 ("0009"). If the SIRET is blank, or cin_scheme is anything other than 9, the invoice is not sent.
  • Client: the recipient must also be identified with a SIRET. Without it, contact creation fails with the error “Chorus needs the SIRET identifier of the client”.

Hierarchical consistency: even though invoicing is always SIRET-to-SIRET, the account or contact shouldn’t be “flat” at SIRET level. Keep the same hierarchy as in the DGFiP circuit: a SIREN parent with the SIRET modeled as an organizational unit (OU) (parent_id + cin_scheme="0009") under that parent (see Account structure). If your company already has a B2Brouter account with a SIREN parent, create the issuing SIRET as an OU under that parent instead of a separate standalone account.

Terminal window
curl --request GET \
--url 'https://api-staging.b2brouter.net/accounts?offset=0&limit=25' \
--header 'X-B2B-API-Key: {YOUR_API_KEY}' \
--header 'X-B2B-API-Version: {YOUR_API_VERSION}' \
--header 'accept: application/json'

Provide cin_scheme: "0009" and cin_value with the issuing establishment’s SIRET (14 digits). This is required for Chorus Pro: without a SIRET, or with a cin_scheme other than 9, invoice submission fails.

If your company already has a B2Brouter account at SIREN parent level (for example, for DGFiP), don’t create a new standalone account with this SIRET: create it as an OU with parent_id pointing to the parent, following the same steps as Create organisational units in the DGFiP guide. The example below is for a company that doesn’t yet have any B2Brouter account.

Terminal window
curl --request POST \
--url https://api-staging.b2brouter.net/accounts \
--header 'X-B2B-API-Key: {YOUR_API_KEY}' \
--header 'X-B2B-API-Version: {YOUR_API_VERSION}' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '{
"account": {
"country": "fr",
"rounding_method": "half_up",
"tin_value": "FR46458880332",
"tin_scheme": 9957,
"cin_scheme": "0009",
"cin_value": "45888033200017",
"name": "Exemplar SAS",
"address": "10 Rue Imaginaire",
"city": "Paris",
"postalcode": "75001",
"province": "Île-de-France",
"email": "john.doe@example.com"
}
}'

You can check if the recipient exists in our public directory:

Terminal window
curl --request GET \
--url https://api-staging.b2brouter.net/directory/fr/0009/13001533200013 \
--header 'X-B2B-API-Key: {YOUR_API_KEY}' \
--header 'X-B2B-API-Version: {YOUR_API_VERSION}' \
--header 'Content-Type: application/json'

This lookup already returns the entity’s existing organizational units (service codes) from the Chorus Pro directory. You don’t need to create them yourself as a contact: use the cin1_scheme/cin1_value already returned by the directory.

When creating a French customer:

  • Use cin_value for SIRET-CODE number. This is required: without a SIRET, Chorus Pro rejects the invoice with the error “Chorus needs the SIRET identifier of the client”.
  • Use cin_scheme to identify the Schemes Codelist. SIRET-CODE is 0009; a SIREN (0002) is not accepted.
  • transport_type_code should be fr.chorus.
  • document_type_code should be xml.ubl.invoice.chorus.
Terminal window
curl --request POST \
--url https://api-staging.b2brouter.net/accounts/{ACCOUNT_ID}/contacts \
--header 'X-B2B-API-Key: {YOUR_API_KEY}' \
--header 'X-B2B-API-Version: {YOUR_API_VERSION}' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '{
"contact": {
"language": "en",
"is_client": true,
"is_provider": true,
"terms": "custom",
"public_sector": true,
"name": "UNIVERSITE D AIX MARSEILLE",
"address": "58 BD CHARLES LIVON",
"city": "MARSEILLE 7",
"postalcode": "13007",
"country": "fr",
"currency": "EUR",
"transport_type_code": "fr.chorus",
"document_type_code": "xml.ubl.invoice.chorus",
"cin_value": "13001533200013",
"cin_scheme": "0009"
}
}'

To bill a specific department or service, create a sub-contact under the main entity using parent_id and include the Chorus Pro “service code” (cin1_scheme / cin1_value).

Terminal window
curl --request POST \
--url https://api-staging.b2brouter.net/accounts/{ACCOUNT_ID}/contacts \
--header 'X-B2B-API-Key: {YOUR_API_KEY}' \
--header 'X-B2B-API-Version: {YOUR_API_VERSION}' \
--header 'Content-Type: application/json' \
--data '{
"contact": {
"parent_id": 1313228381,
"name": "Factures marché FCM ROP cadre A2",
"address": "58 BD CHARLES LIVON",
"city": "MARSEILLE 7",
"postalcode": "13007",
"country": "fr",
"cin1_scheme": "8017",
"cin1_value": "ESR_MISSION_FACTURES_DEPLACEMENTS"
}
}'

When invoicing a French customer, ensure you provide all required fields including:

  • number, date and due_date
  • At least one invoice_lines_attributes with taxes_attributes
  • contact_id or a complete contact object
  • ponumber to identify the Order reference
  • buyer_reference with the cin1_value (Code Service) to identify the target department
Terminal window
curl --request POST \
--url https://api-staging.b2brouter.net/accounts/{ACCOUNT_ID}/invoices \
--header 'X-B2B-API-Key: {YOUR_API_KEY}' \
--header 'X-B2B-API-Version: {YOUR_API_VERSION}' \
--header 'content-type: application/json' \
--data '{
"send_after_import": true,
"invoice": {
"type": "IssuedInvoice",
"contact_id": 1313228399,
"bank_account": {
"type": "iban",
"iban": "FR7630006000011234567890189"
},
"terms": "custom",
"invoice_lines_attributes": [
{
"unit": 5,
"quantity": 135,
"price": 25,
"description": "Cocktail Dinatoire",
"taxes_attributes": [
{ "name": "TVA", "category": "S", "percent": 10 }
],
"article_code": "14",
"position": 1
}
],
"number": "00002",
"date": "2025-06-18",
"due_date": "2025-07-18",
"currency": "EUR",
"ponumber": "0123456",
"buyer_reference": "ESR_MISSION_FACTURES_DEPLACEMENTS"
}
}'
Terminal window
curl --request GET \
--url 'https://api-staging.b2brouter.net/invoices/{INVOICE_ID}?include=lines' \
--header 'X-B2B-API-Key: {YOUR_API_KEY}' \
--header 'X-B2B-API-Version: {YOUR_API_VERSION}' \
--header 'accept: application/json'
Terminal window
curl --request GET \
--url 'https://api-staging.b2brouter.net/accounts/{ACCOUNT_ID}/invoices?offset=0&limit=25&state_updated_at_from=2025-06-12' \
--header 'X-B2B-API-Key: {YOUR_API_KEY}' \
--header 'X-B2B-API-Version: {YOUR_API_VERSION}' \
--header 'accept: application/json'

Instead of polling, subscribe to push notifications via webhooks. Whenever an invoice state changes, B2Brouter will send an HTTP POST to your endpoint.

Invoice Status WebHooks - API Reference

After sending, the GET /invoices/{id} response includes a download_legal_url field. Use it to fetch the exact XML file submitted to Chorus Pro:

Terminal window
curl --request GET \
--url https://api-staging.b2brouter.net{download_legal_url} \
--header 'X-B2B-API-Key: {YOUR_API_KEY}' \
--header 'X-B2B-API-Version: {YOUR_API_VERSION}' \
--header 'Accept: application/xml'

As a one-step alternative, you can call GET /invoices/{id}/as/legal directly, without first fetching download_legal_url from the invoice payload. It returns the archived legal document as stored and does not generate a billable transaction — see Download invoices and Transaction: View_as.

Terminal window
curl --request POST \
--url https://api-staging.b2brouter.net/invoices/{INVOICE_ID}/ack \
--header 'X-B2B-API-Key: {YOUR_API_KEY}' \
--header 'X-B2B-API-Version: {YOUR_API_VERSION}' \
--header 'accept: application/json'

For further help: