Skip to content
Log in

Migration guide v2026-04-20 → v2026-06-26

Everything you need to update your code.

This guide helps you upgrade from X-B2B-API-Version: 2026-04-20 to X-B2B-API-Version: 2026-06-26, focusing on what you need to change, not just what changed.

What changedDo you need to act?
Offices now store their own cin_value / cin_scheme instead of inheriting themYes — only if your integration creates or reads offices (contacts with parent_id)
Stricter contact matching on invoice creation with an inline contactYes — affects integrations that pass inline contact data on invoice creation
French Annuaire lookup on /directory/fr can now return 202 AcceptedYes — only if you call /directory/fr for French identifiers
Flat cinN_value / cinN_scheme no longer accepted on invoice contactYes — move these into contact.routing_codes
Dedicated routing fields (dire, accounting_unit, etc.) no longer accepted at invoice top levelYes — move these into the invoice-level routing_codes object
POST /invoices/{id}/generate_tax_report now returns 201 CreatedYes — only if you check for an exact 200 status
number invoice list filter now matches by prefix instead of exact matchOnly if your integration relies on exact-match behavior
New received invoice webhook eventsNo — opt-in
Create offices via POST /accounts with parent_idNo — opt-in
issue_after_import on invoice creation and importNo — opt-in
Account unit_code_format preferenceNo — opt-in
Per-invoice apply_taxes_per_lineNo — opt-in
New taxable_base / tax_amount fields in invoice listNo — additive
DGFiP refuse_reason_code on mark_as and invoice responsesNo — opt-in

1. Offices now store their own CIN instead of inheriting it

Section titled “1. Offices now store their own CIN instead of inheriting it”

Previously, an office (a contact created with parent_id) always inherited cin_value / cin_scheme from its parent contact. Now these fields are stored independently per office.

  • If you send cin_value / cin_scheme when creating or updating an office, they are stored on the office and returned in responses.
  • If you omit them (or clear with cin_value: ""), the office still inherits the parent's CIN in read responses — no action needed if you never set CIN on offices.
  • tin_value / tin_scheme are unaffected — still always inherited from the parent and cannot be set independently.
  • CIN uniqueness within the account is now enforced: reusing a cin_value already assigned to another contact in the same account returns 422 Unprocessable Entity.
POST /accounts/{account_id}/contacts
{
"contact": {
"parent_id": 12345,
"name": "Branch Office",
"cin_value": "1234567890",
"cin_scheme": 88
}
}
// Response — cin_value is ignored, inherited from the parent contact
{
"contact": {
"id": 67890,
"parent_id": 12345,
"name": "Branch Office",
"cin_value": "<parent's cin_value>",
"cin_scheme": "<parent's cin_scheme>"
}
}

Affected endpoints: POST /accounts/{account}/contacts, PUT /contacts/{id}


2. Stricter contact validation on invoice creation with an inline contact

Section titled “2. Stricter contact validation on invoice creation with an inline contact”

When you pass a contact hash inline on invoice creation, the API now matches it against existing contacts by tin_value or cin_value before deciding what to do:

  • Full match → the invoice is linked to the existing contact.
  • Routing identifiers differ (cin_value, cin_scheme, routing_codes, pin_value, recipient_code) → an office is created automatically.
  • Only descriptive fields differ (address, city, postalcode, etc.) → the request now returns 422 Unprocessable Entity with code data_mismatch instead of silently updating the contact.
  • No match → a new contact is created (and linked to the B2Brouter directory if it matches a known company).

If your integration relies on the previous behavior of silently overwriting descriptive contact data on invoice creation, pass update_contact: true (top-level request field, default false) to opt back in. Routing and tax identifiers are never overwritten this way, and update_contact has no effect on received documents.

// Opt in to updating descriptive contact data from invoice creation
POST /accounts/{account_id}/invoices
{
"update_contact": true,
"invoice": {
"contact": {
"tin_value": "B12345678",
"name": "Acme Corp",
"address": "New address 123"
}
}
}

The contacts endpoint is the recommended way to create and maintain contacts and their offices. To link an invoice to an existing contact without any matching logic, use contact_id directly.

Affected endpoints: POST /accounts/{account}/invoices


3. French Annuaire lookup on /directory/fr is now asynchronous

Section titled “3. French Annuaire lookup on /directory/fr is now asynchronous”

GET /directory/fr/{scheme}/{id} now consults the French PPF Annuaire for SIREN, SIRET, and FRCTC (0225) identifiers. If your integration calls this endpoint for French identifiers, handle the new response shapes:

  • 202 Accepted with a polling_url — the company was not yet known; a background job is querying the Annuaire. Retry after a few seconds.
  • 424 Failed Dependency with error code upstream_unavailable and a Retry-After header — the Annuaire or Peppol is temporarily unavailable. Retry after the indicated delay; do not treat this as a 404.
  • 422 Unprocessable Entity with code invalid_identifier — the identifier failed length/checksum validation for its scheme.
  • A new information_flags array is included in successful responses (FR_ASSUJETTI_ACTIVE, FR_ASSUJETTI_INACTIVE, FR_ASSUJETTI_UNKNOWN).
Terminal window
curl --request GET \
--url 'https://app-staging.b2brouter.net/api/v1/directory/fr/0002/123456789' \
--header 'X-B2B-API-Key: {YOUR_API_KEY}' \
--header 'X-B2B-API-Version: 2026-06-26'
// 202 Accepted — not yet known, retry polling_url
{
"status": "processing",
"message": "Querying French PPF Annuaire. Please retry this request.",
"polling_url": "https://app-staging.b2brouter.net/api/v1/directory/fr/0002/123456789"
}

Affected endpoints: GET /directory/fr/{scheme}/{id} (non-FR countries unaffected)


4. Flat cinN_value / cinN_scheme no longer accepted on invoice contact

Section titled “4. Flat cinN_value / cinN_scheme no longer accepted on invoice contact”

The inline contact object on POST /accounts/{account}/invoices no longer accepts flat cin1_value/cin1_schemecin5_value/cin5_scheme keys. Move them into a contact.routing_codes object — the same shape used by POST /accounts/{account}/contacts.

{
"invoice": {
"contact": {
"tin_value": "ESA13585625",
"name": "Buyer Full Name AS",
"cin1_value": "ESR_MISSION_FACTURES_DEPLACEMENTS",
"cin1_scheme": "8017"
}
}
}

Requests still using the flat keys return 422 Unprocessable Entity.

Affected endpoints: POST /accounts/{account}/invoices


5. Invoice-level routing fields replaced by a routing_codes object

Section titled “5. Invoice-level routing fields replaced by a routing_codes object”

At the invoice top level, both the flat cinN_value / cinN_scheme keys and the dedicated routing fields (dire, accounting_unit, managing_unit, processing_unit, proponent_unit, nadby, naddp, nadiv, nadpr, nadud) are no longer accepted. Use the invoice-level routing_codes object instead — it carries the invoice's own routing codes (e.g. buyer's DIR3 / Chorus Pro service codes), independent of the contact's.

SlotDIR3 (scheme 8014)DIRe (8015)EDI/GLN (88)
cin1accounting_unitnadby
cin2managing_unitnaddp
cin3processing_unitnadiv
cin4proponent_unitnadpr
cin5direnadud
{
"invoice": {
"number": "F-2026-0001",
"accounting_unit": "ESR_MISSION_FACTURES_DEPLACEMENTS"
}
}

The applied codes are echoed back in a routing_codes object at the invoice top level of the response. Requests still using the old fields return 422 Unprocessable Entity.

Affected endpoints: POST /accounts/{account}/invoices, PUT /invoices/{id}


6. generate_tax_report now returns 201 Created

Section titled “6. generate_tax_report now returns 201 Created”

POST /invoices/{id}/generate_tax_report now returns 201 Created on success instead of 200 OK. The response body is unchanged. This only affects clients that check for an exact 200 status — clients checking for any 2xx success are unaffected.

Affected endpoints: POST /invoices/{id}/generate_tax_report


7. Invoice list number filter now matches by prefix

Section titled “7. Invoice list number filter now matches by prefix”

GET /accounts/{account}/invoices?number=... now performs a prefix match instead of an exact match. If your integration relies on the previous exact-match behavior, add a client-side check to filter out unwanted prefix matches, or switch to a more specific value.

GET /accounts/{account}/invoices?number=INV-2

now also returns INV-20, INV-21, … in addition to an invoice numbered exactly INV-2. A new series_code filter is available for exact-match filtering by series.

Affected endpoints: GET /accounts/{account}/invoices


The following fields have been added. They are backwards-compatible — your integration will continue to work without changes. However, if your code fails when it encounters unexpected JSON fields, update your response models to allow them.

Contact responses (GET /contacts/{id}, POST, PUT, DELETE):

  • parent_idnull for top-level contacts, the parent contact's ID for offices

Invoice list (GET /accounts/{account}/invoices):

  • taxable_base — invoice subtotal before taxes
  • tax_amount — total tax amount across all tax breakdown lines

Invoice responses (mark_as, create, update):

  • refuse_reason_code — DGFiP-defined structured reason code for process 210 refusals (accepted on POST /invoices/{id}/mark_as alongside reason)

Account responses (GET /accounts/{account}, GET /accounts):

  • unit_code_formatinternal (default) or unece_rec20; see the opt-in feature below for the effect on invoice line unit

Received invoice webhooks — subscribe to received_invoice.created and received_invoice.state_change to be notified when received invoices are created or change state.

Offices via POST /accounts — create an office (subsidiary) directly under a parent account by passing parent_id; standard required fields become optional and the office inherits the group's integration.

issue_after_import — pass this flag on invoice creation or import to synchronously transition the invoice to state=issued inline with the request, instead of relying on the send flow.

unit_code_format account preference — set to unece_rec20 to exchange invoice line unit as a UN/ECE Recommendation 20 string code (e.g. "HUR") instead of B2Brouter's internal integer id. The default internal keeps existing integrations unaffected.

Per-invoice apply_taxes_per_line (premium accounts) — override the account-level VAT rounding mode for a single invoice, useful when a public administration recomputes totals with a different rounding rule. See the invoice calculation guide.


Use this checklist before switching to v2026-06-26 in production.

Contacts and offices:

  • If you set cin_value / cin_scheme when creating or updating offices, verify the value is unique within the account (duplicates now return 422)
  • Update any code reading parent_id on contact responses if you need to distinguish offices from top-level contacts

Invoice creation with inline contact:

  • Move any cinN_value / cinN_scheme keys inside invoice.contact into invoice.contact.routing_codes
  • Move any invoice top-level cinN_value/cinN_scheme or dedicated fields (dire, accounting_unit, etc.) into the invoice-level routing_codes object
  • Decide whether you need update_contact: true to preserve the previous descriptive-data overwrite behavior
  • Test: create an invoice with an inline contact whose descriptive data differs from an existing contact, and confirm you handle the 422 data_mismatch response (or pass update_contact: true)

Tax reports:

  • Update any status check on POST /invoices/{id}/generate_tax_report that expects exactly 200 OK

Invoice list filtering:

  • Review any use of the number filter that depends on exact-match behavior; switch to series_code if you need exact matching by series

French directory lookups:

  • Handle 202 Accepted with polling_url and retry
  • Handle 424 Failed Dependency with Retry-After as a transient error, not a 404
  • Handle 422 Unprocessable Entity with code invalid_identifier

Switch to the new version header in your sandbox or staging environment first:

X-B2B-API-Version: 2026-06-26

You can set this header per-request, so you can test against sandbox (or staging) without touching your production configuration. Once you have validated the checklist above, update the version in your production API key settings or request headers.