How to send invoices to Chorus Pro using B2Brouter API

French Invoice Submission via Chorus Pro with B2Brouter

Introduction

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.

With B2Brouter’s Chorus Pro integration you can automate SIRET lookups, UBL XML validation, submission, status polling (or webhooks), and legal-file retrieval—minimizing manual steps, avoiding errors, and ensuring full compliance.

Prerequisites

  • French company with a valid VAT/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 & Enterprise plan
    • To go live, register at app.b2brouter.net.
    • An active contract (Enterprise plan) is required for production-grade API access and advanced features.
    • Contact our Sales team at https://www.b2brouter.net/global/contact/ to discuss plans, sign your contract, and receive dedicated integration support.

If you need more details on registration and integration, consult the User Guide and Dev Guide.

Obtaining API Credentials

  1. Log in to your B2Brouter account.
  2. Go to the Integration section (lightning bolt icon).
  3. Select B2Brouter API.
  4. Click Show API Key to retrieve your API token.

Retrieve or Create Your Company Account

If you haven’t yet created a B2Brouter account for your company, use the Create Account endpoint.
If you already have an account, retrieve its id with the List Accounts endpoint and skip the creation step.

1. Retrieve your Company ID

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

GET accounts

2. Create a Company Account (if needed)

Request Example:


curl --request POST \
--url https://app-staging.b2brouter.net/accounts \
--header 'X-B2B-API-Key: {{YOUR_API_KEY}}' \
--header 'accept: application/json' \
--header 'content-type: application/json' \
--data '{
    "account": {
      "country": "fr",
      "rounding_method": "half_up",
      "tin_value": "FR46458880332",
      "tin_scheme": 9957,
      "name": "Exemplar SAS",
      "address": "10 Rue Imaginaire",
      "city": "Paris",
      "postalcode": "75001",
      "province": "Île-de-France",
      "email": "[email protected]"
  }
}'

Sample Response:


{
    "account": {
        "id": 83428,
        "tin_value": "FR46458880332",
        "tin_scheme": 9957,
        "name": "Exemplar SAS",
        "address": "10 Rue Imaginaire",
        "city": "Paris",
        "postalcode": "75001",
        "province": "Île-de-France",
        "country": "fr",
        "currency": "EUR",
        "email": "[email protected]",
         "rounding_method": "half_up",
        "round_before_sum": false,
        "apply_taxes_per_line": false,
        "archived": false,
        "created_at": "2025-06-10T09:54:38.000Z",
        "updated_at": "2025-06-10T09:54:38.000Z",
        "transactions_count": 0,
        "transactions_count_previous_period": 0,
        "transactions_limit": 1200
    }
}

POST account - API Reference

Verify recipient information

If this is your first time sending a document to a recipient, a contact will be automatically generated using the data provided in the invoice. If the contact already exists in our public directory, the appropriate delivery method and format for sending invoices will be assigned. You can also check if the recipient exists in our public directory:

Request Example:

curl --location 'https://app.b2brouter.net/api/v1/directory/fr/0009/13001533200013.json'
--header 'X-B2B-API-KEY:  {{YOUR_API_KEY}}' 
--header 'Content-Type: application/json' 

Sample Response:

{
  "name": "UNIVERSITE D'AIX MARSEILLE",
  "email": null,
  "website": null,
  "address": "58 BD CHARLES LIVON",
  "address2": null,
  "city": "MARSEILLE 7",
  "postalcode": "13007",
  "province": null,
  "country": "fr",
  "language": "fr",
  "currency": "EUR",
  "tin_scheme": null,
  "tin_value": null,
  "cin_scheme": "0009",
  "cin_value": "13001533200013",
  "pin_scheme": null,
  "pin_value": null,
  "invoice": {
    "transport_type_code": "fr.chorus",
    "document_type_code": "xml.ubl.invoice.chorus",
    "other_document_type_codes": []
  },
  "organizational_units": [
    {
      "name": "Service des factures publiques",
      "address": "58 BD CHARLES LIVON",
      "address2": null,
      "city": "MARSEILLE 7",
      "postalcode": "13007",
      "province": null,
      "country": "fr",
      "routing_codes": {
        "cin1_scheme": "8017",
        "cin1_value": "FACTURES_PUBLIQUES"
      },
    },
    {
      "name": "Service facturier HORS RECHERCHE",
      "address": "58 BD CHARLES LIVON",
      "address2": null,
      "city": "MARSEILLE 7",
      "postalcode": "13007",
      "province": null,
      "country": "fr",
      "routing_codes": {
        "cin1_scheme": "8017",
        "cin1_value": "03HORSRECHERCHE",
      },
    },
    {
      "name": "Service facturier BATIMENTS",
      "address": "58 BD CHARLES LIVON",
      "address2": null,
      "city": "MARSEILLE 7",
      "postalcode": "13007",
      "province": null,
      "country": "fr",
      "routing_codes": {
        "cin1_scheme": "8017",
        "cin1_value": "01BATIMENTS",
      },
    },
    {
      "name": "Fondation AMIDEX",
      "address": "58 BD CHARLES LIVON",
      "address2": null,
      "city": "MARSEILLE 7",
      "postalcode": "13007",
      "province": null,
      "country": "fr",
      "routing_codes": {
        "cin1_scheme": "8017",
        "cin1_value": "04AMIDEX",
      },
    },
    {
      "name": "Service facturier RECHERCHE",
      "address": "58 BD CHARLES LIVON",
      "address2": null,
      "city": "MARSEILLE 7",
      "postalcode": "13007",
      "province": null,
      "country": "fr",
      "routing_codes": {
        "cin1_scheme": "8017",
        "cin1_value": "02RECHERCHE",
      },
    },
    {
      "name": "Factures marché FCM ROP cadre A2",
      "address": "58 BD CHARLES LIVON",
      "address2": null,
      "city": "MARSEILLE 7",
      "postalcode": "13007",
      "province": null,
      "country": "fr",
      "routing_codes": {
        "cin1_scheme": "8017",
        "cin1_value": "ESR_MISSION_FACTURES_DEPLACEMENTS",
      },
    }
  ]
}

GET Lookup Directory - API Reference

Create a Contact

Once your company is set up, we can start by creating the contacts that we will later invoice.

When creating a French customer, consider whether they will be registered with their SIRET number for identification:

  • Use cin_value for SIRET-CODE number.
  • Use cin_scheme to identify the Schemes Codelist. SIRET-CODE is 0009.
  • taxcode / tin_value = general VAT number (not used for French B2G)
  • transport_type_code should be fr.chorus.
  • document_type_code should be xml.ubl.invoice.chorus.

Request Example:

curl --request POST
--url https://app-staging.b2brouter.net/projects/{{PROJECT_ID}}/clients.json
--header 'X-B2B-API-Key: {{YOUR_API_KEY}}'
--header 'accept: application/json'
--header 'content-type: application/json'
--data '{
  "client": {
    "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"
  }
}'

Sample Response:

{
  "client": {
    "id": 1313228381,
    "taxcode": null,
    "name": "UNIVERSITE D'AIX MARSEILLE",
    "email": null,
    "website": null,
    "address": "58 BD CHARLES LIVON",
    "address2": null,
    "postalcode": "13007",
    "city": "MARSEILLE 7",
    "province": null,
    "country": "fr",
    "language": "en",
    "currency": "EUR",
    "is_client": true,
    "is_provider": true,
    "invoice_format": "send_chorus",
    "transport_type": "fr.chorus",
    "document_type": "xml.ubl.invoice.chorus",
    "transport_type_code": "fr.chorus",
    "document_type_code": "xml.ubl.invoice.chorus",
    "terms": "custom",
    "sepa_type": "CORE",
    "company_identifier": "13001533200013",
    "recipient_code": "XXXXXXX",
    "cin_scheme": "0009",
    "cin_value": "13001533200013",
    "created_at": "2025-06-18T07:19:32Z",
    "updated_at": "2025-06-18T07:19:32Z"
  }
}

Once the contact is created, it's relevant to save the id of the contact to be able to use it later on to manage the contact or to link it to an invoice.

Create an Organizational Unit Contact

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).

curl --request POST \
  --url https://app-staging.b2brouter.net/projects/{{PROJECT_ID}}/contacts.json \
  --header 'X-B2B-API-Key: {{YOUR_API_KEY}}' \
  --header 'Content-Type: application/json' \
  --data '{
    "contact": {
      "parent_id":   1313228381,                        # ID of the parent public entity
      "name":        "Factures marché FCM ROP cadre A2",
      "address":     "58 BD CHARLES LIVON",
      "city":        "MARSEILLE 7",
      "postalcode":  "13007",
      "country":     "fr",
      "cin1_scheme": "8017",                            # scheme for “service code”
      "cin1_value":  "ESR_MISSION_FACTURES_DEPLACEMENTS" # Chorus Pro routing code
    }
  }'

“This sub-contact can now be billed directly via its contact_id, eliminating the need to duplicate its service code or contact information. Its ID is separate from the parent entity’s.”

POST Contact - API Reference

Create and Send an Invoice

To create and send an invoice in one step, use the "Create an issued invoice" call with the flag send_after_import: true.

When invoicing a French customer, ensure you provide all the required fields in the INVOICE OBJECT, including:

  • number
  • date and due_date
  • At least one invoice_lines_attributes
  • At least one taxes_attributes
  • contact_id or a complete contact object.
  • ponumber to identify the Order reference.
  • Use buyer_reference (cin1_value and cin1_scheme of the contact) to identify the Code Service. It must always be specified when creating the invoice.
    To issue an invoice via Chorus, the Code Service of the target French public entity must be obtained in advance. This identifier is required by French public institutions, as it enables proper routing of the invoice to the designated recipient within the administrative structure.

Request Example:

curl --request POST
--url https://app-staging.b2brouter.net/projects/{{PROJECT_ID}}/invoices.json
--header 'X-B2B-API-Key: {{YOUR_API_KEY}}'
--header 'content-type: application/json'
--data '{
    "send_after_import": true,                              # true = create & send immediately
    "invoice": {
      "type": "IssuedInvoice",
      "contact_id": 1313228399,                             # use the Contact id or Org Unit id
      "bank_account": {
        "type": "iban",
        "iban": "FR7630006000011234567890189"
      },
      "terms": "custom",
      "invoice_lines_attributes": [
        {
          "unit": 5,
          "quantity": 135,
          "price": 25,
          "description": "Cocktail Dinatoire (6 savory + 2 sweet pieces)…",
          "taxes_attributes": [
            { "name": "TVA", "category": "S", "percent": 10 }
          ],
          "article_code": "14",
          "position": 1
        },
        {
          "unit": 5,
          "quantity": 135,
          "price": 2.92,
          "description": "Vins Bio de Loire",
          "taxes_attributes": [
            { "name": "TVA", "category": "S", "percent": 20 }
          ],
          "article_code": "15",
          "position": 2
        }
      ],
      "number": "00002",
      "date": "2025-06-18",
      "due_date": "2025-07-18",
      "transport_type_code": "fr.chorus",
      "document_type_code": "xml.ubl.invoice.chorus",
      "currency": "EUR",
      "ponumber": "0123456",                                      #Order reference
      "buyer_reference": "ESR_MISSION_FACTURES_DEPLACEMENTS"      #Code Service if is not detailed on contact
    }
  }'

Sample Response:

{
  "invoice": {
    "id": 160869,
    "type": "IssuedInvoice",
    "number": "00002",
    "from_net": "api",
    "project": {
      "id": 3729,
      "name": "Exemplar SAS"
    },
    "company": {
      "id": 3898,
      "name": "Exemplar SAS",
      "taxcode": "FR90425434945",
      "address": "10 Rue Imaginaire",
      "address2": null,
      "postalcode": "75001",
      "city": "Paris",
      "province": "Île-de-France",
      "country": "fr"
    },
    "client": {
      "id": 1313228399,
      "name": "Factures marché FCM ROP cadre A2",
      "taxcode": "13001533200013",
      "address": "58 BD CHARLES LIVON",
      "address2": null,
      "postalcode": "13007",
      "city": "MARSEILLE 7",
      "province": null,
      "country": "fr",
      "party_identification": null
    },
    "state": "sending",
    "date": "2025-06-18",
    "due_date": "2025-07-18",
    "buyer_reference": "ESR_MISSION_FACTURES_DEPLACEMENTS",
    "ponumber": "0123456",
    "subtotal": 3769.2,
    "taxes": [
      {
        "name": "TVA 10,00%",
        "percent": 10.0,
        "base": 3375.0,
        "amount": 337.5
      },
      {
        "name": "TVA 20,00%",
        "percent": 20.0,
        "base": 394.2,
        "amount": 78.84
      }
    ],
    "total": 4185.54,
    "currency": "EUR",
    "withheld_percent": 0.0,
    "amounts_withheld": 0.0,
    "payable_amount": 4185.54,
    "payment_method": 4,
    "payment_method_info": "Paiement par virement sur le compte<br />IBAN FR33 1273 9000 5081 2615 4238 C05<br />",
    "attachments": [
    ],
    "created_at": "2025-06-18T08:31:18Z",
    "updated_at": "2025-06-18T08:31:25Z",
    "state_updated_at": "2025-06-18T08:31:25Z",
    "apply_taxes_to_charge": false,
    "charge_is_reimbursable_expense": false
  }
}

Once the invoice has been created and sent you will save the id of this invoice that will be useful to get back information from this invoice later on.

POST Invoice - API Reference

Check Invoice Status

Once the invoice is sent, we need to confirm if the submission was successful and the invoice was properly registered on Chorus Pro. You can also use this check to monitor its state changes over time.

There are different options to check this:

Check Single Invoice Status

You can query the status of each invoice using its internal ID with the "Get an invoice" API.

Request Example:


curl --request GET
--url 'https://app-staging.b2brouter.net/invoices/{{INVOICE_ID}}.json?include=lines'
--header 'X-B2B-API-Key: {{YOUR_API_KEY}}'
--header 'accept: application/json'

Sample Response:


{
  "invoice": {
    "id": 160869,
    "state": "sent",
    "to_net": "fr.chorus",
    "total": 4185.54,
    "currency": "EUR",
    "created_at": "2025-06-18T08:31:18Z",
    "state_updated_at": "2025-06-18T08:31:29Z"
    "download_legal_url": "/attachments/download/359044/FSO1100A_AAB822_AAB8221100175023548801100",

  }
}

GET Invoice - API Reference

Check Multiple Invoices Status

Alternatively, you can get the status of all issued invoices at once with the "List of issued invoices" API.

By using state_updated_at_from, you avoid fetching invoices that haven’t changed since your last check—keeping the response concise and relevant.

Request Example:


curl --request GET
--url 'https://app-staging.b2brouter.net/projects/{{PROJECT_ID}}/invoices.json?offset=0&limit=25&state_updated_at_from=2025-06-12'
--header 'X-B2B-API-Key: {{YOUR_API_KEY}}'
--header 'accept: application/json'

Sample Response:


{
  "invoices": [
    {
      "id": 99915,
      "state": "sent",
      "to_net": "fr.chorus",
      "total": 573.4,
      "currency": "EUR",
      "created_at": "2025-01-22T16:30:39Z",
      "state_updated_at": "2025-01-22T16:30:41Z"
    },
    {
    "id": 160869,
    "state": "sent",
    "to_net": "fr.chorus",
    "total": 4185.54,
    "currency": "EUR",
    "created_at": "2025-06-18T08:31:18Z",
    "state_updated_at": "2025-06-18T08:31:29Z"
    "download_legal_url": "/attachments/download/359044/FSO1100A_AAB822_AAB8221100175023548801100",

  }
  ]
}

GET List Invoices - API Reference

Real-Time Status Updates with Webhooks

Instead of polling the invoice status endpoint, you can subscribe to push notifications via webhooks. Whenever an invoice state changes, B2Brouter will send an HTTP POST to your endpoint, no extra API calls required.

Invoice Status WebHooks - API Reference

Download the Original Invoice XML

After you’ve sent an invoice, the GET /invoices/{id}.json response includes a download_legal_url field. Use that URL to fetch the exact XML file that was submitted to Chorus Pro:

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

Mark Invoice as Acknowledged

If you use the list option, it is also useful to mark invoices as "acknowledged" when they reach their final state. This can be done using the "Mark an invoice as acknowledged" API, which prevents the invoice from appearing in the list of issued invoices.

Request Example:


curl --request POST
--url 'https://app-staging.b2brouter.net/invoices/{{INVOICE_ID}}/ack.json'
--header 'X-B2B-API-Key: {{YOUR_API_KEY}}'
--header 'accept: application/json'

Conclusion

This guide covers the key steps to sending invoices to the Chorus Pro through B2Brouter’s API.

For further help: