v2026-06-26
Changelog
Selecciona esta versión enviando X-B2B-API-Version: 2026-06-26 en las cabeceras de tu petición.
Esta versión implementa cambios incompatibles. Cada cambio incompatible está marcado con la etiqueta Breaking.
Resumen de los cambios
Sección titulada «Resumen de los cambios»| Cambio | Área |
|---|---|
| Eventos de webhook para facturas recibidas | Webhooks |
Crear oficinas via POST /accounts con parent_id | Cuentas |
| CIN independiente para oficinas de contacto 🚨 Breaking | Contactos |
parent_id en las respuestas de contacto | Contactos |
| Validación más estricta de contactos al crear facturas 🚨 Breaking | Facturas |
refuse_reason_code de DGFiP en mark_as y en las respuestas de factura | Facturas |
issue_after_import al crear e importar facturas | Facturas |
Consulta al Annuaire francés en /directory/fr 🚨 Breaking | Directorio |
routing_codes del contacto en línea al crear facturas 🚨 Breaking | Facturas |
routing_codes a nivel de factura al crear facturas 🚨 Breaking | Facturas |
| Base imponible e importe de impuesto en el listado de facturas | Facturas |
Filtros del listado de facturas: number ahora coincide por prefijo y series_code | Facturas |
| Generar informe fiscal retorna 201 Created 🚨 Breaking | Facturas |
Preferencia unit_code_format de la cuenta | Cuentas |
apply_taxes_per_line por factura | Facturas |
Añadido
Sección titulada «Añadido»Eventos de webhook para facturas recibidas
Sección titulada «Eventos de webhook para facturas recibidas»Dos nuevos eventos de webhook para facturas recibidas:
received_invoice.created— se activa cuando se crea una nueva factura recibida en la plataforma (p. ej. importada o subida).received_invoice.state_change— se activa cuando una factura recibida transiciona a un nuevo estado.
Suscríbete a estos eventos al configurar un webhook:
{ "web_hook": { "url": "https://example.com/hooks", "events": ["received_invoice.created", "received_invoice.state_change"] }}Payload para received_invoice.created:
{ "invoice_id": 12345, "account_id": 42, "state": "received"}Payload para received_invoice.state_change:
{ "invoice_id": 12345, "event_id": 67890, "state": "accepted", "notes": null}Crear oficinas via POST /accounts con parent_id
Sección titulada «Crear oficinas via POST /accounts con parent_id»POST /accounts ahora acepta un campo opcional parent_id. Cuando se proporciona, la cuenta se crea como una oficina (filial) bajo la cuenta padre indicada.
Cuando parent_id está establecido:
- El padre debe ser una cuenta de nivel superior dentro del mismo grupo (no puede ser ella misma una oficina).
- Los campos obligatorios habituales (
name,postalcode,email,address,city,province,phone,tin_value) pasan a ser opcionales. - La cuenta creada hereda la integración del grupo y se retorna con
parent_iden la respuesta.
Retorna 422 Unprocessable Entity con código parameter_invalid (parámetro parent_id) si:
- La cuenta padre no se encuentra o pertenece a un grupo distinto.
- El padre es a su vez una oficina.
Ejemplo de petición:
POST /accounts{ "account": { "parent_id": 12345, "name": "Paris Establishment", "address": "1 Rue de la Paix", "city": "Paris", "postalcode": "75001", "province": "Ile-de-France", "email": "paris@example.com" }}Ejemplo de respuesta:
{ "account": { "id": 67890, "parent_id": 12345, "name": "Paris Establishment", ... }}Notas:
tin_value/tin_schemeno se pueden establecer en una oficina — siempre se heredan de la cuenta padre. Enviarlos retorna422 parameter_cannot_change(parámetrotin_value).cin_value/cin_schemese almacenan de forma independiente en la oficina. Si no se establecen, la oficina hereda el CIN del padre en las respuestas de lectura.
parent_id en las respuestas de contacto
Sección titulada «parent_id en las respuestas de contacto»Todas las respuestas de un único contacto ahora incluyen parent_id: GET /contacts/{id}, POST /accounts/{account}/contacts, PUT /contacts/{id} y DELETE /contacts/{id}.
- Para los contactos de nivel superior,
parent_idesnull. - Para las oficinas,
parent_idcontiene el ID del contacto padre.
refuse_reason_code de DGFiP en mark_as y en las respuestas de factura
Sección titulada «refuse_reason_code de DGFiP en mark_as y en las respuestas de factura»POST /invoices/{id}/mark_as ahora acepta un parámetro opcional reason_code al transicionar al estado refused. Este campo lleva un código de motivo estructurado definido por DGFiP para los rechazos del proceso 210.
Valores aceptados:
ADR_ERR, REF_CT_ABSENT, CMD_ERR, TX_TVA_ERR, MONTANTTOTAL_ERR, CALCUL_ERR, DOUBLON, NON_CONFORME, DEST_ERR, TRANSAC_INC, EMMET_INC, CONTRAT_TERM, DOUBLE_FACT, AUTRE
Ejemplo de petición:
POST /invoices/{id}/mark_as{ "state": "refused", "reason": "Incorrect total amount", "reason_code": "MONTANTTOTAL_ERR"}Ejemplo de respuesta (extracto):
{ "invoice": { "id": 12345, "state": "refused", "refuse_reason": "Incorrect total amount", "refuse_reason_code": "MONTANTTOTAL_ERR", "..." }}issue_after_import al crear e importar facturas
Sección titulada «issue_after_import al crear e importar facturas»POST /accounts/{account}/invoices y POST /accounts/{account}/invoices/import ahora aceptan un indicador opcional issue_after_import. Cuando es true y la factura pasa las validaciones:
- La factura transiciona a
state=issued(esquina c1) de forma síncrona, dentro de la misma petición. - Si la cuenta tiene activada una configuración de informe fiscal
before_send(Verifactu, Ksef, Zatca) conauto_generateactivado y la factura cualifica, el informe fiscal se genera en línea y se expone viatax_report_idsen la respuesta. Cuandoauto_generateestá desactivado, la factura se emite igualmente pero no se crea ningún informe — coherente con el flujo de envío, esos informes se gestionan a través de la API de Tax Reports. - Las autoridades que informan después del envío (p. ej. SII, SDI) no se activan al emitir; sus informes se crean cuando la factura se envía o se marca como enviada.
- No se encola ningún job de envío en segundo plano para la factura; el estado a nivel de transporte (c2) permanece intacto. Los informes fiscales que se envían automáticamente (p. ej. Ksef) siguen encolando su propio job de envío.
Mientras la factura permanece en state=issued es de solo lectura: no se puede actualizar, eliminar ni revertir a new hasta que se envía o se descarga.
Si tanto send_after_import como issue_after_import son true, send_after_import gana — enviar ya emite la factura. Se ignora para documentos que no sean facturas.
El endpoint de importación masiva POST /accounts/{account}/invoices/imports acepta el mismo indicador issue_after_import (como campo de formulario multipart). Se aplica por archivo: cada factura importada que cualifica se emite; un archivo que no se puede emitir, o cuya emisión falla, se reporta en ese elemento sin abortar el lote.
Ejemplo de petición:
POST /accounts/{account}/invoices{ "issue_after_import": true, "invoice": { "number": "F-2026-0001", "..." }}Base imponible e importe de impuesto en el listado de facturas
Sección titulada «Base imponible e importe de impuesto en el listado de facturas»GET /accounts/{account}/invoices ahora retorna dos campos adicionales por factura:
taxable_base— el subtotal de la factura antes de impuestos (float, misma moneda quecurrency).tax_amount— el importe total de impuestos, calculado como la suma de todos los desgloses de impuesto (float).
Ejemplo de respuesta (parcial):
{ "invoices": [ { "id": 12345, "number": "INV-2025-001", "total": 121.0, "taxable_base": 100.0, "tax_amount": 21.0, "currency": "EUR" } ]}Preferencia unit_code_format de la cuenta
Sección titulada «Preferencia unit_code_format de la cuenta»Las cuentas ahora exponen un campo unit_code_format que controla la lista de códigos utilizada para los códigos de unidad de las líneas de factura al leer y escribir facturas a través de la API.
internal(por defecto) — la propia lista de códigos de unidad enteros de B2Brouter.unece_rec20— códigos de la Recomendación 20 de UN/ECE (p. ej.HUR,KGM,H87).
Léelo en GET /accounts/{account} y establécelo en PUT /accounts/{account}:
{ "account": { "unit_code_format": "unece_rec20" }}Cuando se establece a unece_rec20, el campo unit de la línea de factura cambia de tipo: en lugar del id entero por defecto, se intercambia como un código UNECERec20 en cadena (p. ej. "HUR"). Se retorna así en las lecturas de factura — GET /invoices/{id} y las respuestas de create/update — y se acepta como código en las escrituras (creación e importación de facturas); los códigos desconocidos recaen en other. Las integraciones que interpretan unit como entero deben gestionar la forma en cadena para estas cuentas.
El valor por defecto internal mantiene el id entero, de modo que las integraciones existentes no se ven afectadas a menos que lo activen.
apply_taxes_per_line por factura
Sección titulada «apply_taxes_per_line por factura»Nuevo campo apply_taxes_per_line (booleano, anulable) en las facturas, tanto escribible como retornado en la respuesta. Sobrescribe, por factura, la configuración a nivel de cuenta del mismo nombre que controla el redondeo de IVA:
true— los impuestos se calculan por línea (cada línea se redondea y luego se suman).false— los impuestos se calculan por factura (la base imponible se suma por tipo, y se redondea una sola vez).- omitido /
null— hereda la configuraciónapply_taxes_per_linede la cuenta (el comportamiento anterior).
La sobrescritura solo se respeta para cuentas premium. Esto permite alinear el modo de redondeo con administraciones públicas que recalculan los totales y rechazan discrepancias, sin cambiar el valor por defecto a nivel de cuenta. Consulta la guía de cálculo de facturas para ejemplos trabajados.
Endpoints afectados:
GET /invoices/{id}— retorna elapply_taxes_per_lineefectivo.POST /accounts/{account}/invoices— acepta y retornaapply_taxes_per_line.PATCH /invoices/{id}— acepta y retornaapply_taxes_per_line.
Modificado
Sección titulada «Modificado»CIN independiente para oficinas de contacto
Sección titulada «CIN independiente para oficinas de contacto»🚨 Breaking
POST /accounts/{account}/contacts y PUT /contacts/{id} ahora almacenan cin_value y cin_scheme de forma independiente en las oficinas. Anteriormente, el CIN siempre se heredaba del contacto padre.
Comportamiento:
- Si se proporcionan
cin_value/cin_scheme, se almacenan en la oficina y se retornan en las respuestas. - Si se omiten (o se borran con
cin_value: ""), la oficina hereda elcin_valuedel contacto padre. tin_value/tin_schemese siguen heredando del padre y no se pueden establecer de forma independiente.- Se aplica la unicidad del CIN dentro de la cuenta: enviar un
cin_valueya asignado a otro contacto de la misma cuenta retorna422 Unprocessable Entity.
Endpoints afectados:
POST /accounts/{account}/contacts—cin_value/cin_schemeahora se almacenan cuandoparent_idestá establecido.PUT /contacts/{id}—cin_value/cin_schemeahora se almacenan en las oficinas.
Ejemplo: Crear una oficina con su propio CIN
POST /accounts/{account_id}/contacts{ "contact": { "parent_id": 12345, "name": "Branch Office", "cin_value": "1234567890", "cin_scheme": 88 }}Respuesta:
{ "contact": { "id": 67890, "parent_id": 12345, "name": "Branch Office", "cin_value": "1234567890", "cin_scheme": "88", "..." }}Validación más estricta de contactos al crear facturas
Sección titulada «Validación más estricta de contactos al crear facturas»🚨 Breaking
Cuando se proporciona un objeto contact al crear una factura, la API lo compara con los contactos existentes por tin_value o cin_value:
- Si todos los datos coinciden con un contacto existente, la factura se crea y se enlaza con él.
- Si los identificadores de enrutamiento difieren (
cin_value,cin_scheme,routing_codes,pin_value,recipient_code), se crea una oficina automáticamente. - Si solo difieren campos descriptivos (
address,city,postalcode, etc.), la petición retorna422 Unprocessable Entitycon códigodata_mismatch. Pasaupdate_contact: true(campo de la petición a nivel superior, por defectofalse) para fusionar los datos descriptivos con el contacto coincidente en su lugar — los identificadores de enrutamiento y fiscales nunca se sobrescriben de esta manera. - Si no se encuentra ningún contacto coincidente, se crea un contacto nuevo. Cuando coincide con una empresa publicada en el directorio de B2Brouter, se enlaza con ella: si los datos descriptivos de la factura coinciden con una de las oficinas registradas de esa empresa, el contacto se enlaza con esa oficina; si difiere, el contacto mantiene los datos de la factura (enlazado al directorio pero no verificado contra él).
update_contactse ignora para los documentos recibidos: una factura entrante nunca reescribe los datos de un contacto existente.
Nota: el endpoint de contactos es la forma recomendada de crear y mantener contactos y sus oficinas. Para enlazar una factura con un contacto existente sin ninguna lógica de coincidencia, usa
contact_iddirectamente.
Consulta al Annuaire francés en /directory/fr
Sección titulada «Consulta al Annuaire francés en /directory/fr»🚨 Breaking
GET /directory/fr/{scheme}/{id} ahora consulta el Annuaire francés de la PPF (API REST de la DGFiP) cuando el identificador es un SIREN (9 dígitos, esquema 0002), un SIRET (14 dígitos, esquema 0009), o una dirección electrónica FRCTC (esquema 0225, formato SIREN, SIREN_SIRET, SIREN_SIRET_CodeRoutage o SIREN_Suffix).
El identificador debe ser válido para su esquema — la longitud y el checksum se validan para cada esquema, no solo los franceses. Un identificador que el validador del esquema rechaza retorna 422 Unprocessable Entity con código invalid_identifier (esto incluye un SIREN/SIRET de longitud incorrecta, un checksum fallido, o una dirección FRCTC con un SIREN/SIRET no válido). Los esquemas sin validador se aceptan y se consultan como antes.
Para el esquema 0225 el valor se utiliza tal cual como identificador de direccionamiento del Annuaire, y la empresa resuelta se clasifica por su identificador legal subyacente: un SIREN solo resuelve a cin_scheme 0002, un SIREN_SIRET a 0009 (SIRET), con la dirección FRCTC completa mantenida como pin_scheme 0225; un code routage se expone como código de enrutamiento cin1 (esquema 0224). Esto significa que una consulta 0225 y una posterior consulta 0009/0002 de la misma empresa retornan la misma entrada de directorio.
La consulta es asíncrona para mantener los tiempos de respuesta en milisegundos:
- Si la empresa ya es conocida en el directorio de B2Brouter, la respuesta es
200 OKcon los datos de la empresa. - Si aún no es conocida, la respuesta es
202 Acceptedcon unpolling_url. Un job en segundo plano consulta el Annuaire y persiste el resultado. Reintenta la petición al cabo de unos segundos — una vez los datos estén disponibles, la petición retorna200 OK. - Cuando una entrada de directorio conocida no se ha comprobado en las últimas 24 horas (o nunca se ha enriquecido desde el Annuaire), la respuesta sigue siendo
200 OKinmediatamente y se activa una actualización en segundo plano (stale-while-revalidate). - Después de un
404 Not Founden un SIREN/SIRET, las peticiones posteriores para el mismo identificador retornan404durante 15 minutos sin volver a consultar el Annuaire. Reintenta después de ese periodo si crees que el identificador se ha registrado desde entonces. - Si la consulta falla porque un servicio upstream (Annuaire, Peppol) no está disponible, la respuesta es
424 Failed Dependencycon código de errorupstream_unavailabley una cabeceraRetry-After— reintenta después del retraso indicado. Un fallo upstream transitorio nunca se convierte en un404.
La respuesta incluye un nuevo campo information_flags (array de cadenas) que lleva el estado de registro en el Annuaire, alineado con la especificación CTC de Peppol:
| Indicador | Significado |
|---|---|
FR_ASSUJETTI_ACTIVE | El identificador está presente y activo en el Annuaire |
FR_ASSUJETTI_INACTIVE | El identificador está presente en el Annuaire pero inactivo |
FR_ASSUJETTI_UNKNOWN | La empresa es conocida por B2Brouter pero ausente del Annuaire |
Se establece exactamente uno de estos indicadores por empresa. Cuando una empresa ya conocida por B2Brouter (p. ej. via Peppol) está ausente del Annuaire, se retorna con FR_ASSUJETTI_UNKNOWN para que la ausencia sea visible. Un identificador desconocido para B2Brouter y ausente del Annuaire resuelve a 404 Not Found (ver la nota de caché de negación anterior).
Las unidades subordinadas (oficinas SIRET bajo un SIREN) todavía no se exponen en la respuesta.
Ejemplo: empresa aún no conocida — primera petición
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'{ "status": "processing", "message": "Querying French PPF Annuaire. Please retry this request.", "polling_url": "https://app-staging.b2brouter.net/api/v1/directory/fr/0002/123456789"}Estado HTTP: 202 Accepted.
Ejemplo: la misma petición unos segundos después
{ "name": "SIREN 123456789", "country": "fr", "information_flags": ["FR_ASSUJETTI_ACTIVE"], ...}Estado HTTP: 200 OK.
Endpoints afectados:
GET /directory/fr/{scheme}/{id}— comportamiento ampliado descrito anteriormente. Los países no franceses no se ven afectados.
routing_codes del contacto en línea al crear facturas
Sección titulada «routing_codes del contacto en línea al crear facturas»🚨 Breaking
El contact en línea en POST /accounts/{account}/invoices ahora acepta un objeto routing_codes — la misma forma utilizada por POST /accounts/{account}/contacts y retornada en las respuestas de contacto. Lleva hasta cinco Números de Identificación de Empresa adicionales (cin1_value/cin1_scheme … cin5_value/cin5_scheme) además del cin principal. Si se proporciona cinN_value, cinN_scheme es obligatorio (y viceversa). Estos códigos de enrutamiento también se retornan dentro de routing_codes en el objeto invoice.contact incrustado en la respuesta.
POST /accounts/{account_id}/invoices{ "invoice": { "contact": { "tin_value": "ESA13585625", "name": "Buyer Full Name AS", "routing_codes": { "cin1_value": "ESR_MISSION_FACTURES_DEPLACEMENTS", "cin1_scheme": "8017" } } }}Cambio incompatible: las claves planas cinN_value / cinN_scheme dentro de contact ya no se aceptan. Estas peticiones retornan 422 Unprocessable Entity. Envía los códigos de enrutamiento del contacto a través de contact.routing_codes en su lugar.
routing_codes a nivel de factura al crear facturas
Sección titulada «routing_codes a nivel de factura al crear facturas»🚨 Breaking
POST /accounts/{account}/invoices y PUT /invoices/{id} ahora aceptan un objeto routing_codes en el nivel superior de la factura — la misma forma cin1_value/cin1_scheme … cin5_value/cin5_scheme utilizada por contact.routing_codes. Lleva los códigos de enrutamiento propios de la factura (p. ej. los códigos de servicio DIR3 / Chorus Pro del comprador), independientes de los del contacto.
Los códigos aplicados se retornan en un objeto routing_codes en el nivel superior de la factura de la respuesta, de modo que un integrador puede verificar qué se ha almacenado.
POST /accounts/{account_id}/invoices{ "invoice": { "number": "F-2026-0001", "routing_codes": { "cin1_value": "ESR_MISSION_FACTURES_DEPLACEMENTS", "cin1_scheme": "8017" } }}Los campos de enrutamiento dedicados que versiones anteriores aceptaban en el nivel superior de la factura ya no se aceptan. Establece la ranura routing_codes equivalente en su lugar, según el esquema:
| Ranura | DIR3 (esquema 8014) | DIRe (8015) | EDI/GLN (88) |
|---|---|---|---|
| cin1 | accounting_unit | — | nadby |
| cin2 | managing_unit | — | naddp |
| cin3 | processing_unit | — | nadiv |
| cin4 | proponent_unit | — | nadpr |
| cin5 | — | dire | nadud |
Cambio incompatible: en el nivel superior de la factura, tanto las claves planas cinN_value / cinN_scheme como los campos de enrutamiento dedicados (dire, accounting_unit, managing_unit, processing_unit, proponent_unit, nadby, naddp, nadiv, nadpr, nadud) ya no se aceptan, tanto en POST /accounts/{account}/invoices como en PUT /invoices/{id}. Estas peticiones retornan 422 Unprocessable Entity. Envía los códigos de enrutamiento de la factura a través del objeto routing_codes de la factura en su lugar. Versiones anteriores de la API todavía aceptan estos campos.
Filtros del listado de facturas
Sección titulada «Filtros del listado de facturas»Cambios de filtrado en GET /accounts/{account}/invoices:
number ahora coincide por prefijo
El parámetro de consulta number ahora realiza una coincidencia de prefijo en lugar de una coincidencia exacta. Se retornan las facturas cuyo número empieza por el valor proporcionado.
GET /accounts/{account}/invoices?number=INV-2retorna INV-20, INV-21, … además de una factura numerada exactamente INV-2.
En versiones anteriores, el filtro
numberrequería el número de factura completo y retornaba solo una coincidencia exacta.
series_code
El parámetro de consulta series_code filtra facturas por código de serie mediante coincidencia exacta.
Generar informe fiscal retorna 201 Created
Sección titulada «Generar informe fiscal retorna 201 Created»🚨 Breaking
POST /invoices/{id}/generate_tax_report ahora retorna 201 Created en caso de éxito en lugar de 200 OK. El cuerpo de la respuesta no cambia.
Antes (≤ 2026-04-20): 200 OK
Después (≥ 2026-06-26): 201 Created
Este es un cambio incompatible menor para los clientes que comprueban un estado exacto de
200. Los clientes que comprueban cualquier éxito2xxno se ven afectados.