FE API Fiscal NFC-e
Documentação técnica

API REST de emissão de NFC-e.

Uma API REST multiempresa em Delphi(Horse) que monta, assina e transmite Notas Fiscais de Consumidor Eletrônica, com motor de tributos (ICMS, PIS/COFINS, IBS/CBS da Reforma Tributária), contingência offline automática e idempotência de venda.

Transmissão à SEFAZ Multiempresa Motor de tributos embutido Reforma Tributária (IBS/CBS) Contingência offline Idempotência
POST /v1/emitir · 201 Created
"sucesso": true,
"status": 100,
"motivo": "Autorizado o uso da NF-e",
"chave": "25082055254933000105650020000000551234567890",
"protocolo": "135260000123456",
"numero": 55,
"serie": 2,
"qrcode": "https://sefaz.pb.gov.br/nfcehom?p=...",
// nota autorizada de verdade pela SEFAZ
Construída com
Delphilinguagem
Horseframework
ACBrintegração fiscal
FastReportgeração de PDF (DANFE)
Firebird 5banco de dados
01 — Visão geral

O que essa API faz

API REST multiempresa (Delphi + Horse) para emissão de NFC-e (modelo 65). Cada empresa emissora tem seu próprio certificado A1, UF, ambiente e numeração, resolvidos pelo header de autenticação em cada requisição — o cadastro da empresa em si fica num aplicativo à parte, direto no banco.

10
rotas de negócio autenticadas
5
tributos calculados por item
7
tabelas no modelo de dados, com FK real
24h
janela de contingência offline
Tempo real

Emissão real via SEFAZ

Monta o XML, assina com o certificado A1 da empresa e transmite à SEFAZ via ACBr.

Por item

Motor de tributos por item

Recebe CST/CSOSN e alíquotas; calcula ICMS, PIS e COFINS automaticamente por item.

Reforma 2026

Reforma Tributária (IBS/CBS)

Já calcula e persiste IBS e CBS por item e por nota — pronta para a transição do novo sistema tributário.

NT 2025.001

QR-Code v2 e v3

Suporta as duas versões do QR-Code da NFC-e, configurável por empresa — v3 dispensa CSC.

Offline

Emissão em contingência

SEFAZ inacessível: cai sozinha em modo offline, assina e grava local — o PDV não para de vender.

Por série

Numeração automática

Contador de NFC-e por série (um por PDV/caixa), avançado sozinho a cada emissão homologada.

Sem duplicidade

Idempotência de venda

Reenviar a mesma venda após uma queda de rede devolve a nota original, sem duplicar.

Convênio 199/22

Grupo de combustível

ICMS monofásico (CST 61) para postos de combustível e revenda de GLP, com mistura GLP/GN.

02 — Arquitetura

Stack & organização do código

Sem frameworks pesados nem camadas indiretas demais — Horse para HTTP, ACBr para o domínio fiscal, FireDAC puro para o banco.

CamadaTecnologia
Framework HTTPHorse — app console, roteamento por middleware chain
Parsing JSONHorse.Jhonson
Documentação interativaHorse.GBSwagger — Swagger UI em /v1/swagger/doc/html
CompressãoHorse.Compression
LogHorse.Logger + provider de console
Emissão fiscalACBrNFe — assinatura, schema, transmissão SEFAZ
Geração de DANFEACBrNFeDANFeFR + FastReport — layout do cupom fiscal, exportado em PDF
Criptografia / SSLOpenSSL (assinatura XML e certificado A1)
Banco de dadosFirebird 5.0, acesso via FireDAC puro (TFDConnection/TFDQuery)
E-mailIndy (TIdSMTP) com SSL explícito/implícito conforme porta
Organização de pastas
Fontes/
controller/   Controllers Horse + GBSwagger
dao/ / SQL/   Acesso a dados e comandos SQL
service/      TServiceFiscal — cria e configura uma TACBrNFe nova por requisição
infra/        Settings (.ini), conexão FireDAC
middleware/   TAuthMiddleware + tratamento global de erro
Princípios de projeto
  • Sem estado global entre requisições. Cada emissão cria sua própria TACBrNFe e sua própria conexão de banco — necessário porque cada empresa tem UF, certificado e ambiente diferentes, e o Horse serve cada requisição numa thread própria.
  • Validação de formato antes de gastar uma chamada à SEFAZ. NCM (8 dígitos), CFOP (4 dígitos, começando 5/6), CEST (7 dígitos) e CPF/CNPJ (dígito verificador) são validados localmente primeiro.
03 — Autenticação

Uma chave por empresa, resolvida antes do controller rodar

Toda rota, exceto /healthcheck e /swagger/*, exige dois headers. Não existe emitente no body ou na query string em rota nenhuma.

HeaderDescrição
X-EmitenteCNPJ da empresa emissora (só números)
X-Api-KeyChave de 64 caracteres gerada pelo app de cadastro, por empresa

Um middleware dedicado (TAuthMiddleware) roda antes de qualquer controller: valida os dois headers contra o banco, e — se corretos — guarda o emitente resolvido numa sessão de requisição. Nenhum controller lê CNPJ de body/query nunca mais. Requisição sem headers, com CNPJ inexistente, empresa inativa ou chave errada recebe 401 antes de qualquer outra lógica rodar.

Rotas identificadas por {chave} (consulta, XML, DANFE, cancelamento, e-mail) fazem uma checagem extra de posse: se o documento pedido não pertencer à empresa autenticada, respondem 404 — o mesmo formato de "não encontrado" usado para chave inexistente, de propósito, para não revelar que a chave existe mas é de outra empresa.


04 — Referência de rotas

Todas as rotas, prefixo /v1

Todo exemplo de JSON abaixo é o contrato real da API, não uma simplificação — inclusive o corpo completo de emissão, com o bloco de combustível.

POST /v1/emitir

Emite uma NFC-e a partir do JSON da venda e transmite à SEFAZ em tempo real. Aceita idempotência via identificador_venda e cai em contingência offline sozinha se a SEFAZ estiver fora do ar.

Requer X-Emitente / X-Api-Key Calcula tributos por item Idempotente (opcional)
Campos, JSON completo e respostas
Corpo da requisição
CampoObrigatórioDescrição
serieOpcionalNúmero da série de NFC-e. Sem ele, usa a série marcada como padrão da empresa.
identificador_vendaOpcionalReferência livre da venda no sistema chamador (cabe um UUID). Reenviar o mesmo valor devolve a nota já emitida, sem duplicar.
consumidor.cnpj_cpfOpcionalCPF ou CNPJ do consumidor. Dígito verificador validado se enviado.
consumidor.nomeOpcionalNome do consumidor identificado.
natureza_operacaoOpcionalDefault "VENDA".
trocoOpcionalDefault 0. Vira Pag.vTroco quando o pagamento em dinheiro supera o total.
itens[]ObrigatórioAo menos um item — ver tabela de campos do item abaixo.
totais.valor_total / valor_descontoObrigatórioTotais da nota.
pagamentos[]ObrigatórioAo menos um pagamento — ver tabela de formas de pagamento abaixo.
Campos por item (itens[])
CampoObrigatórioDescrição
quantidadeObrigatórioSem default.
cfopObrigatório4 dígitos (NFC-e é sempre saída/venda).
unidadeObrigatórioSem default.
ncmObrigatórioExatamente 8 dígitos.
valor_descontoOpcionalDefault 0.
origem_mercadoriaOpcionalDefault "0" (nacional). Tabela B do CST.
cst_csosnObrigatórioCampo único de código tributário do ICMS — a API decide CSOSN ou CST a partir do regime (CRT) da empresa.
aliquota_icmsOpcionalDefault 0. Só tem efeito fora do Simples Nacional.
cst_pis / cst_cofinsObrigatórioEx.: "49".
aliquota_pis / aliquota_cofinsOpcionalDefault 0.
cst_ibscbsObrigatórioCST da Reforma Tributária (IBS/CBS).
cclasstribObrigatórioCódigo de Classificação Tributária da Reforma — sem default.
aliquota_ibs_uf / aliquota_ibs_mun / aliquota_cbsOpcionalDefault 0 cada.
cestOpcional7 dígitos se preenchido. "0" e vazio contam como não informado.
combustivelOpcionalObjeto — ativa o regime de ICMS monofásico (CST 61). Ver tabela dedicada abaixo.
Objeto combustivel (posto / revenda de GLP)
CampoObrigatórioDescrição
codigo_anpObrigatórioCódigo do produto na tabela ANP (ex. 210203001 = GLP).
descricao_anpObrigatórioDescrição do produto conforme ANP.
uf_consumoOpcionalDefault = UF da empresa.
quantidade_tributavelObrigatórioNa unidade da ANP (kg para GLP, litro para líquidos).
aliquota_ad_remObrigatórioValor fixo em R$ por unidade — "ad rem", não percentual.
percentual_glpObrigatório se GLP% de GLP na mistura, sem default — revenda de botijão puro manda 100 explicitamente.
percentual_gn_nacional / percentual_gn_importadoOpcionalDefault 0 — mistura GLGN.
valor_partidaOpcionalDefault 0 — rateio de preço da mistura.
Requisição completa — biscoito (Simples Nacional) + GLP monofásico
POST /v1/emitirX-Emitente / X-Api-Key obrigatórios
{
  "serie": 2,
  "identificador_venda": "a1b2c3d4-5e6f-47a8-9b21-venda-pdv-0042",
  "consumidor": { "cnpj_cpf": "12345678909", "nome": "Cliente Teste" },
  "natureza_operacao": "VENDA",
  "troco": 0,
  "itens": [
    {
      "produto_id": "SKU-0031",
      "descricao": "Biscoito Recheado 130g",
      "ncm": "19053100",
      "cfop": "5102",
      "unidade": "UN",
      "quantidade": 3,
      "valor_unitario": 4.5,
      "valor_total": 13.5,
      "valor_desconto": 0,
      "origem_mercadoria": "0",
      "cst_csosn": "102",
      "aliquota_icms": 0,
      "cst_pis": "49",
      "aliquota_pis": 0,
      "cst_cofins": "49",
      "aliquota_cofins": 0,
      "cst_ibscbs": "000",
      "cclasstrib": "000001",
      "aliquota_ibs_uf": 0,
      "aliquota_ibs_mun": 0,
      "aliquota_cbs": 0,
      "cest": "1706300"
    },
    {
      "produto_id": "SKU-GLP-13",
      "descricao": "GLP 13kg (Botijão)",
      "ncm": "27111910",
      "cfop": "5405",
      "unidade": "KG",
      "quantidade": 1,
      "valor_unitario": 120.0,
      "valor_total": 120.0,
      "valor_desconto": 0,
      "origem_mercadoria": "0",
      "cst_csosn": "61",
      "cst_pis": "49",
      "aliquota_pis": 0,
      "cst_cofins": "49",
      "aliquota_cofins": 0,
      "cst_ibscbs": "000",
      "cclasstrib": "000001",
      "aliquota_ibs_uf": 0,
      "aliquota_ibs_mun": 0,
      "aliquota_cbs": 0,
      "combustivel": {
        "codigo_anp": 210203001,
        "descricao_anp": "GLP",
        "uf_consumo": "PB",
        "quantidade_tributavel": 13,
        "aliquota_ad_rem": 143.66,
        "percentual_glp": 100,
        "percentual_gn_nacional": 0,
        "percentual_gn_importado": 0,
        "valor_partida": 0
      }
    }
  ],
  "totais": { "valor_total": 133.5, "valor_desconto": 0 },
  "pagamentos": [
    {
      "forma": "01",
      "valor": 133.5,
      "descricao": "",
      "cnpj_credenciadora": "",
      "bandeira": "",
      "tipo_integracao": "2",
      "autorizacao": ""
    }
  ]
}
Formas de pagamento (pagamentos[].forma)

Código SEFAZ direto (NT 2020.006 / Anexo I, Grupo YA) — sem tradução interna. "03"/"04"/"17" (crédito, débito, PIX dinâmico) exigem cnpj_credenciadora e bandeira; "99" exige descricao.

CódigoDescriçãoCódigoDescrição
01Dinheiro16Depósito Bancário
02Cheque17PIX Dinâmico
03Cartão de Crédito18Transferência / Carteira Digital
04Cartão de Débito19Fidelidade / Cashback
05Crédito Loja20PIX Estático
10Vale Alimentação90Sem Pagamento
11Vale Refeição91Pagamento Posterior
12Vale Presente99Outros (exige descricao)
13Vale Combustível
15Boleto Bancário
Respostas
Autorizada201
{
  "sucesso": true,
  "contingencia": false,
  "status": 100,
  "motivo": "Autorizado o uso da NF-e",
  "chave": "25082055254933...",
  "protocolo": "135260000123456",
  "numero": 55,
  "serie": 2,
  "data_autorizacao": "2026-08-11T14:32:07",
  "qrcode": "https://sefaz.../nfcehom?p=...",
  "xml_url": "/v1/nfce/.../xml",
  "danfe_url": "/v1/nfce/.../danfe",
  "documento_id": 512
}
Rejeitada pela SEFAZ422
{
  "sucesso": false,
  "contingencia": false,
  "status": 539,
  "motivo": "Rejeicao: Duplicidade de NF-e",
  "chave": "25082055254933...",
  "protocolo": ""
}
// numero NAO avanca numa rejeicao real —
// reenviar o mesmo payload corrigido reusa o numero
Contingência (SEFAZ fora do ar)201
{
  "sucesso": true,
  "contingencia": true,
  "status": 0,
  "chave": "25082055254933...",
  "aviso": "Emitida em contingencia offline -- SEFAZ inacessivel. Pendente de transmissao (POST /nfce/{chave}/transmitir-contingencia) dentro de 24 horas."
}
Replay de venda duplicada201
{
  "sucesso": true,
  "duplicado": true,
  "chave": "25082055254933...",
  "aviso": "Requisicao identica ja processada anteriormente (mesmo identificador_venda) — retornando o resultado da emissao original, nenhuma nota nova foi criada."
}
GET /v1/nfce/status-servico

Consulta em tempo real do status do serviço da SEFAZ — na UF, ambiente e certificado da empresa autenticada.

Exemplo de resposta
200 OK
{
  "cStat": 107,
  "xMotivo": "Servico em Operacao",
  "tMed": 1,
  "dhRecbto": "2026-08-11T09:12:03",
  "ambiente": "homologacao",
  "uf": "PB",
  "em_operacao": true
}
GET /v1/certificado

Validade do certificado digital A1 da empresa. Checagem 100% local — lê o .pfx configurado, não depende da SEFAZ estar no ar.

Exemplo de resposta
200 OK
{
  "razaoSocialCertificado": "MEDEIROS SOLUCOES DE SOFTWARE",
  "cnpjCertificado": "55254933000105",
  "dataValidade": "2027-03-15T23:59:59",
  "diasRestantes": 217,
  "vencido": false,
  "alerta": false
}

alerta: true quando faltam 30 dias ou menos para o vencimento.

GET /v1/nfce/{chave}

Consulta o documento persistido no banco local pela chave de acesso (44 dígitos). Só o emitente dono do documento consegue — chave de outra empresa responde 404.

Exemplo de resposta
200 OK
{
  "id": 512,
  "chave": "25082055254933...",
  "numero": 55,
  "serie": 2,
  "dhEmissao": "2026-08-11T14:32:05",
  "dhAutorizacao": "2026-08-11T14:32:07",
  "protocolo": "135260000123456",
  "status": 1,
  "motivo": "Autorizado o uso da NF-e",
  "tipoEmissao": 1,
  "contingenciaPendente": false,
  "valorProdutos": 133.5,
  "valorDesconto": 0,
  "valorTotal": 133.5,
  "emitente": { "cnpj": "55254933000105", "razaoSocial": "..." },
  "consumidor": { "cnpj_cpf": "12345678909", "nome": "Cliente Teste" },
  "itens": [ { "id": 1, "quantidade": 3, "valorProdutos": 13.5, "valorDesconto": 0, "totalItem": 13.5 } ],
  "pagamentos": [ { "id": 1, "tipoPagamento": "01", "valorPagamento": 133.5, "formaPagamento": 1 } ],
  "xml_url": "/v1/nfce/.../xml",
  "danfe_url": "/v1/nfce/.../danfe"
}
GET /v1/nfce/{chave}/xml

Retorna o XML assinado, gravado em disco no momento da emissão — Content-Type: application/xml, corpo não é JSON.

200 · application/xml404 se não encontrado / outra empresa
GET /v1/nfce/{chave}/danfe

Gera (se ainda não existir) e retorna o PDF binário do DANFE — Content-Type: application/pdf. Em contingência, o layout já imprime "Pendente de Autorização" sozinho.

Exemplo de resposta
Sucesso200
binário do PDF
Falha na geração500
{
  "sucesso": false,
  "pdf_gerado": false,
  "mensagem": "..."
}
POST /v1/nfce/{chave}/cancelar

Evento de cancelamento real na SEFAZ.

Requisição e resposta
Requisição
{
  "justificativa": "Item vendido por engano, cliente devolveu"
}
Cancelado200
{
  "sucesso": true,
  "cStat": 135,
  "chave": "...",
  "protocolo": "135260000654321",
  "dhCancelamento": "2026-08-11T15:01:00",
  "motivo": "Evento registrado e vinculado a NF-e",
  "justificativa": "Item vendido por engano, cliente devolveu"
}
POST /v1/nfce/{chave}/transmitir-contingencia

Retransmite uma NFC-e emitida em contingência offline. Sem body — carrega o XML já assinado e chama a SEFAZ de novo.

Comportamento por status
SituaçãoStatusEfeito
Autorizada agora200Documento passa a status=1, protocolo real gravado
SEFAZ ainda inacessível503Nada muda no banco — pode tentar de novo depois
Rejeitada de verdade422Documento passa a status=0
POST /v1/nfce/inutilizar

Inutiliza uma faixa de numeração na SEFAZ (número pulado, erro de sistema, etc.). Após homologada, avança o contador da série se a faixa estiver à frente do número atual.

Requisição e resposta
Requisição
{
  "ano": 2026,
  "serie": 1,
  "numero_inicial": 41,
  "numero_final": 41,
  "justificativa": "Numero pulado por falha de sistema no PDV"
}
// "ano" opcional, default = ano corrente
Homologada200
{
  "sucesso": true,
  "cStat": 102,
  "serie": 1,
  "numeroInicial": 41,
  "numeroFinal": 41,
  "protocolo": "135260000112233",
  "dhInutilizacao": "2026-08-11T15:10:00",
  "motivo": "Inutilizacao de numero homologado"
}
POST /v1/nfce/{chave}/email

Envio real via SMTP (Indy). Gera o DANFE sob demanda se ainda não existir em disco, para anexar.

Campos e resposta
CampoObrigatórioDescrição
paraObrigatórioE-mail destinatário.
assuntoOpcionalAssunto do e-mail.
anexar_xmlOpcionalDefault true.
anexar_danfeOpcionalDefault true — se a geração do PDF falhar, o e-mail ainda sai só com o XML.
200 OK
{
  "sucesso": true,
  "chave": "...",
  "para": "cliente@exemplo.com",
  "anexar_xml": true,
  "anexar_danfe": true,
  "danfe_anexado": true,
  "mensagem": "E-mail enviado com sucesso"
}
GET /v1/healthcheck

Rota pública, sem autenticação. Fixa — não checa banco nem ACBr, só confirma que o processo está de pé.

200 OK
{ "Status": "Funcionando" }

05 — Modelo de dados

7 tabelas, com integridade referencial real

Firebird 5.0, com integridade referencial real entre as tabelas — só o necessário para o ciclo de vida da NFC-e.

emitente Dados fiscais completos por empresa: identificação, endereço, IE, CRT, certificado A1, ambiente, versão de QR-Code.
emitente_serie Uma linha por série de NFC-e (por PDV/caixa) — contador de numeração isolado por série. FK → emitente.id (RESTRICT)
documentoNfce Capa do documento: chave, status, protocolo, consumidor, totais de tributos calculados, idempotência. FK → emitente.id (RESTRICT)
itemDocumento Linha de item, com CST/CSOSN, base de cálculo e valor de cada tributo, mais campos de combustível. FK → documentoNfce.id (CASCADE)
pagamentoNfce Forma de pagamento, valor e os dados de cartão/PIX usados para montar o grupo <card> do XML. FK → documentoNfce.id (CASCADE)
inutilizacaoNFCe Histórico de faixas de numeração inutilizadas na SEFAZ. FK → emitente.id (RESTRICT)
config_resptec Linha única, global — identificação da software house responsável (grupo infRespTec), exigido por algumas UFs. sem FK — não é filha de empresa
06 — Escopo

O que fica fora do escopo

Uma API de emissão fiscal, não uma retaguarda completa — isso é o que fica por conta de quem integra.

  • A API não decide qual CST/CSOSN/alíquota usar por produto — isso é responsabilidade de quem chama. Um motor de regra fiscal automático por NCM é um projeto à parte.
  • Sem HTTPS nativo — precisa de proxy reverso (IIS/Nginx/Caddy) na frente em produção.
  • Log só em stdout, sem persistência entre reinícios.
  • Sem retentativa automática de contingência em background — quem chama precisa acionar transmitir-contingencia periodicamente enquanto houver notas pendentes.