Portal do Desenvolvedor FOX NF-e
Integração fiscal por API REST, com SDKs oficiais e webhooks assinados. Esta documentação descreve o que já está em operação e sinaliza, com honestidade, o que ainda está em revisão.
Comece por aqui: a API vive em https://www.foxnfe.com.br/api/v1. Toda requisição autenticada leva dois cabeçalhos — Authorization: Bearer <token> e X-Tenant-ID: <slug-do-tenant>. O token é emitido pela API (POST /auth/login) e deve ficar guardado no servidor do integrador (variável de ambiente ou cofre de segredos) — nunca no código do navegador nem em log. Faça primeiro chamadas de leitura, como GET /auth/me, antes de qualquer emissão.
URL base e verificação de saúde #
A URL base canônica é https://www.foxnfe.com.br/api/v1. Os endpoints protegidos respondem 401 sem credenciais válidas — isso verifica a conectividade do endpoint e a exigência de autenticação, sem comprovar os fluxos autenticados. Use uma chamada de leitura para validar conectividade e TLS antes de escrever qualquer fluxo de emissão.
# Sem token: deve responder 401 (autenticação exigida)
curl -i https://www.foxnfe.com.br/api/v1/auth/me
# Com token de servidor (nunca embutido no front-end)
curl -s https://www.foxnfe.com.br/api/v1/auth/me \
-H "Authorization: Bearer $FOXNFE_TOKEN" \
-H "X-Tenant-ID: $FOXNFE_TENANT"
X-Tenant-ID recebe o identificador textual do tenant (por exemplo minha-empresa), não um UUID nem o ID inteiro interno.
Primeiros passos, na ordem certa #
Uma primeira integração segura lê antes de escrever. Comece confirmando identidade e configuração; só depois avance para emissão em ambiente de homologação.
- Confirme o token com
GET /auth/me— retorna o usuário e o tenant vinculados ao token. - Inspecione o webhook com
GET /webhooks/config— mostra se há URL configurada, sem revelar o segredo. - Escolha o ambiente pelo campo
ambientedo payload (2= homologação) — veja Autenticação e ambientes. - Trate o
202como "enfileirado", não como "autorizado pela SEFAZ" — veja Emissão assíncrona.
console.log, print ou echo com o token ou o segredo de webhook. Eles pertencem a variáveis de ambiente do servidor e a um cofre de segredos — jamais ao histórico de terminal, a logs ou ao bundle do navegador.
Autenticação e ambientes #
Autentique com dois cabeçalhos e escolha o ambiente pelo payload. Todo endpoint protegido exige, em conjunto, Authorization: Bearer <token> e X-Tenant-ID: <slug>. Para NF-e/NFC-e, produção e homologação são selecionadas pelo campo ambiente do corpo da requisição (1 = produção, 2 = homologação) — trocar o hostname não muda o ambiente fiscal. Essa é a convenção da NF-e/NFC-e; a NFSe (Padrão Nacional) tem contrato próprio e ainda está em revisão, então não presuma o mesmo campo para todos os documentos.
Cabeçalhos obrigatórios
| Cabeçalho | Valor | Observação |
|---|---|---|
Authorization | Bearer <token> | Token emitido pelo servidor; carregado de variável de ambiente. |
X-Tenant-ID | <slug> | Slug textual do tenant (ex.: minha-empresa). |
Content-Type | application/json | Em requisições com corpo. |
Accept | application/json | Respostas de erro seguem o padrão Laravel (message + errors). |
Obter e reutilizar o token
Um token pode ser obtido por POST /auth/login (e-mail e senha) ou injetado a partir de um segredo já persistido. Em serviços de longa duração, prefira injetar um token guardado no cofre a autenticar a cada chamada.
ps e no histórico do shell, e interpolar a senha em JSON de curl quebra com caracteres especiais. Leia e-mail e senha de variáveis de ambiente, monte o corpo com um serializador JSON e grave o token direto no cofre — sem ecoá-lo.
#!/usr/bin/env python3
# Somente biblioteca padrão. Credenciais vêm do ambiente; o token é gravado
# num arquivo 0600 (ou entregue ao cofre) e NUNCA impresso no stdout.
import json, os, urllib.request
body = json.dumps({ # json.dumps escapa o conteúdo corretamente
"email": os.environ["FOXNFE_EMAIL"],
"password": os.environ["FOXNFE_PASSWORD"],
}).encode("utf-8")
req = urllib.request.Request(
"https://www.foxnfe.com.br/api/v1/auth/login",
data=body, method="POST",
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"X-Tenant-ID": os.environ["FOXNFE_TENANT"],
},
)
with urllib.request.urlopen(req, timeout=30) as resp:
token = json.load(resp)["token"] # não faça print(token)
# Use um caminho novo em um diretório privado, pertencente ao usuário do serviço.
# O_EXCL recusa arquivos existentes e links simbólicos; nunca sobrescreve um token.
# No Windows, use um cofre ou controle de acesso equivalente em vez deste exemplo POSIX.
fd = os.open(os.environ["FOXNFE_TOKEN_FILE"], os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w") as f:
f.write(token)
print("token obtido e gravado com permissão 600") # confirma sem revelar o segredo
Produção × homologação (NF-e/NFC-e)
Na NF-e/NFC-e, o ambiente fiscal é decidido pelo campo ambiente dentro do payload de emissão: 1 para produção e 2 para homologação. Esse valor viaja no corpo da requisição, não na URL. Um mesmo token e uma mesma URL base atendem os dois ambientes — a diferença está no que você declara no documento. Para a NFSe (Padrão Nacional), o contrato de ambiente é próprio e está em revisão; confira o estado em Estado dos domínios fiscais antes de assumir a mesma convenção.
ambiente: 2 são de teste na SEFAZ e não substituem emissão de produção.Emissão assíncrona: o que o 202 significa
A emissão é assíncrona. Ao aceitar uma emissão, a API responde imediatamente com o recurso em estado pending (nas respostas HTTP diretas isso corresponde a 202 Accepted). Isso quer dizer enfileirado para processamento — e não autorizado pela SEFAZ.
authorized, acompanhado do protocolo fiscal correspondente, confirma a autorização. Sair de pending não é, por si só, autorização: rejected é recusa pela SEFAZ, e estados intermediários como processing ou de erro (failed) não confirmam nada. Consulte o status por polling ou aguarde o webhook nfe.authorized, e nunca trate o 202 como confirmação de autorização fiscal.Idempotência, erros e novas tentativas
A idempotência de emissão está vinculada ao par payload/tenant: reenviar o mesmo documento para o mesmo tenant não deve gerar duplicidade silenciosa. Diante de um status ambíguo (timeout, 5xx, conexão perdida), não reemita às cegas — primeiro consulte o status do recurso e só então decida. Erros de validação chegam como 422 com o mapa errors; erros de autenticação, como 401/403.
SDKs oficiais #
Há SDKs para Node.js (≥18), Python (≥3.9) e PHP (≥8.1), todos na versão 1.3.0. Os pacotes são distribuídos por download direto (não estão publicados em npm, PyPI ou Packagist). Em todos eles, informe a baseUrl canônica explicitamente e carregue token e tenant de variáveis de ambiente.
https://docs.foxnfe.com.br/downloads/…. Comandos como npm install foxnfe puro (nome no registry) não são garantidos.
baseUrl: "https://www.foxnfe.com.br/api/v1" (Node), base_url=… (Python) ou o segundo argumento do construtor (PHP) para fixar a URL canônica.
Node.js (≥ 18)
Instale a partir do tarball oficial e crie o cliente com a URL base explícita. As classes de módulo (Nfe, Nfse, Webhook, Reference, Documents, NfeEvents, Distribuicao, Rtc, Support) recebem o cliente no construtor.
# A partir do download oficial (recomendado)
npm install https://docs.foxnfe.com.br/downloads/foxnfe-1.3.0.tgz
# Ou a partir do arquivo local entregue à sua equipe
npm install ./foxnfe-1.3.0.tgz
import { createClient, Webhook } from 'foxnfe';
const client = createClient({
tenantSlug: process.env.FOXNFE_TENANT, // slug, não UUID
token: process.env.FOXNFE_TOKEN, // token do cofre/env
baseUrl: 'https://www.foxnfe.com.br/api/v1',
});
// Chamadas de leitura para validar a integração:
const me = await client.me();
// A resposta é aninhada: { user: { …, tenant_id }, tenant: { id, slug, name, status }, subscription, abilities }
console.log('tenant (slug):', me.tenant?.slug ?? '(sem tenant)');
console.log('tenant_id:', me.user?.tenant_id ?? '(sem tenant)');
const webhook = new Webhook(client);
const cfg = await webhook.getConfig();
console.log('webhook configurado?', cfg.configured);
Python (≥ 3.9)
Instale a wheel oficial e instancie Client com base_url. Os módulos são expostos como propriedades (client.nfe, client.nfse, client.webhook, client.reference, client.documents, client.nfe_events, client.distribuicao, client.rtc, client.support).
pip install https://docs.foxnfe.com.br/downloads/foxnfe-1.3.0-py3-none-any.whl
# Ou local:
pip install ./foxnfe-1.3.0-py3-none-any.whl
import os
from foxnfe import Client
client = Client(
tenant_slug=os.environ["FOXNFE_TENANT"], # slug, não UUID
token=os.environ["FOXNFE_TOKEN"], # token do cofre/env
base_url="https://www.foxnfe.com.br/api/v1",
)
me = client.me()
# Resposta aninhada: {"user": {..., "tenant_id"}, "tenant": {"id", "slug", ...}, ...}
print("tenant (slug):", (me.get("tenant") or {}).get("slug", "(sem tenant)"))
print("tenant_id:", (me.get("user") or {}).get("tenant_id", "(sem tenant)"))
cfg = client.webhook.get_config()
print("webhook configurado?", cfg.get("configured"))
PHP (≥ 8.1)
O pacote depende de Guzzle. Aponte um repositório Composer do tipo package para o zip oficial e então instale por composer require.
{
"repositories": [
{
"type": "package",
"package": {
"name": "foxdigital/foxnfe-php",
"version": "1.3.0",
"dist": {
"type": "zip",
"url": "https://docs.foxnfe.com.br/downloads/foxnfe-php-1.3.0.zip"
},
"require": { "php": ">=8.1", "guzzlehttp/guzzle": "^7.0" },
"autoload": { "psr-4": { "FoxNfe\\": "src/" } }
}
}
]
}
composer require foxdigital/foxnfe-php:1.3.0
<?php
require 'vendor/autoload.php';
use FoxNfe\Client;
$client = (new Client(
tenantSlug: getenv('FOXNFE_TENANT'), // slug, não UUID
baseUrl: 'https://www.foxnfe.com.br/api/v1',
))->withToken(getenv('FOXNFE_TOKEN')); // token do cofre/env
$me = $client->me();
// Resposta aninhada: ['user' => [..., 'tenant_id'], 'tenant' => ['id','slug',...], ...]
echo 'tenant (slug): ' . ($me['tenant']['slug'] ?? '(sem tenant)') . PHP_EOL;
echo 'tenant_id: ' . ($me['user']['tenant_id'] ?? '(sem tenant)') . PHP_EOL;
$cfg = $client->webhook()->getConfig();
echo 'webhook configurado? ' . var_export($cfg['configured'] ?? false, true) . PHP_EOL;
foxnfe 1.3.0 e substitui apenas o transporte HTTP por um mock — nenhuma chamada de rede, SMTP ou emissão. Eles exercitam o cliente (me(), webhook.getConfig()) e a verificação de assinatura HMAC sobre o corpo canônico, servindo de base segura para os seus próprios scripts de leitura.
- Node.js consumer.mjs — importa
foxnfe, mock defetch - Python consumer.py — importa
foxnfe, adapterrequestssimulado - PHP consumer.php — usa
FoxNfe\Client, handler Guzzle simulado
Os contratos dos módulos novos (referência, documentos, CC-e, recebidos, catálogo de webhooks) têm demos offline distribuídos junto de cada pacote: examples/reference_documents_offline_demo.{mjs,py,php}.
Webhooks #
Os eventos chegam como CloudEvents v1.0 assinados por HMAC-SHA256. Cada entrega traz o cabeçalho X-Webhook-Signature: sha256=<hex>, calculado sobre os bytes exatos do corpo. Verifique a assinatura sobre o corpo cru recebido, com comparação em tempo constante, e use o id do CloudEvent para deduplicar. Este é o domínio já revisado e pronto para integração.
Configurar o webhook
A configuração é por tenant. O segredo deve ser definido por você: gere um valor aleatório de alta entropia, envie-o em webhook_secret e guarde a mesma cópia na sua integração — é ela que valida a assinatura. Ao omitir webhook_secret numa atualização, o valor atual é preservado; o DELETE apenas desativa o webhook (active: false) e preserva o segredo para reativação por um novo PUT. A API nunca devolve o segredo: tanto GET quanto PUT retornam apenas secret_preview mascarado.
| Operação | Método / rota | Notas |
|---|---|---|
| Consultar | GET /webhooks/config | Retorna configured e, se houver, webhook_url + secret_preview + active. Não retorna o segredo. |
| Criar/atualizar | PUT /webhooks/config | Corpo { webhook_url, webhook_secret? }. URL deve ser HTTPS pública; hosts privados/loopback são rejeitados (anti-SSRF) com 422. |
| Desativar | DELETE /webhooks/config | Preserva o histórico; não há exclusão silenciosa das entregas. |
webhook_secret um valor aleatório de pelo menos 16 caracteres, conhecido só pela sua integração — é o que permite validar o HMAC. Se você não enviar um segredo, o servidor gera um automaticamente no primeiro cadastro, mas esse valor nunca é retornado (a resposta traz apenas o preview mascarado): sem conhecê-lo, você não consegue verificar a assinatura. Nesse caso, faça um PUT com um webhook_secret seu antes de começar a verificar. Para rotacionar, basta um novo PUT com o novo valor.
Envelope CloudEvents v1.0
Cada evento é um CloudEvent no formato abaixo. O id é estável e coincide com o delivery_id — use-o para deduplicação idempotente no consumidor. O catálogo vigente, com os campos garantidos em data, está em GET /webhooks/events (também nos SDKs: webhook.events()).
{
"specversion": "1.0",
"id": "550e8400-e29b-41d4-a716-446655440000",
"source": "https://foxnfe.com.br",
"type": "br.com.centralfox.foxnfe.nfe.authorized",
"datacontenttype": "application/json",
"time": "2026-09-05T12:00:00-03:00",
"data": { "nfe_id": 123, "status": "authorized" }
}
Tipo (após o prefixo br.com.centralfox.foxnfe.) | Quando |
|---|---|
nfe.authorized | NF-e autorizada pela SEFAZ |
nfe.rejected | NF-e rejeitada pela SEFAZ |
nfe.cancelled | NF-e cancelada |
nfse.authorized | NFSe autorizada |
nfse.rejected | NFSe rejeitada |
nfe.test | Evento sintético de teste (não emite documento) |
tenant.registered | Tenant cadastrado |
onboarding.completed | Onboarding concluído |
certificate.validated | Certificado A1 validado |
nfe.contingency | NF-e estacionada em contingência durável (não transmitida) |
nfe.cce.registered / nfe.cce.rejected | Carta de Correção registrada (cStat 135) ou rejeitada de forma definitiva |
nfe.received | NF-e destinada ao seu CNPJ recebida pela distribuição (resumo ou XML integral) |
nfe.manifestacao.transmitted / nfe.manifestacao.rejected | Manifestação do destinatário registrada ou rejeitada pela SEFAZ |
document.imported | XML de terceiros importado para a Central de Documentos |
nfse.cancelled / nfse.substituted | NFS-e Nacional cancelada ou substituída |
certificate.expiring / certificate.expired | Certificado A1 ativo perto do vencimento ou expirado |
nfe.ator.registered / nfe.ator.rejected | Ator interessado (110150) registrado ou rejeitado de forma definitiva |
nfe.insucesso.registered / nfe.insucesso.rejected | Insucesso na entrega (110192) ou seu cancelamento (110193) registrado ou rejeitado |
nfe.inutilizacao.registered / nfe.inutilizacao.rejected | Inutilização de faixa de numeração homologada (cStat 102/206) ou rejeitada |
nfe.econf.registered / nfe.econf.rejected | Conciliação financeira (110750) ou seu cancelamento (110751) registrado ou rejeitado |
nfe.rtc.registered / nfe.rtc.rejected | Evento RTC de IBS/CBS (NT 2025.002) ou cancelamento de evento (110001) registrado ou rejeitado — nota própria ou recebida |
Verificar a assinatura (HMAC-SHA256)
Verifique sobre o corpo cru, sem reserializar. A assinatura é o HMAC-SHA256 dos bytes exatos enviados (JSON canônico, com chaves ordenadas). Se você reparsear e reserializar o JSON antes de verificar, a ordem das chaves pode mudar e a assinatura deixa de bater. Compare em tempo constante.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
// IMPORTANTE: capture o corpo CRU; não use express.json() antes de verificar.
app.use('/hooks/foxnfe', express.raw({ type: '*/*' }));
app.post('/hooks/foxnfe', (req, res) => {
const secret = process.env.FOXNFE_WEBHOOK_SECRET;
const header = req.get('X-Webhook-Signature') || '';
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(req.body) // Buffer cru, exatamente como recebido
.digest('hex');
const a = Buffer.from(header);
const b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).end(); // assinatura inválida
}
const event = JSON.parse(req.body.toString('utf8')); // só depois de verificar
// deduplicar por event.id antes de processar
res.status(204).end();
});
import hmac, hashlib, os, json
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/hooks/foxnfe")
def receive():
secret = os.environ["FOXNFE_WEBHOOK_SECRET"].encode()
body = request.get_data() # bytes crus, sem reparse
expected = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest()
header = request.headers.get("X-Webhook-Signature", "")
if not hmac.compare_digest(expected, header): # tempo constante
abort(401)
event = json.loads(body) # só depois de verificar
# deduplicar por event["id"] antes de processar
return "", 204
<?php
$secret = getenv('FOXNFE_WEBHOOK_SECRET');
$body = file_get_contents('php://input'); // corpo cru, sem reparse
$header = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
if (!hash_equals($expected, $header)) { // tempo constante
http_response_code(401);
exit;
}
$event = json_decode($body, true); // só depois de verificar
// deduplicar por $event['id'] antes de processar
http_response_code(204);
Entregas, tentativas e reenvio
Cada entrega é tentada até 3 vezes, com espera de 30 s após a primeira falha e 300 s após a segunda. O histórico paginado (15 por página) fica em GET /webhooks/deliveries. Uma rotina de reconciliação recoloca na fila apenas entregas ainda não tentadas, presas entre o commit e o enfileiramento, com idade mínima de 5 minutos — entregas já falhadas ou em backoff nunca são redirigidas por ela.
id do CloudEvent (que é persistente) e responda rápido (2xx) para confirmar o recebimento.
Para reenviar manualmente uma entrega em falha terminal, use POST /webhooks/deliveries/{delivery_id}/resend. A operação é auditada (ator, tenant, data e tentativas anteriores) antes de zerar o contador. Os códigos abaixo vêm do controlador atual:
| Código | Significado |
|---|---|
202 | Reenfileirada; o job é despachado após o commit para evitar duplo enfileiramento. |
404 | UUID inválido, ou entrega inexistente/pertencente a outro tenant (não distingue, para não vazar existência). |
409 | Entrega já realizada com sucesso. |
422 | Entrega não está em falha terminal (ainda pendente); nada é reenfileirado. |
429 | Limite de 10 requisições por minuto atingido. |
503 | Não foi possível confirmar o enfileiramento; a falha fica recuperável e auditada. Consulte o histórico antes de repetir. |
O endpoint POST /webhooks/test enfileira um evento sintético nfe.test para validar URL, assinatura e parsing sem emitir documento real (202; 422 se não houver webhook ativo; 429 no limite de taxa).
Consultas auxiliares #
Catálogos oficiais para preencher cadastros sem digitar código errado. Todas as rotas ficam sob /reference-data, aceitam qualquer token do tenant com ability reference:read (ou fiscal) e devolvem source com origem, versão, hash e datas reais. São consultas de preenchimento — nunca orientação tributária.
| Rota | Conteúdo | Notas |
|---|---|---|
GET /reference-data/municipalities[/{ibge}] | Municípios IBGE (5.571) | Filtros q, uf; código de 7 dígitos. |
GET /reference-data/cfop[/{code}] | CFOP consolidado | selectable distingue operações de cabeçalhos; effective_at ≠ publicação. |
GET /reference-data/cnae[/{code}] | CNAE 2.0 subclasses | 7 dígitos. |
GET /reference-data/ncm[/{code}] | NCM vigente | Só o código de 8 dígitos é selecionável; os demais são hierarquia. |
GET /reference-data/servicos-nacionais[/{cTribNac}] | Lista de Serviços Nacional (NFS-e) | 6 dígitos selecionáveis; cabeçalhos item/subitem vêm com code_synthetic=true. Não converte para código municipal. |
GET /reference-data/nbs[/{code}] | NBS 2.0 | 9 dígitos selecionáveis; 5/6 são posição/subposição. |
GET /reference-data/cep/{cep} | Endereço por CEP | 404 inexistente; 503 provedor indisponível (nunca cacheado); municipality_known cruza o IBGE devolvido com o catálogo local. |
GET /reference-data/cnpj/{cnpj} | Dados cadastrais públicos | Dígitos verificadores conferidos antes de consultar (422); somente empresa, endereço e atividade principal; atualidade é a do provedor. |
Paginação: page e per_page (máximo 100). Entrada inválida responde 422; catálogo não provisionado responde 503 tipado, nunca uma amostra apresentada como catálogo completo.
Central de Documentos #
Emitidas, recebidas e importadas num único índice, com exportação verificável. A importação de XML de terceiros tem prévia sem persistência e commit amarrado ao hash dos mesmos bytes; documento importado nunca vira "autorizado pelo FoxNFe".
| Operação | Método / rota | Notas |
|---|---|---|
| Consultar | GET /documents | Filtros de período, origem (emitida, recebida, importada), tipo, status e participante; sort, page, per_page. |
| Detalhe | GET /documents/{source}/{id} | source é a origem física (nfes, nfses, nfe_distribuicoes, imported_documents). |
| Prévia de importação | POST /documents/imports/preview | multipart, campo file (XML UTF-8 até 2 MiB, modelos 55/65). Nada é gravado; devolve integrity.raw_sha256. |
| Importar | POST /documents/imports | multipart file + preview_sha256 opcional. 201 criado, 200 deduplicado, 409 mesma chave com bytes diferentes, 422 inválido. Emite document.imported. |
| Exportar | POST /documents/exports → GET /documents/exports/{id} → GET …/download | ZIP assíncrono com manifesto de hashes e XMLs originais; link temporário. |
SDKs: documents.importPreview(), documents.import(), documents.exportRequest() (Node/PHP) e client.documents.import_preview(), import_xml(), export_request() (Python).
NF-e recebidas, manifestação e Carta de Correção #
Nada é transmitido à SEFAZ sem aprovação explícita e identidade conferida. A manifestação segue solicitar → aprovar (com o payload_hash devolvido) → transmitir; uma resposta ambígua exige reconciliar por consulta real, nunca reenvio cego. A CC-e é assíncrona e durável (no máximo 20 por NF-e).
| Operação | Método / rota | Notas |
|---|---|---|
| Recebidas | GET /nfe-distribuicao, GET /nfe-distribuicao/cursor | Documentos por NSU e o cursor por CNPJ/ambiente. Evento nfe.received por documento novo. |
| Captura | POST /nfe-distribuicao/capturar; GET/PUT/DELETE …/captura-automatica | Captura manual limitada ou opt-in automático por certificado e ambiente. |
| Manifestação | POST /nfe-distribuicao/manifestacoes → /{id}/aprovar → /{id}/transmitir → /{id}/reconciliar | Tipos 210200, 210210, 210220, 210240 (este exige justificativa). Eventos nfe.manifestacao.transmitted / rejected. |
| Carta de Correção | GET/POST /nfe/{id}/cce | Corpo { correcao (15..1000), sequencial? (1..20) }. Eventos nfe.cce.registered / rejected. |
| Ator interessado | GET/POST /nfe/{id}/ator-interessado | Evento 110150. Corpo { documento (CPF/CNPJ), tp_autor?, tp_autorizacao?, sequencial? }. Eventos nfe.ator.registered / rejected. |
| Insucesso na entrega | GET/POST /nfe/{id}/insucesso-entrega; POST …/{evento}/cancelar | Eventos 110192/110193. Corpo { dh_tentativa, tp_motivo (1..4), justificativa? (obrigatória se 4), n_tentativa?, latitude?, longitude?, imagem_base64? }; a imagem não é persistida (só o hash). |
| Inutilização | GET/POST /nfe/inutilizacoes, GET …/{id} | Corpo { serie, numero_inicial, numero_final, justificativa (15..255), modelo?, ano? }. Assíncrona e durável; eventos nfe.inutilizacao.*. |
| Eventos por contrato | GET /nfe/eventos/contratos; GET/POST /nfe/{id}/eventos; GET/POST /nfe/recebidas/{distribuicao}/eventos | Catálogo oficial (conciliação financeira 110750/110751, cancelamento genérico, eventos RTC de IBS/CBS) com campos, autor (emitente/adquirente) e validação local antes da SEFAZ. Corpo { tipo, sequencial?, …campos do contrato }. |
Homologação por modelo e rejeições explicadas #
Cada rejeição da SEFAZ chega com o texto oficial e a ação que resolve. A consulta da NF-e traz rejection com code, reason (xMotivo intacto), categoria, acao (corrigir_dados, certificado, reenviar, consultar, suporte) e dica. A homologação por modelo gera amostras XML/PDF assinadas com o seu certificado sem transmitir nada e sem consumir numeração.
| Operação | Método / rota | Notas |
|---|---|---|
| Catálogo de rejeições | GET /nfe/rejeicoes, GET /nfe/rejeicoes/{cstat} | Códigos fora do catálogo são classificados só pela faixa; nunca inventamos motivo. |
| Matriz de homologação | GET /nfe/homologacao | Cenários por modelo (55: venda simples, multi-itens, destinatário contribuinte, payload inválido; 65: consumidor CPF, multi-pagamentos, payload inválido), UF e últimas corridas. |
| Executar | POST /nfe/homologacao/run | Corpo { modelo (55|65), cenario?, mode? }. simulated (padrão): protocolo sintético marcado, numeração própria (999000001+), nada em nfes. sefaz: transmissão real ao ambiente de homologação, só com autorização no servidor (409 external_disabled). Limite 5/min. |
| Amostras | GET /nfe/homologacao/{id}/xml, …/pdf | nfeProc com protocolo sintético (verAplic=FOXNFE_HOMOLOG_SIMULADA) e DANFE/DANFCe; cabeçalho X-FoxNFe-Homolog: simulated. |
SDKs: nfe.rejeicao(), nfe.homologacaoRun(), nfe.homologacaoXml()/Pdf() (Node/PHP) e client.nfe.rejeicao(), homologacao_run() (Python).
Reforma tributária: IBS/CBS reproduzível #
Nenhum valor de IBS/CBS é estimado. A resolução usa a calculadora oficial sobre a parametrização aprovada pela sua empresa; sem regra aprovada a API bloqueia com um code estável. Toda resolução pode ser explicada e reexecutada; cenários versionados detectam deriva quando fontes ou regras mudam.
| Operação | Método / rota | Notas |
|---|---|---|
| Status e classificações | GET /fiscal-agent/rtc/status, GET /fiscal-agent/rtc/classifications?dfe_type=&cst= | Versão da calculadora e catálogo de classificações vigentes. |
| Resolver | POST /fiscal-agent/rtc/resolve | Corpo { sku, dfe_type (NFE|NFCE|NFSE), tax_date, item_number, calculator_payload, … }. 422 com code: rule_missing, rule_ambiguous, classification_invalid, catalog_mismatch, payload_invalid. Os grupos oficiais (regular, monofasia, transferência de crédito, ajuste de competência, estorno, crédito presumido, compra governamental) entram no XML da NF-e por rtc_resolution_id no item. |
| Explicar e verificar | GET /fiscal-agent/rtc/resolutions/{id}, POST …/{id}/verify | Regra, perfis, catálogo, versões e digest; verificação devolve reproducible, output_drift, version_drift ou unavailable. |
| Cenários | GET/POST /rtc-parameterization/scenarios, POST …/run, POST …/{id}/run, DELETE …/{id} | Linha de base congelada na criação; cada execução devolve match, drift_classification, drift_output, version_drift ou rule_missing. |
SDKs: classe Rtc (Node/PHP) e client.rtc (Python).
Cobertura NFS-e por município e provedor #
Publicamos só o que os drivers e os testes provam. Para cada município: provedor, tipo de integração, adesão ao padrão Nacional e o status de emitir, consultar, cancelar, substituir e consultar_lote (suportada, composta, nao_oferecida, nao_implementada), cada um com o arquivo de teste que o prova. Quando o provedor não tem integração, a emissão é recusada antes da rede (422) e cancelamento/substituição respondem 409 operacao_nao_oferecida.
| Operação | Método / rota | Notas |
|---|---|---|
| Municípios | GET /nfse/cobertura?uf=&q=&provider=&page= | 50 por página, ordenado por UF e nome. |
| Detalhe | GET /nfse/cobertura/{ibge} | Capacidades com status, prova e nota por operação; homologacao_externa sempre pendente_autorizacao até o ciclo real. |
| Provedores | GET /nfse/cobertura/provedores | Matriz por provedor e mapa de provas. |
SDKs: nfse.cobertura(), nfse.coberturaMunicipio(), nfse.coberturaProvedores() (Node/PHP) e client.nfse.cobertura_municipio() (Python).
Status do serviço e casos de suporte #
| Operação | Método / rota | Notas |
|---|---|---|
| Status | GET /status (sem token) e página /status | Componentes (banco, filas, cofre, disco, HTTP) com latência, incidentes abertos e simulações de contingência. Cache de 30 s. |
| Casos de suporte | GET/POST /support/cases, GET …/{id}, GET …/sla | Corpo { subject (3..200), priority? (urgent|high|normal|low), channel? }. SLA de primeira resposta/resolução por prioridade e aderência medida. |
SDKs: classe Support (Node/PHP) e client.support (Python).
Estado dos domínios fiscais #
Publicamos somente o que está implantado e testado. Autenticação, webhooks, NF-e/NFC-e (com CC-e e contingência SVC), NFS-e Nacional, recebidas/manifestação, Central de Documentos e consultas auxiliares têm contrato disponível nesta página e nos SDKs 1.3.0. CT-e, MDF-e, NFCom, DC-e, NFGás e RetailSync têm contrato publicado em revisão — build, assinatura e validação contra o XSD oficial são reais, mas nenhum dos seis ainda transmite à SEFAZ; veja Expansão em revisão.
| Domínio | Disponibilidade | O que isso significa para você |
|---|---|---|
| Webhooks (CloudEvents + HMAC) | Disponível | Contrato publicado e documentado — veja Webhooks. |
| Autenticação e ambientes | Disponível | Cabeçalhos e fluxo documentados — veja Autenticação e ambientes. Os endpoints exigem autenticação (respondem 401 sem credenciais) sobre TLS. |
| NF-e / NFC-e | Disponível | Emissão assíncrona, consulta, XML/DANFE, cancelamento, Carta de Correção (nfe/{id}/cce), recuperação durável e contingência SVC-AN/SVC-RS (modelo 55). EPEC/FS-DA e NFC-e off-line ficam estacionados e sinalizados por nfe.contingency. Outros eventos (ator interessado, insucesso na entrega, inutilização, conciliação financeira e eventos RTC), rejeições explicadas e homologação simulada por modelo — veja Homologação e rejeições. |
| NFS-e (Padrão Nacional) | Disponível | Emissão, consulta, cancelamento e substituição com recuperação durável; cobertura por município/provedor é declarada, não presumida. Matriz pública de cobertura e recusa explícita antes da rede — veja Cobertura NFS-e. |
| NF-e recebidas e manifestação | Disponível | Distribuição DF-e com cursor, captura automática e manifestação do destinatário — veja NF-e recebidas. |
| Central de Documentos | Disponível | Consulta unificada, importação de XML com prévia e exportação ZIP — veja Central de Documentos. |
| Consultas auxiliares | Disponível | Municípios, CFOP, CNAE, NCM, serviços nacionais, NBS, CEP e CNPJ — veja Consultas auxiliares. |
| Reforma tributária (IBS/CBS) | Disponível | Resolução oficial, explicação, verificação de reprodutibilidade e cenários — veja Reforma tributária. |
| Status e suporte | Disponível | Status público com incidentes e casos de suporte com SLA — veja Status e suporte. |
| CT-e, MDF-e, NFCom, DC-e, NFGás, RetailSync | Parcial | Build, assinatura e validação XSD reais; sem transmissão SEFAZ ainda. Não integre em produção contra eles até saírem de Parcial — veja Expansão em revisão. |
Homologação fiscal externa com certificado real é responsabilidade de cada integrador no ambiente de homologação (ambiente=2); esta página não substitui essa validação. Ela é atualizada a cada domínio que passa a disponível.
Expansão em revisão: CT-e, MDF-e, NFCom, DC-e, NFGás, RetailSync #
Seis domínios novos, todos com o mesmo limite hoje: build, assinatura ICP-Brasil e validação contra o XSD oficial são reais e testados; nenhum transmite à SEFAZ ainda. O documento gerado é assinado e válido contra o schema publicado do modelo correspondente, mas fica em status=validated — nunca authorized. Não use estas rotas para emissão fiscal real até a transmissão ser habilitada e anunciada aqui.
| Domínio | Modelo | Rota | Nota |
|---|---|---|---|
| CT-e | 57 (rodoviário) | POST /ctes, GET /ctes/{id} | Build/assinatura/validação XSD reais. |
| MDF-e | 58 (rodoviário) | POST /mdfes, GET /mdfes/{id} | Build/assinatura/validação XSD reais. |
| NFCom | 62 | POST /nfcoms, GET /nfcoms/{id} | Um item por nota; ICMS via indSemCST. |
| DC-e | 99 | POST /dces, GET /dces/{id} | Emitente/destinatário por CNPJ; transporte Correios. |
| NFGás | 76 | POST /nfgas, GET /nfgas/{id} | Chave de acesso alfanumérica (CNPJ letras+números, IN RFB 2.229/2024) suportada nativamente. |
| RetailSync | não fiscal | POST /retail-sync/events, GET /retail-sync/manifest | Ingestão idempotente de eventos de varejo offline (chave de retomada durável, sem expirar) e manifesto assinado por HMAC-SHA256. Não é um documento fiscal — reserva numeração modelo 65 para reconciliação posterior. |
Todas as seis rotas exigem ability:nfes:emit e seguem o mesmo ciclo pending → processing → validated|rejected das demais emissões — nunca authorized nesta fase. Novidades por capacidade: veja Novidades.
Guias práticos #
Certificado A1
A emissão exige um certificado digital A1 (ICP-Brasil) válido, referenciado por certificate_id no payload. O certificado é carregado e validado no servidor; o evento certificate.validated sinaliza quando um certificado passou pela validação. Nunca envie o arquivo .pfx/.p12 nem a senha do certificado pelo front-end ou por canais de log.
Erros e novas tentativas
Erros de validação retornam 422 com um mapa errors por campo; falhas de autenticação, 401 ou 403. Para chamadas de escrita com resposta ambígua (timeout ou 5xx), não reemita cegamente: consulte o status do recurso e decida a partir do estado real. Respeite os limites de taxa (429) com espera antes de repetir.
Envios confirmados
Uma emissão só está confirmada quando o recurso sai de pending. Confirme por um destes caminhos: o webhook nfe.authorized/nfse.authorized, ou o polling do recurso até que status deixe de ser pending. O 202 inicial nunca é confirmação de autorização.
Reconciliação de entregas
Para garantir que nenhum evento foi perdido, cruze o seu registro local de eventos recebidos (deduplicados por id) com o histórico em GET /webhooks/deliveries. Entregas em falha terminal podem ser reenviadas manualmente pelo endpoint de resend; entregas presas antes da primeira tentativa são recolocadas na fila pela rotina interna de reconciliação. Como o modelo é at-least-once, a deduplicação no consumidor é parte obrigatória da integração.
Suporte #
Dúvidas de integração podem ser encaminhadas pelos canais oficiais da Central Fox:
- WhatsApp: +55 85 99101-0707
- E-mail de desenvolvimento: dev@centralfox.online