Antes de este documento conviene leer la introducción:
explica por qué el contrato usa el vocabulario de FIRE y no el del ente tributario.
1. Autenticación
Son dos direcciones distintas y conviene no confundirlas.1.1 Cómo se consume el Fiscal Gateway de FIRE
API key, y nada más. Es el único mecanismo, hoy y siempre. No hay OAuth, ni JWT de usuario, ni sesión.
La key es account + vendor scoped y debe tener el scope
fiscal:write. El tenant se deriva
de la key, nunca del cuerpo: un payload puede mentir, una credencial no. Por eso el request
no lleva accountId ni vendorId.
La Idempotency-Key la genera quien llama y la reutiliza en cada reintento de la misma
venta. Generarla nosotros sería idempotencia decorativa: cada intento traería una llave
distinta y no habría nada que comparar.
No es lo que evita el documento duplicado — eso lo hace el
orderCode, la llave natural
(país + orderCode + operación). Un reintento con el mismo orderCode devuelve el mismo
documento aunque el canal regenere la llave, que es el error de implementación más común.Lo que la llave aporta es detectar que se reusó para otra venta: misma llave con cuerpo
distinto responde 409 en vez de numerar.1.2 Cómo consumimos al proveedor
También API key. Mismo mecanismo en las dos direcciones: el proveedor entrega una key por ambiente y FIRE la envía enx-api-key en cada llamada. No hay OAuth, ni endpoint de token, ni
audiencia que configurar.
La key se guarda cifrada en la configuración de la cuenta y no sale de la instancia. Es
write-only en el backoffice: se carga, nunca se muestra.
El proveedor debe poder rotarla sin cortar el servicio — aceptando la key anterior durante
una ventana de solapamiento. Sin eso, rotar significa una interrupción de facturación
coordinada, y en la práctica se traduce en keys que no se rotan nunca.
2. Endpoint — uno por país
{country} es el código ISO 3166-1 alpha-2 en minúsculas.
baseUrl y una credencial; las rutas se
derivan del país. FIRE resuelve el país antes de llamar —sale de la tienda— así que no hay
nada que descubrir ni que configurar por separado.
Por qué por país y no una ruta única. Una integración fiscal se construye y se certifica
contra un ente, y las normativas cambian por país. Con una ruta por país, un cambio en
Ecuador es una versión del endpoint de Ecuador: no toca Colombia, no obliga a versionar
todo, y no puede romperlo. El versionado queda con la misma granularidad que el cambio.También hace innecesario declarar capacidades: las rutas que existen son los países que
atendés.
3. Request
3.1 Numerar una venta
Un solo request, igual para todos los países. No cambia de forma según el ente: lo que cambia es qué usa cada proveedor. El de Ecuador arma la clave de acceso con la fecha, el emisor y el correlativo, y no mira los importes. El de Colombia los necesita todos, porque su identificador es un hash de la factura. Los dos ejemplos de abajo son el mismo contrato: mismos campos, mismo orden. Lo único que cambia son los valores.- Ecuador (EC)
- Colombia (CO)
storeFiscalConfig.metadata va vacío: en Ecuador el establecimiento y el punto de emisión
los resolvés vos contra tu catálogo, a partir de store.code y device.uid.3.2 Anular
Idéntico, con"operation": "CANCEL". Mismos campos, client y totals incluidos: la
anulación emite un documento nuevo y necesita los mismos datos que la emisión.
No enviamos referencia al documento original. El proveedor resuelve qué compensa buscando
la emisión del mismo orderCode — que es su propia llave de idempotencia, ya indexada.
3.3 Campos
Los campos vacíos se omiten. Nunca enviamos
"". Un campo ausente significa “no
configurado”; un string vacío no debe interpretarse como valor válido.
3.4 Los dos metadata
Hay dos bloques llave-valor, en niveles distintos y con propósitos distintos:
store.storeFiscalConfig.metadata— atributos de la tienda, constantes. Es donde viven los datos que el proveedor necesita y que no son parte del dominio compartido: por ejemplo la clave técnica y el rango de numeración que la DIAN entrega con la resolución. Se configuran una vez en el backoffice y viajan en cada llamada de esa tienda.metadata(raíz) — atributos de la venta, variables: los que cambian en cada transacción y que algún régimen exige declarar.
Así se carga el de la tienda

Arriba: el NIT del emisor, la clave técnica y el rango de facturación como grupo anidado.

Más abajo, en la misma pantalla: el rango de notas crédito y la vista previa del JSON que se va a enviar.
store.storeFiscalConfig.metadata. En la captura, esa tienda va a mandarte:
Por qué es llave-valor y no un formulario con campos fijos.Los datos que un proveedor necesita son del régimen de su país, no del dominio que
compartimos: una clave técnica de la DIAN, un rango de numeración, lo que venga después.
Tipificarlos en nuestra pantalla significaría que sumar un país —o que un ente agregue un
requisito— obligue a desplegar el backoffice. Con llave-valor, es cargar una fila.Los valores pueden ser texto o un grupo anidado, sin límite de profundidad. Por eso un
rango entero —prefijo, desde, hasta, resolución, vigencia— entra como un bloque, en vez de
cinco claves con el prefijo pegado al nombre.El ejemplo lleva dos rangos porque son dos cosas distintas: el de facturación y el de
notas crédito. Una tienda que solo tenga el primero puede emitir pero no anular.
Ese mismo bloque viaja también en los eventos de la orden, no solo en la numeración.
Aparece como El detalle campo por campo está en
data.store.storeFiscalConfig —con su metadata adentro— en:order.opened ·
order.completed ·
order.cancelled ·
order.invoiced ·
order.reversedEs el mismo dato en los dos caminos, y a propósito: quien consume eventos para conciliar ve
con qué configuración se emitió esa venta, sin tener que preguntárselo a nadie.order.completed → Datos fiscales.Los nombres de las claves los definís vos, no nosotros. El campo es libre: quien
configura la tienda escribe la clave que tu integración espera. Por eso conviene que
publiques cuáles necesitás y con qué nombre exacto — un
claveTecnica contra un
clave_tecnica es un dato que llega y que no vas a encontrar.FIRE no valida esos nombres a propósito: el vocabulario es del régimen y del proveedor, y
tipificarlo de nuestro lado significaría desplegar el backoffice cada vez que un país nuevo
pide un dato distinto.metadata puede pisar un campo de dominio. Si aparece una clave
storeCode o country dentro de metadata, debe ignorarse. De lo contrario el llave-valor
se convierte en la puerta trasera por la que se redefine el contrato.
3.5 client y totals — lo que viene de la venta
Estos dos bloques son los mismos que el punto de venta arma para inyectar la orden, y
viajan tal cual: FIRE no los recorta ni los renombra. Por eso no llevan vocabulario fiscal —
llevan el del negocio.
Van siempre, en todos los países. Lo que cambia es quién los usa: el proveedor de Ecuador
los ignora, porque la clave de acceso se arma con fecha, emisor y correlativo. El de Colombia
los necesita enteros, porque el CUFE es un hash de la factura: entran los importes, cada
impuesto por separado, la fecha con hora y el documento del adquiriente.
totals — lo cobrado
- Ecuador (EC)
- Colombia (CO)
Moneda El SRI no los mira: la clave de acceso se arma con fecha, emisor y secuencial. Viajan
igual, por si los necesitás para tu propio control.
USD. Hoy, un solo impuesto: IVA al 15%.client — quién compró
Llega entero, tal como lo armó el punto de venta. No es un subconjunto fiscal: trae también
datos que no le sirven a ningún ente.
govIdType sale de un catálogo cerrado. Estos son todos los valores que FIRE emite, y
no van a llegar otros:Traducirlos al código que exige tu ente es parte de tu implementación, igual que el resto de
la traducción al idioma del régimen.
Leé solo lo fiscal y descartá el resto. Lo que necesita un régimen está en
govIdType,
govIdNumber, name y —para empresas— billingInformation.businessName y
additionalInfo.fiscal. El uid, el correo y el teléfono son del negocio, no del ente.govIdType y govIdNumber aparecen dos veces: en la raíz y en billingInformation.
Cuando difieren, manda el de facturación — es el documento que el cliente pidió para su
factura.- Ecuador (EC)
- Colombia (CO)
FINAL_CONSUMER y ceros. Traducirlo a lo que el SRI espera
en el comprobante es parte de tu implementación.taxes trae siempre el desglose, un elemento por impuesto, cada uno con name, base,
rate y amount. El monto por impuesto es el dato que importa: amount es lo que declarás
al ente, y taxValue de arriba es apenas su suma.Recorré el array, no leas taxes[0]. Hoy en Ecuador y Colombia es un solo IVA, pero un
régimen puede declarar varios tributos por comprobante y el array los trae todos, sin que el
contrato cambie.Los dos bloques viajan en las dos operaciones,
INVOICE y CANCEL. Es un solo request
canónico y no se recorta por operación.No es simetría por prolijidad: la anulación produce un documento nuevo. Una nota crédito
colombiana tiene su propio identificador calculado sobre los importes y el adquiriente, así
que sin client y totals no habría con qué armarlo.Lo que no cambia es el alcance: la anulación es total. No existen anulaciones parciales
en ningún país que atendemos, así que los importes que llegan son los de la venta completa, y
qué documento compensás lo resolvés por el orderCode.4. Idempotencia
La llave escountry + orderCode + operation.
Repetir esa terna debe devolver el mismo documento con "reused": true, sin consumir otro
secuencial. Es la misma llave que usa FIRE de su lado, para que un choque se detecte en ambos
extremos a la vez.
Esto asume que el orderCode es único por cuenta y país. Es la misma suposición que ya hacen
las dos partes.
5. Respuesta exitosa
La respuesta tiene dos partes con reglas distintas:- El sobre — idéntico en todos los países. Es con lo que FIRE opera: decide si reintentar, si hubo comprobante, qué error reportar.
document— el idioma fiscal del país. Cada uno manda lo que existe en su régimen, con los nombres de su ente, y nada más.
document es lo único que cambia, y por eso
acá va elidido: su contenido está en 5.2, con una
sección por país. Si estás implementando Ecuador, el bloque que te toca es el de Ecuador y
ninguno más.
country viaja aunque esté en la ruta. No es redundancia: FIRE compara country y
orderCode contra lo que pidió y descarta la respuesta si no coinciden. Es lo que evita
imprimir el documento de otra venta cuando hay un cruce de respuestas o un proxy con caché.5.1 status
Dos valores, uno por operación:
No hay más estados, y es deliberado. Este endpoint produce la representación fiscal —los
identificadores para imprimir— y nada más. El envío al ente y su autorización ocurren después,
del lado del proveedor, y su desenlace llega por el callback. Modelar aquí estados de
autorización mezcla dos ciclos de vida distintos.
status es casi un eco de operation, y existe por un solo caso: cuando la operación no
produce documento. La cancelación en Brasil es un evento de cancelamento, no un documento
nuevo, así que la respuesta llega con document: null y sin graphic. Ahí status es lo único
que afirma que la operación se completó, en vez de dejar una respuesta exitosa y vacía que no
se puede distinguir de un error silencioso.
En particular:
- No existe
PENDING. Inmediatamente después de numerar, el documento siempre está pendiente de autorización: es la condición normal, no un estado que informar. La caja imprime con los identificadores que acaba de recibir. - No existe
REJECTED. Si no se pudo numerar es un error: HTTP no-2xx con el bloquefailure. Un rechazo con200 OKy el motivo escondido en un campo es un contrato donde alguien no valida y cree que numeró. - No existe
REUSED. Eso esreused: true, un booleano ortogonal. Se puede tenerINVOICEDconreused: true— un reintento idempotente de una venta ya numerada — y esa distinción se pierde siREUSEDfuera un estado.
5.2 document — el documento numerado
Acá se habla el idioma fiscal del país. Es el único bloque de la respuesta que cambia entre
países, y cambia entero: los nombres son los del ente, no una traducción nuestra.
Un país manda lo que existe en su régimen y nada más. Un campo que no aplica no viaja en
null: sencillamente no está.
- Ecuador (EC) — SRI
- Colombia (CO) — DIAN
Las tres piezas siguen viajando igual, como las nombra el SRI y sin concatenarlas en una
serie de 6 dígitos: se usan para conciliar, no para componer el número.El puntoEmision que devuelvas es el que quedó emitido, que puede no ser el que se pidió en
device.uid a través de tu catálogo. Lo que vale es siempre lo que vuelve, nunca lo que se
mandó.Por qué el bloque es del país y no un modelo común
Se evaluó un bloque plano con nombres por rol —accessKey, sequential, controlNumber— y se
descartó. El costo no era un campo nulo: era que cada país nuevo agregaba un campo que todos
los demás cargaban vacío para siempre, y que el mismo identificador tenía dos nombres según
entrara por el prekey o por el callback.
Este es además el mismo mecanismo que ya usa el callback de resultado, que valida por país
sobre el countryCode de la raíz. Un solo patrón en las dos direcciones.
Lo que document NO lleva
No lleva documentType. Con qué instrumento fiscal se materializa la operación —una nota
de crédito en Ecuador, un evento de cancelamento en Brasil— es asunto del país y del proveedor.
Lo que se pidió ya lo dice status.
No lleva lo que asigna el ente al autorizar — el numeroAutorizacion del SRI, el
protocolo de la SEFAZ. Eso llega por el callback; declararlo acá lo condena a venir siempre
en null.
No lleva authorizationMode ni issuedAt. Son comunes a todos los países y viven en la
raíz de la respuesta.
5.3 graphic — lo imprimible
Llave-valor, con las claves que cada país necesite. {} o null cuando la operación no
produce nada que imprimir — la cancelación en Brasil, por ejemplo.
qr, barcode, ted — no de la
simbología del momento.
Este bloque es el único de la respuesta que el proveedor aporta y no se puede derivar, por un
caso concreto: el QR de la NFC-e brasileña es una URL firmada con un hash que solo puede
construir el emisor. No se deriva de la chave. Si no llega, no hay QR.
En Ecuador el valor va a coincidir con document.claveAcceso. Esa redundancia es deliberada:
la alternativa es que el punto de venta sepa que en Ecuador se codifica la clave, en Brasil la
URL y en Chile el TED.
No incluye pdfUrl, xmlUrl ni lookupUrl. Los dos primeros solo existen después de que
el ente autorizó y llegan por el callback; declararlos acá los condena a venir siempre en null,
y un campo que siempre es nulo enseña a ignorarlo. lookupUrl es una constante por país y
ambiente, no un dato del documento.
5.4 provider y metadata — los dos bloques del proveedor
Son dos bloques distintos y no intercambiables, y FIRE los guarda en dos columnas
distintas. La diferencia es si el campo tiene forma acordada o no.
provider — identidad, con forma
Los tres van siempre presentes, con
null cuando no aplica. null dice “no tengo”; ausente
obliga a distinguir dos formas de lo mismo.
reference no es nuestra Idempotency-Key. Esa la mandamos nosotros y el proveedor la
ecoa por otro lado. Esta es del proveedor, y es la que sirve cuando hay que escalarle un caso:
sin ella, la única forma de que encuentre la operación es que busque por orderCode en el
rango de fechas correcto.metadata — la bolsa opaca
{}. Lo que no puede es cambiar entre una emisión y su reintento idempotente: un
201 que trae la bolsa poblada y un 200 REUSED que la trae vacía describen la misma operación
de dos maneras distintas, y quien lea el evento va a ver que “cambió” algo que no cambió.
6. Respuesta con error
Viaja con código HTTP no-2xx —422 para un problema de configuración o de datos, 5xx
para uno transitorio. Nunca con 200.
retryable es obligatorio y lo decide el proveedor. Es lo que nos permite distinguir un
problema de configuración —que no mejora reintentando— de uno transitorio. Sin ese campo hay
que adivinar por el código HTTP, y adivinar mal significa reintentar en la caja mientras el
cliente espera, o abandonar una venta que se podía numerar.
failure.code debe ser un código estable y accionable, no un texto libre. Es lo que
permite construir alertas y documentación de soporte.
failure.message debe describir el problema real, no una generalidad. "identidad fiscal de tienda no configurada: EC / tienda K0050" permite arreglar; "documento rechazado" obliga
a abrir un ticket.
7. Reglas de la integración
Lo que enviamos manda. Si el catálogo del proveedor tiene una identidad fiscal distinta de la que enviamos, debe rechazar con error explícito, nunca emitir con la suya. Un comprobante emitido bajo el contribuyente equivocado no se corrige con un deploy.metadata es opaco en ambos sentidos y no puede pisar campos de dominio.
Los códigos del ente no viajan en el contrato. Nada de documentTypeCode: "01",
tipoComprobante ni equivalentes. El proveedor los deriva de operation + country.
El contrato es versionado. Un cambio rompiente requiere una versión nueva del endpoint y
una ventana de convivencia; no se cambia el significado de un campo existente.
8. El callback de resultado
El callback de resultado sigue como está. Se correlaciona pororderCode + operation,
con el tenant derivado de la API key con la que se autentica. Debe incluir la operación: sin
ella, una factura y su anulación sobre la misma orden son indistinguibles.
Devolver identificadores adicionales en el callback es opcional y bienvenido, pero no requerido.
Usá los mismos nombres que en la numeración. El callback de Colombia declara
cufe,
prefijo, numeroDian, numeroComprobante, qrCode y ambiente — exactamente los de
5.2. Es el mismo documento contado dos veces, y si los nombres divergen,
conciliar los dos caminos deja de ser comparar campos y pasa a ser traducir, que es donde se
cuelan los errores.
