Skip to main content
Antes deste documento convém ler a introdução: ela explica por que o contrato usa o vocabulário do FIRE e não o do órgão fiscal.

1. Autenticação

São duas direções distintas e convém não confundi-las.

1.1 Como se consome o Fiscal Gateway do FIRE

API key, e nada mais. É o único mecanismo, hoje e sempre. Não há OAuth, nem JWT de usuário, nem sessão. A key é account + vendor scoped e precisa ter o scope fiscal:write. O tenant é derivado da key, nunca do corpo: um payload pode mentir, uma credencial não. Por isso a requisição não leva accountId nem vendorId. A Idempotency-Key é gerada por quem chama e reutilizada em cada retentativa da mesma venda. Gerá-la nós seria idempotência decorativa: cada tentativa traria uma chave diferente e não haveria nada a comparar.
Não é o que evita o documento duplicado — isso é papel do orderCode, a chave natural (país + orderCode + operação). Uma retentativa com o mesmo orderCode devolve o mesmo documento mesmo que o canal regenere a chave, que é o erro de implementação mais comum.O que a chave acrescenta é detectar que foi reusada para outra venda: mesma chave com corpo diferente responde 409 em vez de numerar.
Essa chave não trafega até o provedor. A chamada que sai em direção a ele leva apenas x-api-key e Content-Type. Se você implementar a deduplicação do lado do provedor, faça por orderCode.

1.2 Como consumimos o provedor

Também API key. Mesmo mecanismo nas duas direções: o provedor entrega uma key por ambiente e o FIRE a envia em x-api-key a cada chamada. Não há OAuth, nem endpoint de token, nem audiência para configurar. A key fica guardada cifrada na configuração da conta e não sai da instância. É write-only no backoffice: é carregada, nunca exibida.

2. Endpoint — um por país

{country} é o código ISO 3166-1 alpha-2 em minúsculas.
Uma única integração. O provedor recebe um baseUrl e uma credencial; as rotas derivam do país. O FIRE resolve o país antes de chamar —ele sai da loja— então não há nada a descobrir nem a configurar à parte.
Por que por país e não uma rota única. Uma integração fiscal é construída e certificada contra um órgão, e as normas mudam por país. Com uma rota por país, uma mudança no Equador é uma versão do endpoint do Equador: não toca a Colômbia, não obriga a versionar tudo, e não pode quebrá-lo. O versionamento fica com a mesma granularidade da mudança.Também torna desnecessário declarar capacidades: as rotas que existem são os países que você atende.
Um 404 nesta rota significa “não atendo esse país”, e é assim que reportamos. Não o use para outros erros: um país suportado que falha responde 4xx/5xx com o bloco failure.
Síncrono. Esta chamada está no caminho crítico da venda: o caixa está esperando os números para imprimir. Orçamento de latência alvo: menos de 3 segundos.

3. Requisição

3.1 Numerar uma venda

Uma só requisição, igual para todos os países. Ela não muda de forma conforme o órgão: o que muda é o que cada provedor usa. O do Equador monta a chave de acesso com a data, o emitente e o sequencial, e nem olha os valores. O da Colômbia precisa de todos eles, porque o seu identificador é um hash da nota. Os dois exemplos abaixo são o mesmo contrato: mesmos campos, mesma ordem. A única coisa que muda são os valores.
storeFiscalConfig.metadata vai vazio: no Equador o estabelecimento e o ponto de emissão você resolve do seu lado, no seu catálogo, a partir de store.code e device.uid.

3.2 Cancelar

Idêntico, com "operation": "CANCEL". Mesmos campos, client e totals incluídos: o cancelamento emite um documento novo e precisa dos mesmos dados da emissão. Não enviamos referência ao documento original. O provedor resolve o que compensa buscando a emissão do mesmo orderCode — que é a sua própria chave de idempotência, já indexada.

3.3 Campos

Os campos vazios são omitidos. Nunca enviamos "". Um campo ausente significa “não configurado”; uma string vazia não deve ser interpretada como valor válido.
Não enviamos estabelecimento nem ponto de emissão. Quem os atribui é o órgão, sob o CNPJ/RUC do emitente, e o FIRE não tem esse catálogo — o ponto de venda também não, e exigi-lo dele o obrigaria a falar a língua do SRI só para faturar.Você os resolve: store.code → estabelecimento, device.uid → ponto de emissão, no seu próprio catálogo. É o mesmo trato da identidade fiscal: mandamos identificadores do negócio e você traduz para os do órgão.Até há pouco o canal declarava o seu ponto de emissão em device.externalId. Foi retirado: era um dado que exigíamos sem poder validar, e que ainda podia não coincidir com o que acabava emitido.

3.4 Os dois metadata

Há dois blocos chave-valor, em níveis distintos e com propósitos distintos:
  • store.storeFiscalConfig.metadata — atributos da loja, constantes. É onde vivem os dados de que o provedor precisa e que não fazem parte do domínio compartilhado: por exemplo a chave técnica e a faixa de numeração que a DIAN entrega com a resolução. São configurados uma vez no backoffice e trafegam em cada chamada daquela loja.
  • metadata (raiz) — atributos da venda, variáveis: os que mudam a cada transação e que algum regime exige declarar.
Ambos são opacos: o FIRE não os interpreta nem os valida.

Assim se carrega o da loja

Configuração fiscal da loja: o NIT e um editor de chave e valor com claveTecnica e o grupo rangoFacturacion, com desde, hasta e prefijo.

Em cima: o NIT do emitente, a chave técnica e a faixa de faturamento como grupo aninhado.

Continuação da mesma tela: o grupo rangoNotaCredito com desde, hasta e prefijo, e abaixo a prévia do JSON resultante.

Mais abaixo, na mesma tela: a faixa de notas de crédito e a prévia do JSON que será enviado.

O que se carrega ali é exatamente o que você recebe em store.storeFiscalConfig.metadata. Na captura, aquela loja vai lhe mandar:
Isto é um exemplo, não o contrato. Nem os nomes das chaves nem a lista de campos são fixados pelo FIRE: são carregados como o provedor pedir, e acrescenta-se o que for preciso. Se amanhã o seu regime precisar de mais um dado, é uma linha nova nesta tela — não uma versão nova do contrato nem um deploy nosso.Publique as chaves que você espera, com o nome exato. Um claveTecnica contra um clave_tecnica é um dado que chega e que você não vai encontrar.
Por que é chave-valor e não um formulário com campos fixos.Os dados de que um provedor precisa são do regime do seu país, não do domínio que compartilhamos: uma chave técnica da DIAN, uma faixa de numeração, o que vier depois. Tipificá-los na nossa tela significaria que somar um país —ou um órgão acrescentar um requisito— obrigue a fazer deploy do backoffice. Com chave-valor, é carregar uma linha.Os valores podem ser texto ou um grupo aninhado, sem limite de profundidade. Por isso uma faixa inteira —prefixo, de, até, resolução, vigência— entra como um bloco, em vez de cinco chaves com o prefixo colado ao nome.O exemplo leva duas faixas porque são duas coisas distintas: a de faturamento e a de notas de crédito. Uma loja que só tenha a primeira pode emitir mas não cancelar.
Esse mesmo bloco também trafega nos eventos do pedido, não só na numeração. Aparece como data.store.storeFiscalConfig —com o seu metadata dentro— em:order.opened · order.completed · order.cancelled · order.invoiced · order.reversedÉ o mesmo dado nos dois caminhos, e de propósito: quem consome eventos para conciliar vê com que configuração aquela venda foi emitida, sem ter de perguntar a ninguém.
O detalhe campo a campo está em order.completed → Dados fiscais.
Os nomes das chaves são definidos por você, não por nós. O campo é livre: quem configura a loja escreve a chave que a sua integração espera. Por isso convém publicar quais você precisa e com que nome exato — um claveTecnica contra um clave_tecnica é um dado que chega e que você não vai encontrar.O FIRE não valida esses nomes de propósito: o vocabulário é do regime e do provedor, e tipificá-lo do nosso lado significaria fazer deploy do backoffice toda vez que um país novo pedir um dado diferente.
Nenhuma chave dentro de metadata pode sobrescrever um campo de domínio. Se aparecer uma chave storeCode ou country dentro de metadata, ela deve ser ignorada. Caso contrário o chave-valor vira a porta dos fundos por onde o contrato é redefinido.

3.5 client e totals — o que vem da venda

Estes dois blocos são os mesmos que o ponto de venda monta para injetar o pedido, e trafegam tal como estão: o FIRE não os recorta nem os renomeia. Por isso não levam vocabulário fiscal — levam o do negócio. Vão sempre, em todos os países. O que muda é quem os usa: o provedor do Equador os ignora, porque a chave de acesso é montada com data, emitente e sequencial. O da Colômbia precisa deles inteiros, porque o CUFE é um hash da nota: entram os valores, cada imposto separadamente, a data com hora e o documento do adquirente.

totals — o que foi cobrado

Moeda USD. Hoje, um único imposto: IVA a 15%.
O SRI não os olha: a chave de acesso é montada com data, emitente e sequencial. Trafegam mesmo assim, caso você precise deles para o seu próprio controle.

client — quem comprou

Chega inteiro, tal como o ponto de venda o montou. Não é um subconjunto fiscal: traz também dados que não servem a nenhum órgão.
govIdType sai de um catálogo fechado. Estes são todos os valores que o FIRE emite, e não vão chegar outros:Traduzi-los para o código que o seu órgão exige é parte da sua implementação, assim como o resto da tradução para a língua do regime.
Leia só o que é fiscal e descarte o resto. O que um regime precisa está em govIdType, govIdNumber, name e —para empresas— billingInformation.businessName e additionalInfo.fiscal. O uid, o e-mail e o telefone são do negócio, não do órgão.govIdType e govIdNumber aparecem duas vezes: na raiz e em billingInformation. Quando divergem, vale o de faturamento — é o documento que o cliente pediu para a sua nota.
O consumidor final chega com FINAL_CONSUMER e zeros. Traduzi-lo para o que o SRI espera no comprovante é parte da sua implementação.
Os valores trafegam na escala do spec: inteiro, em string, ×10.000. Um total de 50.000 COP chega como "500000000"; um de 8,70 USD, como "87000".Não é uma peculiaridade deste endpoint: é como o FIRE armazena e publica todo valor, então é a MESMA escala que você verá nos eventos do pedido. Um único formato nas duas superfícies, e nenhuma conversão que dependa de por onde você leu o dado.Antes este request levava decimais ("total": 50000) enquanto o evento levava "500000000". Quem confundia a superfície declarava dez mil vezes o valor, num documento bem formado que o órgão aceitava do mesmo jeito. Essa classe de erro deixou de existir.Para voltar ao valor real, divida por 10.000. E como a escala são 4 decimais fixos, a conversão para a string que seu regime exige é exata: você move o ponto quatro casas a partir da direita e corta nos decimais da sua moeda. Nada de floats.Isso importa se o seu identificador é um hash sobre uma string: o CUFE é calculado sobre "50000.00", e essa string você monta. O FIRE não a formata porque não conhece a regra do seu regime — mas partir de um inteiro exato é mais seguro que partir de um decimal JSON, onde 8.70 chega como 8.7 e os zeros à direita se perdem.Uma venda de 50.000 COP com IVA de 7.983,19 COP — cada país trafega na sua moeda e com seus impostos, mas a regra da escala é a mesma:
Atenção: só os valores são escalados. rate, taxesPercentage e discountPercentage são proporções, não dinheiro, e trafegam como estão — "0.19" continua "0.19".E isso importa especialmente porque você vai consumir os eventos do pedido. Não é opcional: a numeração te dá os identificadores, mas a venda que você emite ao órgão sai do evento — e de lá você volta com o callback. Sem esse circuito, ninguém sabe se o documento foi emitido.Ver O que chega ao integrador.
taxes traz sempre o detalhamento, um elemento por imposto, cada um com name, base, rate e amount. O montante por imposto é o dado que importa: amount é o que você declara ao órgão, e o taxValue de cima é apenas a soma deles.Percorra o array, não leia taxes[0]. Hoje no Equador e na Colômbia é um único IVA, mas um regime pode declarar vários tributos por comprovante e o array os traz todos, sem que o contrato mude.
Os dois blocos trafegam nas duas operações, INVOICE e CANCEL. É uma só requisição canônica e não é recortada por operação.Não é simetria por capricho: o cancelamento produz um documento novo. Uma nota de crédito colombiana tem o seu próprio identificador calculado sobre os valores e o adquirente, então sem client e totals não haveria com o que montá-lo.O que não muda é o alcance: o cancelamento é total. Não existem cancelamentos parciais em nenhum país que atendemos, então os valores que chegam são os da venda completa, e qual documento você compensa se resolve pelo orderCode.

4. Idempotência

A chave é country + orderCode + operation. Repetir essa tripla deve devolver o mesmo documento com "reused": true, sem consumir outro sequencial. É a mesma chave que o FIRE usa do seu lado, para que um choque seja detectado nas duas pontas ao mesmo tempo.

5. Resposta bem-sucedida

A resposta tem duas partes com regras distintas:
  • O envelope — idêntico em todos os países. É com ele que o FIRE opera: decide se tenta de novo, se houve comprovante, que erro reportar.
  • document — a língua fiscal do país. Cada um manda o que existe no seu regime, com os nomes do seu órgão, e nada mais.
Tudo o que está acima é igual para qualquer país. document é a única coisa que muda, e por isso aqui vai elidido: o seu conteúdo está em 5.2, com uma seção por país. Se você está implementando o Equador, o bloco que lhe cabe é o do Equador e nenhum outro.
country trafega mesmo estando na rota. Não é redundância: o FIRE compara country e orderCode com o que pediu e descarta a resposta se não coincidirem. É o que evita imprimir o documento de outra venda quando há um cruzamento de respostas ou um proxy com cache.

5.1 status

Dois valores, um por operação: Não há mais estados, e é deliberado. Este endpoint produz a representação fiscal —os identificadores para imprimir— e nada mais. O envio ao órgão e a sua autorização ocorrem depois, do lado do provedor, e o desfecho chega pelo callback. Modelar aqui estados de autorização mistura dois ciclos de vida distintos. status é quase um eco de operation, e existe por um único caso: quando a operação não produz documento. O cancelamento no Brasil é um evento de cancelamento, não um documento novo, então a resposta chega com document: null e sem graphic. Ali o status é a única coisa que afirma que a operação foi concluída, em vez de deixar uma resposta bem-sucedida e vazia que não se distingue de um erro silencioso. Em particular:
  • Não existe PENDING. Imediatamente depois de numerar, o documento está sempre pendente de autorização: é a condição normal, não um estado a informar. O caixa imprime com os identificadores que acabou de receber.
  • Não existe REJECTED. Se não foi possível numerar, é um erro: HTTP não-2xx com o bloco failure. Uma rejeição com 200 OK e o motivo escondido num campo é um contrato onde alguém não valida e acredita ter numerado.
  • Não existe REUSED. Isso é reused: true, um booleano ortogonal. É possível ter INVOICED com reused: true — uma retentativa idempotente de uma venda já numerada — e essa distinção se perde se REUSED fosse um estado.

5.2 document — o documento numerado

Aqui se fala a língua fiscal do país. É o único bloco da resposta que muda entre países, e muda inteiro: os nomes são os do órgão, não uma tradução nossa. Um país manda o que existe no seu regime e nada mais. Um campo que não se aplica não trafega em null: simplesmente não está.
O número visível é você quem monta, já pronto para imprimir.Quinze dígitos em três trechos separados por hífen —establecimiento(3), puntoEmision(3), secuencial(9)— conforme o art. 18 do Reglamento de Comprobantes de Venta.Antes era o FIRE que o compunha com as três peças. Foi movido para cá de propósito: o formato é regra do regime, não apresentação, e quem está certificado perante o SRI é você. Se o Regulamento mudar a convenção, muda do seu lado sem que o FIRE faça deploy.Há ainda uma razão concreta: o Regulamento permite omitir os zeros à esquerda do sequencial. 001-020-123 pode ser tão legal quanto 001-020-000000123. Montando-o nós, estaríamos escolhendo uma variante em seu nome. Mande o que você emitiu — o FIRE o imprime tal como veio, sem reformatar.
As três peças continuam trafegando igual, como o SRI as nomeia e sem concatená-las numa serie de 6 dígitos: elas servem para conciliar, não para compor o número.O puntoEmision que você devolver é o que ficou emitido, que pode não ser o que foi pedido em device.uid através do seu catálogo. O que vale é sempre o que volta, nunca o que foi mandado.
ambiente não é informativo. O FIRE o compara com o ambiente configurado para o vendor e corta se não coincidirem. É o que pega um provedor emitindo contra o ambiente de homologação do SRI enquanto a operação acredita estar em produção — sem essa verificação as vendas saem com chaves de acesso que o órgão não reconhece, e isso se descobre quando um cliente reclama a sua nota.

Por que o bloco é do país e não um modelo comum

Avaliou-se um bloco plano com nomes por papel —accessKey, sequential, controlNumber— e ele foi descartado. O custo não era um campo nulo: era que cada país novo acrescentava um campo que todos os demais carregariam vazio para sempre, e que o mesmo identificador teria dois nomes conforme entrasse pelo prekey ou pelo callback. Este é, além disso, o mesmo mecanismo que o callback de resultado já usa, validando por país sobre o countryCode da raiz. Um único padrão nas duas direções.

O que document NÃO leva

Não leva documentType. Com que instrumento fiscal a operação se materializa —uma nota de crédito no Equador, um evento de cancelamento no Brasil— é assunto do país e do provedor. O que foi pedido já é dito pelo status. Não leva o que o órgão atribui ao autorizar — o numeroAutorizacion do SRI, o protocolo da SEFAZ. Isso chega pelo callback; declará-lo aqui o condena a vir sempre em null. Não leva authorizationMode nem issuedAt. São comuns a todos os países e vivem na raiz da resposta.

5.3 graphic — o que é imprimível

Chave-valor, com as chaves de que cada país precisar. {} ou null quando a operação não produz nada para imprimir — o cancelamento no Brasil, por exemplo.
Cada valor é a string exata a codificar, já pronta para renderizar. O FIRE não a interpreta nem a transforma: repassa ao ponto de venda, que a renderiza com a sua própria biblioteca e a manda para a impressora. Não se geram imagens deste lado — o tamanho e a resolução dependem da impressora, e isso só quem imprime sabe. É um mapa aberto e não um campo fixo porque o comprovante de cada país não leva sempre a mesma coisa: o Equador imprime o código da chave de acesso, o Brasil o QR da NFC-e, o Chile o timbre eletrônico (TED). Um país pode precisar de mais de um. Com um mapa, acrescentar um é enviá-lo; com campos fixos, é versionar o contrato. As chaves são estáveis e descritivas do propósito — qr, barcode, ted — não da simbologia do momento. Este bloco é o único da resposta que o provedor aporta e que não se pode derivar, por um caso concreto: o QR da NFC-e brasileira é uma URL assinada com um hash que só o emitente consegue construir. Não se deriva da chave. Se não chegar, não há QR. No Equador o valor vai coincidir com document.claveAcceso. Essa redundância é deliberada: a alternativa é o ponto de venda ter de saber que no Equador se codifica a chave, no Brasil a URL e no Chile o TED.
A Colômbia não manda graphic. O seu QR já é uma URL pronta para imprimir e trafega em document.qrCode; repeti-la aqui seria o mesmo dado em dois lugares que podem divergir, e na dúvida ninguém saberia qual vence.A diferença em relação ao Equador não é capricho: lá o QR deriva da chave de acesso, e entregá-lo explícito poupa o ponto de venda de ter de saber disso. Aqui não deriva de nada — já vem resolvido.
Não inclui pdfUrl, xmlUrl nem lookupUrl. Os dois primeiros só existem depois de o órgão autorizar e chegam pelo callback; declará-los aqui os condena a vir sempre em null, e um campo que é sempre nulo ensina a ignorá-lo. lookupUrl é uma constante por país e ambiente, não um dado do documento.

5.4 provider e metadata — os dois blocos do provedor

São dois blocos distintos e não intercambiáveis, e o FIRE os guarda em duas colunas diferentes. A diferença é se o campo tem forma acordada ou não.

provider — identidade, com forma

Os três vão sempre presentes, com null quando não se aplica. null diz “não tenho”; ausente obriga a distinguir duas formas da mesma coisa.
reference não é a nossa Idempotency-Key. Aquela é mandada por nós e o provedor a ecoa por outro lado. Esta é do provedor, e é a que serve quando é preciso escalar um caso a ele: sem ela, a única forma de ele encontrar a operação é buscar por orderCode na faixa de datas certa.

metadata — a sacola opaca

Sem forma acordada. Vai o que servir ao provedor para diagnosticar: os códigos com que ele nomeia a loja e o aparelho, um identificador da sua fila, o que for. O FIRE guarda tal como está e publica tal como está, e nada nosso programa contra as suas chaves.
Não mandem aqui o que já tem o seu lugar. Repetir failure dentro de metadata, ou o name do bloco de cima, produz o mesmo fato guardado duas vezes — e duas cópias dessincronizam. Se um dado tem campo próprio no contrato, vai no seu campo e não também aqui.
Pode ser {}. O que não pode é mudar entre uma emissão e a sua retentativa idempotente: um 201 que traz a sacola populada e um 200 REUSED que a traz vazia descrevem a mesma operação de duas maneiras diferentes, e quem ler o evento vai ver que “mudou” algo que não mudou.

6. Resposta com erro

Trafega com código HTTP não-2xx422 para um problema de configuração ou de dados, 5xx para um transitório. Nunca com 200.
retryable é obrigatório e quem decide é o provedor. É o que nos permite distinguir um problema de configuração —que não melhora tentando de novo— de um transitório. Sem esse campo é preciso adivinhar pelo código HTTP, e adivinhar errado significa tentar de novo no caixa enquanto o cliente espera, ou abandonar uma venda que podia ser numerada. failure.code deve ser um código estável e acionável, não um texto livre. É o que permite construir alertas e documentação de suporte. failure.message deve descrever o problema real, não uma generalidade. "identidad fiscal de tienda no configurada: EC / tienda K0050" permite consertar; "documento rejeitado" obriga a abrir um chamado.

7. Regras da integração

O que enviamos manda. Se o catálogo do provedor tiver uma identidade fiscal diferente da que enviamos, ele deve rejeitar com erro explícito, nunca emitir com a sua. Um comprovante emitido sob o contribuinte errado não se conserta com um deploy. metadata é opaco nos dois sentidos e não pode sobrescrever campos de domínio. Os códigos do órgão não trafegam no contrato. Nada de documentTypeCode: "01", tipoComprobante ou equivalentes. O provedor os deriva de operation + country. O contrato é versionado. Uma mudança quebrante requer uma versão nova do endpoint e uma janela de convivência; não se muda o significado de um campo existente.

8. O callback de resultado

O callback de resultado segue como está. É correlacionado por orderCode + operation, com o tenant derivado da API key com que se autentica. Deve incluir a operação: sem ela, uma nota e o seu cancelamento sobre o mesmo pedido são indistinguíveis. Devolver identificadores adicionais no callback é opcional e bem-vindo, mas não obrigatório.
Use os mesmos nomes da numeração. O callback da Colômbia declara cufe, prefijo, numeroDian, numeroComprobante, qrCode e ambiente — exatamente os de 5.2. É o mesmo documento contado duas vezes, e se os nomes divergirem, conciliar os dois caminhos deixa de ser comparar campos e passa a ser traduzir, que é onde os erros se infiltram.
O callback não é rejeitado por um campo que falte. Quando ele chega, o documento já existe perante o órgão: devolver um 400 não o desfaz, só nos deixa sem saber de uma nota autorizada — e esse aviso não volta.Por isso os campos novos entram sempre opcionais e o que você mandar a mais é preservado. É o contrário da numeração, que valida estrito: ali o dado acabou de ser calculado e ainda não se imprimiu nada. A assimetria é deliberada, e depende de onde o erro dói.