Documentação Técnica para Desenvolvedores — DFe Inbound (nfe.io)
Produto: DFe Inbound — Recepção de NF-e e CT-e Público: Desenvolvedores que integram sistemas com a API nfe.io Versão da API: v2 Atualizado em: 2026-05-07
📑 Este documento cobre os subsistemas NF-e e CT-e. Para o subsistema NFS-e Recebidas (captura via ADN — Ambiente de Dados Nacional), consulte:
- 02-doc-tecnica-clientes-dev-nfse-inbound-api.md — referência completa de endpoints REST do NFS-e
- 02-doc-tecnica-clientes-dev-nfse-inbound-webhook.md — contrato dos webhooks do NFS-e
Para a visão geral dos três subsistemas (NF-e, CT-e e NFS-e), abra README.md e 03-doc-funcional-clientes.md.
Fontes autoritativas
A API é descrita em três artefatos sincronizados. Em caso de divergência, o OpenAPI é a fonte canônica do contrato:
| Artefato | Para quê | Conteúdo |
|---|---|---|
openapi-inbound-api.yaml (PT) / .en.yaml (EN) | Contrato da API | Schemas de request e response com example por schema, parâmetros, autenticação, todos os códigos de status (200/201/202/302/400/401/403/404/409/422/500). Importável em Swagger UI, Postman e geradores de cliente. |
DFeDistribution-Inbound-API-Full.postman_collection.json (PT) / .en.postman_collection.json (EN) | Testes manuais | 55 endpoints com request body de exemplo + response[] populado (200, 202, 400, 404 conforme aplicável). Visível na aba Examples de cada request no Postman. |
| Este Markdown | Guia de integração | Visão narrativa dos fluxos NF-e/CT-e, dicas de implementação, troubleshooting e exemplos em código (curl/Python/Node/C#). |
Para testes manuais, importe a Postman collection acima — todos os 55 endpoints já trazem exemplo de resposta salvo (visível na aba Examples de cada request no Postman).
Índice
- Introdução
- Guia de Primeiros Passos
- Conceitos Fundamentais
- Autenticação
- URL Base e Ambientes
- Ativando o Serviço de Inbound
- Endpoints de NF-e
- Endpoints de CT-e
- Listagem Paginada via OData (NF-e e CT-e)
- Webhooks — Recebendo Notificações
- Referência de Tipos e Enums
- Tratamento de Erros
- Exemplos de Integração
- Perguntas Frequentes (FAQ)
1. Introdução
O DFe Inbound da nfe.io é um serviço que monitora automaticamente a SEFAZ e captura todos os Documentos Fiscais Eletrônicos (NF-e e CT-e) emitidos por terceiros que têm como destinatário o CNPJ da sua empresa.
O que você ganha com este serviço?
- Recepção automática: Sem precisar acessar o portal da SEFAZ manualmente.
- XML armazenado: Os XMLs ficam disponíveis para download via API a qualquer momento.
- Notificações em tempo real: Receba webhooks assim que um documento chegar.
- Dados estruturados: Consulte metadados (emitente, valor, data) sem precisar parsear XML.
- Histórico completo: Acesso a documentos recebidos nos últimos 90 dias (ou desde o NSU configurado).
Visão Geral da Integração
SEFAZ (Ambiente Nacional)
│
│ (Polling automático via NSU)
▼
nfe.io DFe Inbound
│
├──▶ Armazena XML no cloud storage
├──▶ Salva metadados (emitente, valor, data...)
└──▶ Dispara Webhook ──▶ SEU SISTEMA
│
├── Consulta metadados via API
└── Baixa XML via API
2. Guia de Primeiros Passos
Se você está integrando pela primeira vez, siga esta sequência em ordem. Não pule etapas.
Etapa 1 — Obter credenciais
No painel nfe.io, crie uma API Key em Configurações → Chaves de API. Guarde-a com segurança — ela só é exibida uma vez.
Etapa 2 — Localizar seu company_id
Em Empresas, clique na empresa que deseja integrar e copie o company_id exibido na URL ou nos detalhes da empresa.
Etapa 3 — Ativar o inbound
Faça uma requisição para ativar o monitoramento do CNPJ:
curl -X POST \
"https://api.nfse.io/v2/companies/SEU_COMPANY_ID/inbound/productinvoices" \
--header "Authorization: SUA_API_KEY" \
--header "Content-Type: application/json" \
-d '{
"StartFromNsu": 0,
"EnvironmentSEFAZ": "Production",
"WebhookVersion": 2
}'
Resposta esperada: { "status": "Active", ... }
Etapa 4 — Configurar seu endpoint de webhook
No painel nfe.io, vá em Webhooks e cadastre a URL do seu servidor que vai receber as notificações.
Mínimo necessário no seu endpoint:
# Python (Flask)
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.json
access_key = data['accessKey']
# Processar...
return '', 200 # OBRIGATÓRIO retornar 200
Etapa 5 — Verificar que documentos chegam
Após ativar, aguarde entre 15 minutos e 4 horas. Consulte documentos recebidos:
curl "https://api.nfse.io/v2/companies/SEU_COMPANY_ID/inbound/productinvoices/CHAVE_44_DIGITOS" \
--header "Authorization: SUA_API_KEY"
Etapa 6 — Migrar para produção
Quando seus testes estiverem OK com "EnvironmentSEFAZ": "Test", recrie a configuração com "Production":
# 1. Desativar o inbound de homologação
curl -X DELETE \
"https://api.nfse.io/v2/companies/SEU_COMPANY_ID/inbound/productinvoices" \
--header "Authorization: SUA_API_KEY"
# 2. Ativar com ambiente de produção
curl -X POST \
"https://api.nfse.io/v2/companies/SEU_COMPANY_ID/inbound/productinvoices" \
--header "Authorization: SUA_API_KEY" \
--header "Content-Type: application/json" \
-d '{
"StartFromNsu": 0,
"EnvironmentSEFAZ": "Production",
"WebhookVersion": 2
}'
Importante: Documentos de ambiente
Test(homologação SEFAZ) eProductionsão separados. Nunca misture os dois na mesma configuração.
3. Conceitos Fundamentais
NSU — Número Sequencial Único
O NSU é um número mantido pela SEFAZ que cresce a cada documento recebido pelo seu CNPJ. Pense nele como um "contador de correspondências" da SEFAZ para a sua empresa.
Exemplo:
- NSU 1000: NF-e de fornecedor A, valor R$ 500
- NSU 1001: NF-e de fornecedor B, valor R$ 1.200
- NSU 1002: Evento de cancelamento da NF-e do fornecedor A
Nosso serviço consulta a SEFAZ periodicamente e baixa todos os documentos a partir do último NSU processado.
Chave de Acesso (access_key)
É um código de 44 dígitos que identifica unicamente uma NF-e ou CT-e. Exemplo:
35240112345678000195550010000012341234567890
│ │ │ │ │ │
│ │ │ │ │ └─── Dígito verificador
│ │ │ │ └────── Número NF-e
│ │ │ └─────────── Série
│ │ └────────────────────────── CNPJ emitente
│ └──────────────────────────── Mês/Ano emissão (AAAAMM)
└─────────────────────────────── UF (35 = SP)
Para eventos (cancelamento, ciência, etc.), a chave tem 55 dígitos.
Identificando o tipo de documento pela chave
A posição 20-21 da chave de acesso (0-indexed) indica o modelo do documento:
| Posição 20-21 | Tipo | Descrição |
|---|---|---|
55 | NF-e | Nota Fiscal Eletrônica |
57 | CT-e | Conhecimento de Transporte Eletrônico |
65 | NFC-e | Nota Fiscal de Consumidor |
67 | CT-eOS | CT-e para Outros Serviços |
def get_document_type(access_key: str) -> str:
model = access_key[20:22] # posições 20 e 21
types = {"55": "NF-e", "57": "CT-e", "65": "NFC-e", "67": "CT-eOS"}
return types.get(model, "Desconhecido")
Tipos de Registro retornados pela API
| MetadataResourceType | Significado |
|---|---|
productInvoice | NF-e completa (XML disponível) |
productInvoiceEvent | Evento de NF-e (cancelamento, ciência, etc.) |
productInvoiceSummary | Resumo de NF-e (apenas metadados básicos) |
productInvoiceEventSummary | Resumo de evento |
transportationInvoice | CT-e completo |
transportationInvoiceEvent | Evento de CT-e |
4. Autenticação
Todas as requisições exigem autenticação. Suportamos dois métodos:
Método 1 — API Key (recomendado para integração server-to-server)
Passe a chave de API no header Authorization:
Authorization: SUA_API_KEY_AQUI
Onde obter a API Key: Painel nfe.io → Configurações → Chaves de API.
Método 2 — Bearer Token (JWT)
Use um token JWT gerado via https://id.nfe.io:
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Exemplo de Requisição com Autenticação
curl -X GET \
"https://api.nfse.io/v2/companies/comp_123/inbound/productinvoices" \
--header "Authorization: sk_live_abc123..."
5. URL Base e Ambientes
A API tem uma única URL base para todos os clientes:
https://api.nfse.io
Não existe um host separado para testes. Para emitir/receber documentos em ambiente de homologação da SEFAZ (sem validade fiscal), use o campo EnvironmentSEFAZ: "Test" no corpo da requisição de ativação do inbound — ver §6. O switch entre homologação e produção é feito por configuração, não trocando de host.
Estrutura da URL:
https://api.nfse.io/v2/companies/{company_id}/inbound/...
O company_id é o identificador da sua empresa na plataforma nfe.io (encontrado no Painel → Empresas).
6. Ativando o Serviço de Inbound
Antes de receber documentos, você precisa ativar o serviço de inbound para cada CNPJ que deseja monitorar.
Ativar Inbound de NF-e
POST /v2/companies/{company_id}/inbound/productinvoices
Authorization: {api_key}
Content-Type: application/json
{
"StartFromNsu": 0,
"EnvironmentSEFAZ": "Production",
"AutomaticManifesting": {
"MinutesToWaitAwarenessOperation": 60
},
"WebhookVersion": 2
}
Campos do corpo:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
StartFromNsu | long | Não | NSU inicial. 0 = busca desde o início. Use um NSU específico para sincronizar com histórico |
EnvironmentSEFAZ | string | Sim | "Production" (produção SEFAZ) ou "Test" (homologação SEFAZ) |
EventType | string | Não | Filtro de tipos de evento (códigos separados por vírgula). null = todos |
Schema | int | Não | 0=NF-e + Eventos, 1=Somente NF-e, 2=Somente Eventos |
AutomaticManifesting.MinutesToWaitAwarenessOperation | int | Não | Minutos aguardados antes de auto-manifestar ciência. Mínimo: 5 |
WebhookVersion | int | Não | Versão do formato de webhook. Use 2 (mais recente) |
Resposta de sucesso (200):
{
"companyId": "comp_123",
"environmentSEFAZ": "Production",
"startFromNsu": 0,
"startFromDate": "2024-01-01T00:00:00Z",
"status": "Active",
"createdOn": "2024-03-15T10:30:00Z"
}
Ativar Inbound de CT-e
POST /v2/companies/{company_id}/inbound/transportationinvoices
Authorization: {api_key}
Content-Type: application/json
{
"StartFromNsu": 0,
"EnvironmentSEFAZ": "Production"
}
Verificar Configuração Atual
GET /v2/companies/{company_id}/inbound/productinvoices
Authorization: {api_key}
Desativar Inbound
DELETE /v2/companies/{company_id}/inbound/productinvoices
Authorization: {api_key}
7. Endpoints de NF-e
Buscar Metadados de uma NF-e
Retorna os dados estruturados de uma NF-e pela sua chave de acesso.
GET /v2/companies/{company_id}/inbound/productinvoices/{access_key}
Authorization: {api_key}
Parâmetros de URL:
company_id: ID da sua empresa na nfe.ioaccess_key: Chave de acesso de 44 dígitos da NF-e
Resposta de sucesso (200):
{
"accessKey": "35240112345678000195550010000012341234567890",
"createdOn": "2024-03-15T14:22:10Z",
"nsu": "21825",
"nsuParent": null,
"nfeNumber": "1234",
"nfeSerialNumber": "1",
"issuedOn": "2024-03-15T10:00:00Z",
"type": "productInvoice",
"description": "Autorizado o uso da NF-e",
"totalInvoiceAmount": "1500.00",
"operationType": "Incoming",
"issuer": {
"federalTaxNumber": "12345678000195",
"name": "Fornecedor LTDA"
},
"buyer": {
"federalTaxNumber": "98765432000100",
"name": "Minha Empresa S.A."
},
"company": {
"id": "comp_123",
"federalTaxNumber": "98765432000100"
},
"links": {
"xml": "https://storage.nfe.io/temp/xml/...",
"pdf": "https://storage.nfe.io/temp/pdf/..."
}
}
Descrição dos campos:
| Campo | Tipo | Descrição |
|---|---|---|
accessKey | string | Chave de acesso 44 dígitos |
createdOn | DateTime | Quando o documento entrou no sistema |
nsu | string | Número Sequencial Único na SEFAZ |
nfeNumber | string | Número da NF-e |
nfeSerialNumber | string | Série da NF-e |
issuedOn | DateTime | Data de emissão |
type | string | productInvoice, productInvoiceEvent, productInvoiceSummary |
description | string | Status da NF-e (ex: "Autorizado o uso da NF-e") |
totalInvoiceAmount | string | Valor total em reais |
operationType | string | Incoming (destinatário) ou Outgoing (emitente) |
issuer | object | Dados do emitente (quem emitiu a NF-e) |
buyer | object | Dados do destinatário (comprador) |
links.xml | string | URL temporária para download do XML (expira em 1 hora) |
links.pdf | string | URL temporária para download do PDF/DANFE |
Baixar XML de uma NF-e
GET /v2/companies/{company_id}/inbound/productinvoices/{access_key}/xml
Authorization: {api_key}
# Retorna o arquivo XML diretamente (Content-Type: application/xml)
Alternativamente, use a URL temporária retornada em links.xml para download direto do storage (sem precisar passar pela API). Esse link expira em 1 hora.
Baixar PDF (DANFE) de uma NF-e
GET /v2/companies/{company_id}/inbound/productinvoices/{access_key}/pdf
Authorization: {api_key}
# Retorna o arquivo PDF (Content-Type: application/pdf)
Buscar Dados de um Evento de NF-e
Eventos são ações sobre a NF-e: cancelamento, ciência, confirmação de operação, etc.
GET /v2/companies/{company_id}/inbound/productinvoices/{access_key}/events/{event_key}
Authorization: {api_key}
O event_key tem 55 dígitos (chave de acesso da NF-e + código do evento + sequência).
Registrar Manifestação
A manifestação é o processo pelo qual o destinatário comunica à SEFAZ que tem conhecimento da NF-e. É obrigatória para NF-es de entrada.
POST /v2/companies/{company_id}/inbound/{access_key}/manifest?tpEvent=210210
Authorization: {api_key}
Tipos de manifestação (tpEvent):
| Código | Tipo | Descrição |
|---|---|---|
210210 | Ciência da Operação | "Estou ciente desta NF-e" (não confirma recebimento físico) |
210200 | Confirmação da Operação | "Recebi a mercadoria conforme NF-e" |
210220 | Desconhecimento da Operação | "Não reconheço esta operação" |
210240 | Operação não Realizada | "A operação não foi concluída" |
Resposta (200):
"Manifestação registrada com sucesso"
Manifestação Automática: Se você configurou
AutomaticManifesting.MinutesToWaitAwarenessOperation, o sistema registrará "Ciência da Operação" automaticamente após esse intervalo. Você não precisa chamar este endpoint para ciência se tiver auto-manifestação ativa.
Reprocessar Webhook de uma NF-e
Use quando o webhook não foi entregue ou precisa ser reenviado.
POST /v2/companies/{company_id}/inbound/productinvoices/{access_key}/processwebhook
Authorization: {api_key}
# Ou por NSU:
POST /v2/companies/{company_id}/inbound/productinvoices/{nsu}/processwebhook
8. Endpoints de CT-e
Ativar e Configurar CT-e
Veja a seção Ativando o Serviço de Inbound — o processo é idêntico ao NF-e mas no endpoint /transportationinvoices.
Buscar Metadados de um CT-e
GET /v2/companies/{company_id}/inbound/{access_key}
Authorization: {api_key}
Resposta (200):
{
"id": "abc123",
"accessKey": "35240198765432000100570010000009871234567890",
"parentAccessKey": "",
"createdOn": "2024-03-15T14:22:10Z",
"nsu": 10042,
"type": "transportationInvoice",
"description": "Autorizado o uso do CT-e",
"company": {
"id": "comp_123",
"federalTaxNumber": "98765432000100"
},
"issuedOn": "2024-03-15T10:00:00Z",
"xmlUrl": "https://api.nfse.io/v2/companies/comp_123/inbound/35240198765432000100570010000009871234567890/xml"
}
Campos do CT-e:
| Campo | Tipo | Descrição |
|---|---|---|
id | string | Id gerado pela plataforma. |
accessKey | string | Chave de acesso 44 dígitos do CT-e. |
parentAccessKey | string | Vazio em respostas REST de CT-e (só populado em eventos). |
createdOn | DateTime | Quando o documento entrou no sistema. |
nsu | long | NSU. Atenção: em CT-e o NSU é numérico (em NF-e é string). |
type | string | Sempre "transportationInvoice" neste endpoint. |
description | string | Status fixo: "Autorizado o uso do CT-e". |
company | object | Sua empresa nfe.io (id, federalTaxNumber). |
issuedOn | DateTime | Data de emissão do CT-e. |
xmlUrl | string | URL para download do XML via API (válida via GET .../xml). |
Observação: dados de emitente (transportadora), destinatário, tomador, expedidor, recebedor e valor total não vêm nesta resposta REST. Esses campos só são entregues no payload do webhook de CT-e (
transportationInvoice/issued_successfully). Para reprocessar o webhook e obter o payload completo, use o endpointprocesswebhookdocumentado abaixo.
Baixar XML de um CT-e
GET /v2/companies/{company_id}/inbound/{access_key}/xml
Authorization: {api_key}
# Retorna o arquivo XML (Content-Type: application/xml)
Reprocessar Webhooks de CT-e
POST /v2/companies/{company_id}/inbound/transportationinvoices/reprocess/webhook
Authorization: {api_key}
Content-Type: application/json
{
"Key": "35240198765432000100570010000009871234567890",
"Date": "2024-03-15"
}
Reprocessar NSUs Específicos
Útil quando um NSU falhou no processamento:
PUT /v2/companies/{company_id}/inbound/transportationinvoices/reprocess/item
Authorization: {api_key}
Content-Type: application/json
[10042, 10043, 10044]
Consolidação de Batch
Consolida batches em um intervalo de NSU:
PUT /v2/companies/{company_id}/inbound/transportationinvoices/batch/consolidation
Authorization: {api_key}
Content-Type: application/json
{
"StartNSU": 10000,
"EndNSU": 10500
}
9. Listagem Paginada via OData (NF-e e CT-e)
Esses endpoints permitem listar documentos em lote com paginação cursor-based. São a forma mais eficiente de processar grandes volumes de documentos fiscais quando você precisa sincronizar um histórico completo ou fazer auditorias.
Existem quatro endpoints OData, todos sob o mesmo prefixo /v2/companies/{company_id}/inbound/odata/:
| Endpoint | Retorna | Filtro obrigatório |
|---|---|---|
ProductInvoices | Listagem de NF-e da sua empresa | environmentType eq {1 ou 2} |
ProductInvoiceEvents | Listagem de eventos de uma NF-e específica | accessKey eq '{chave_44_dígitos}' |
TransportationInvoices | Listagem de CT-e da sua empresa | issuedOn eq {data_ISO} |
TransportationInvoiceEvents | Listagem de eventos de CT-e | receiptOn eq {data_ISO} |
9.1. Autenticação e permissões
- Endpoints
ProductInvoiceseProductInvoiceEventsexigem roleNota Fiscal (api.nfe.io)ouNFeDist (dfe.nfe.io). - Endpoints
TransportationInvoiceseTransportationInvoiceEventsexigem roleNota Fiscal (api.nfe.io)ouCTeDist (dfe.nfe.io).
9.2. Parâmetros OData suportados
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
$filter | string | Sim | Filtro simples no formato propriedade eq valor. Veja restrições abaixo. |
$top | int | Não | Quantidade máxima de registros por página. Máximo: 1000, padrão: 50. |
$skiptoken | long | Não | Cursor de paginação — use o nsu do último item da página anterior. |
$count | bool | Não | Se true, inclui a contagem total na resposta. |
Restrição importante sobre
$filter: apenas expressões simples de igualdade (prop eq valor) são suportadas. Filtros compostos comand/ornão funcionam — use um único predicado por requisição. Essa limitação existe para manter a performance dos índices MongoDB.
9.3. Paginação cursor-based via $skiptoken
A paginação funciona por cursor incremental usando o NSU (ascendente):
- Cliente faz a primeira requisição sem
$skiptoken. - API retorna até
$topregistros ordenados pornsuascendente (do menor para o maior). - Cliente pega o
nsudo último item retornado. - Cliente faz a próxima requisição passando esse valor em
$skiptoken. - API retorna os próximos registros com
nsumaior que o$skiptoken. - Quando a resposta vier com menos registros que
$top(ou vazia), você chegou ao fim.
A ordem é sempre ascendente (menor NSU primeiro). Você sempre começa pelos documentos mais antigos e avança no tempo.
9.4. Listar NF-e — ProductInvoices
GET /v2/companies/{company_id}/inbound/odata/ProductInvoices?$filter=environmentType eq 1&$top=100
Authorization: {api_key}
Valores aceitos para environmentType:
| Valor | Significado |
|---|---|
1 | Produção |
2 | Homologação (testes) |
Resposta (200):
{
"@odata.context": "https://api.nfse.io/v2/companies/comp_123/inbound/odata/$metadata#ProductInvoices",
"value": [
{
"accessKey": "35240612345678000195550010000012341123456789",
"createdOn": "2026-04-09T10:30:00Z",
"company": {
"id": "comp_123",
"federalTaxNumber": "12345678000195"
},
"type": "productInvoice",
"nsu": 373289,
"environmentType": 1,
"issuedOn": "2026-04-08T14:00:00Z",
"nfeNumber": "1234",
"nfeSerialNumber": "1",
"operationType": "Incoming",
"totalInvoiceAmount": "1500.50",
"federalTaxNumberSender": "98765432000100",
"nameSender": "Fornecedor LTDA",
"xmlUrl": "https://api.nfse.io/v2/companies/comp_123/inbound/3524.../xml"
}
]
}
Atenção ao tipo de
nsu: neste endpoint OData onsuélong(número). Nos endpoints REST individuais (/productinvoices/{access_key}), o mesmo campo é string. Essa diferença é histórica e deve ser tratada no cliente conforme o contexto.
9.5. Listar Eventos de NF-e — ProductInvoiceEvents
GET /v2/companies/{company_id}/inbound/odata/ProductInvoiceEvents?$filter=accessKey eq '35240612345678000195550010000012341123456789'&$top=50
Authorization: {api_key}
O filtro é obrigatoriamente por accessKey — a chave de acesso de 44 dígitos da NF-e pai. O fluxo típico é:
- Listar NF-e via
ProductInvoices(passo 9.4). - Para cada NF-e retornada, chamar
ProductInvoiceEventscom aaccessKeydela para obter os eventos relacionados (ciência, confirmação, cancelamento, CC-e, etc.).
Resposta (200):
{
"value": [
{
"id": "comp_123_abc_35240612345678000195550010000012341123456789110111",
"createdOn": "2026-04-09T11:00:00Z",
"company": {
"id": "comp_123",
"federalTaxNumber": "12345678000195"
},
"accessKey": "35240612345678000195550010000012341123456789",
"type": "productInvoiceEvent",
"nsu": 373290,
"receiptOn": "2026-04-09T10:55:00Z",
"description": "Ciência da Operação",
"xmlUrl": "https://api.nfse.io/v2/companies/comp_123/inbound/3524.../xml"
}
]
}
9.6. Listar CT-e — TransportationInvoices
GET /v2/companies/{company_id}/inbound/odata/TransportationInvoices?$filter=issuedOn eq 2026-04-01&$top=100
Authorization: {api_key}
O filtro é obrigatoriamente pela data de emissão (issuedOn). Passe a data no formato yyyy-MM-dd (sem hora). A API retorna todos os CT-e emitidos exatamente naquele dia.
Resposta (200):
{
"value": [
{
"id": "comp_123_trans_35240198765432000100570010000009871234567890",
"createdOn": "2026-04-01T14:22:10Z",
"company": {
"id": "comp_123",
"federalTaxNumber": "98765432000100"
},
"accessKey": "35240198765432000100570010000009871234567890",
"parentAccessKey": "",
"type": "transportationInvoice",
"nsu": 10042,
"issuedOn": "2026-04-01T10:00:00Z",
"description": "Autorizado o uso do CT-e",
"xmlUrl": "https://api.nfse.io/v2/companies/comp_123/inbound/3524.../xml"
}
]
}
9.7. Listar Eventos de CT-e — TransportationInvoiceEvents
GET /v2/companies/{company_id}/inbound/odata/TransportationInvoiceEvents?$filter=receiptOn eq 2026-04-01&$top=100
Authorization: {api_key}
Similar ao endpoint de CT-e, mas o filtro é pela data de recebimento do evento (receiptOn), não pela data de emissão.
9.8. Exemplo completo de paginação (NF-e)
Primeira página (sem $skiptoken):
curl -G "https://api.nfse.io/v2/companies/comp_123/inbound/odata/ProductInvoices" \
--header "Authorization: sk_live_abc123" \
--data-urlencode '$filter=environmentType eq 1' \
--data-urlencode '$top=100'
Suponha que a resposta contenha 100 NF-e e o último item tenha nsu = 373388.
Próxima página (passando o último NSU como $skiptoken):
curl -G "https://api.nfse.io/v2/companies/comp_123/inbound/odata/ProductInvoices" \
--header "Authorization: sk_live_abc123" \
--data-urlencode '$filter=environmentType eq 1' \
--data-urlencode '$top=100' \
--data-urlencode '$skiptoken=373388'
Critério de parada: quando a resposta vier com value vazio ou com menos itens que $top, você chegou ao fim dos registros.
9.9. Erros comuns e troubleshooting
| Erro | Causa | Como corrigir |
|---|---|---|
400 filter environmentType eq is required | O $filter está ausente ou malformado | Adicione $filter=environmentType eq 1 (ou 2) na URL |
400 filter accessKey eq is required | Faltou o filtro por accessKey em ProductInvoiceEvents | Adicione $filter=accessKey eq '{chave_44}' |
400 Could not find a property named 'X' | Nome da propriedade no filtro está errado (case sensitive) | Use exatamente environmentType, accessKey, issuedOn, receiptOn |
400 The query specified in the URI is not valid com and/or | Tentativa de filtro composto | Use apenas um predicado simples por requisição |
401 Unauthorized | API Key ausente ou inválida | Verifique o header Authorization |
403 Forbidden | API Key sem a role correta | Confirme se a role inclui Nota Fiscal (api.nfe.io) ou a role específica do endpoint |
| Página vazia na primeira requisição | Não há dados no ambiente solicitado | Verifique se está consultando o ambiente correto (environmentType eq 1 vs 2) |
10. Webhooks — Recebendo Notificações
📘 Doc dedicada: o contrato completo do webhook (envelope, payloads por evento, validação HMAC, reprocessamento) está em 02-doc-tecnica-clientes-dev-nfe-cte-inbound-webhook.md. Esta seção contém apenas o resumo essencial.
Como configurar
- Acesse o Painel nfe.io → Empresas → {sua empresa} → Webhooks.
- Cadastre a URL do seu endpoint HTTPS.
- Configure o Webhook Secret (usado para validação HMAC-SHA256).
- Ative o inbound (seção 6) com
WebhookVersion: 2.
Famílias de evento entregues
Quando um documento ou evento é recebido, fazemos um POST para sua URL.
O envelope traz body.type (um de seis valores: productInvoice,
productInvoiceEvent, productInvoiceSummary,
productInvoiceEventSummary, transportationInvoice,
transportationInvoiceEvent) e body.action (issued_successfully,
event_raised_successfully, input_event_raised_successfully). A tabela
completa de pares body.type × body.action e o payload de cada um estão
na §12 da doc dedicada acima.
Resposta esperada
- Seu endpoint deve retornar HTTP 2xx em até 30 segundos.
- Em caso de falha, retentamos até 50x em 24h com backoff exponencial.
- Status 4xx (exceto 408/429) marca a entrega como
DefinitivelyFailed(sem retry). UsePOST .../processwebhookpara reprocessar manualmente.
11. Referência de Tipos e Enums
EnvironmentSEFAZ
| Valor | Descrição |
|---|---|
"Test" | Ambiente de homologação SEFAZ — documentos sem validade fiscal |
"Production" | Ambiente de produção SEFAZ — documentos com validade fiscal real |
OperationType
| Valor | Descrição |
|---|---|
"Incoming" | Você é o destinatário (recebeu o documento) |
"Outgoing" | Você é o emitente (emitiu o documento) |
MetadataResourceType
| Valor | Descrição |
|---|---|
"productInvoice" | NF-e completa |
"productInvoiceEvent" | Evento de NF-e |
"productInvoiceSummary" | Resumo de NF-e (sem XML completo) |
"productInvoiceEventSummary" | Resumo de evento de NF-e |
"transportationInvoice" | CT-e |
"transportationInvoiceEvent" | Evento de CT-e |
EntityStatus
| Valor | Descrição |
|---|---|
"Active" | Configuração ativa — sistema monitorando |
"Inactive" | Configuração inativa — sem monitoramento |
Campo relatedIds em NFeMetadata
A collection NFeMetadata possui o campo relatedIds (array de strings) que armazena os IDs dos eventos relacionados àquela NF-e. Esse campo é populado automaticamente pelo worker DFeTech.App.Inbound.NFe sempre que um NFeEventMetadata é persistido:
| Propriedade | Tipo | Descrição |
|---|---|---|
relatedIds | string[] | IDs dos eventos ({accountId}_{companyId}_{eventId}) vinculados a esta NF-e |
Semântica:
- O link é implícito via
accessKey— o worker identifica a NF-e pai peloaccessKeycompartilhado entre NF-e e seus eventos - A operação de append é atômica e idempotente no MongoDB (usa
$addToSet) - Se a NF-e pai ainda não existir quando o evento chegar (ordem de chegada não garantida pela SEFAZ), o evento é persistido normalmente e apenas um
warningé logado — o link não é criado retroativamente nessa versão - Documentos antigos (persistidos antes desta implementação) podem não ter o campo
relatedIds— o consumidor deve tratar ausência/null como array vazio
Exemplo de documento:
{
"_id": "acc-1_comp-1_35240612345678000195550010000012341123456789",
"accountId": "acc-1",
"companyId": "comp-1",
"accessKey": "35240612345678000195550010000012341123456789",
"nsu": 373289,
"environmentType": 1,
"relatedIds": [
"acc-1_comp-1_35240612345678000195550010000012341123456789110111",
"acc-1_comp-1_35240612345678000195550010000012341123456789110222"
]
}
12. Tratamento de Erros
Formato do Erro
Quando ocorre um erro, a API retorna um JSON no seguinte formato:
{
"errors": [
{
"code": 404,
"message": "Document not found for the given access key."
}
]
}
Códigos HTTP
| HTTP | Significa | O que fazer |
|---|---|---|
200 | Sucesso | Processe a resposta normalmente |
202 | Aceito (assíncrono) | Operação enfileirada — acompanhe via webhook ou GET correspondente |
204 | Sucesso sem conteúdo | Operação realizada, sem corpo de resposta |
302 | Redirect | Quando um endpoint redireciona para uma URL assinada, siga o header Location (a maioria dos clientes HTTP faz isso automaticamente) |
400 | Requisição inválida | Verifique os parâmetros enviados |
401 | Não autorizado | Verifique sua API Key ou token |
403 | Proibido | API Key sem a policy exigida (NFeDist/CTeDist) |
404 | Não encontrado | O documento/empresa não existe |
409 | Conflito (duplicado) | Recurso já existe |
422 | Entidade não processável | Dados válidos mas não processáveis (ex: NF-e cancelada) |
503 | Serviço indisponível | SEFAZ temporariamente fora. Tente novamente em alguns minutos |
504 | Timeout | A operação demorou muito. Tente novamente |
500 | Erro interno | Entre em contato com o suporte |
Formato do corpo de erro. Os controllers que herdam de
ApiBaseController(a grande maioria) devolvem o wrapperErrorsResource(JSON{ "errors": [{ "code", "message" }] }) em qualquer 4xx/5xx — é o exemplo mostrado acima. Exceções (texto puro, não JSON):
BadRequest("...")direto — usado em validações de paginação e em alguns filtros OData (ex.:pageCount must not exceed 100).NFSeDownloadController(endpoint público/blob/download) — sempretext/plain("Invalid parameters.","Link expired.", etc.).Para o shape exato por endpoint e a lista completa de status documentados, consulte
openapi-inbound-api.yaml.
Eventos NF-e (tpEvento) e códigos de retorno SEFAZ
Os eventos NF-e mais comuns retornados nos endpoints inbound e nas respostas
de POST /manifest:
tpEvento | Nome | Quem emite | Quando aparece no inbound |
|---|---|---|---|
110110 | Carta de Correção (CC-e) | Emissor | Recebida automaticamente após autorização SEFAZ. |
110111 | Cancelamento | Emissor | Recebido automaticamente após autorização SEFAZ. |
210200 | Confirmação da Operação | Destinatário | Disparado por POST /manifest?tpEvent=210200 ou pela política de manifestação automática. |
210210 | Ciência da Operação | Destinatário | Default de POST /manifest. |
210220 | Operação Não Realizada | Destinatário | Disparado manualmente — exige justificativa SEFAZ. |
210240 | Desconhecimento da Operação | Destinatário | Disparado manualmente quando a NF-e foi emitida indevidamente contra o CNPJ. |
Códigos cStat de retorno SEFAZ mais frequentes na operação de manifestação:
cStat | Significado | Ação |
|---|---|---|
135 | Evento registrado e vinculado | Sucesso. |
136 | Evento registrado, não vinculado | Sucesso parcial — chave pode estar incorreta. |
573 | Duplicidade de evento | Já existe evento de mesmo tipo. |
217 | NF-e não consta na base | Aguarde alguns minutos (latência SEFAZ). |
999 | Erro interno SEFAZ | Retentar após 5–10 min. |
Para a lista completa de cStat, consulte o Manual de Orientação do
Contribuinte da SEFAZ.
13. Exemplos de Integração
Exemplo 1 — Buscar NF-e por Chave de Acesso (C#)
using System.Net.Http;
using System.Text.Json;
public class NfeIoClient
{
private readonly HttpClient _http;
private readonly string _companyId;
private readonly string _baseUrl = "https://api.nfse.io";
public NfeIoClient(string apiKey, string companyId)
{
_companyId = companyId;
_http = new HttpClient();
_http.DefaultRequestHeaders.Add("Authorization", $"ApiKey {apiKey}");
}
public async Task<NfeMetadata> GetNfeAsync(string accessKey)
{
var url = $"{_baseUrl}/v2/companies/{_companyId}/inbound/productinvoices/{accessKey}";
var response = await _http.GetAsync(url);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<NfeMetadata>(content);
}
public async Task<Stream> GetNfeXmlAsync(string accessKey)
{
var url = $"{_baseUrl}/v2/companies/{_companyId}/inbound/productinvoices/{accessKey}/xml";
return await _http.GetStreamAsync(url);
}
}
Exemplo 2 — Consumir Webhook e Baixar XML (Node.js)
const express = require('express');
const axios = require('axios');
const crypto = require('crypto');
const app = express();
app.use(express.raw({ type: 'application/json' })); // raw para validar assinatura
const API_KEY = 'sk_live_sua_chave_aqui';
const COMPANY_ID = 'comp_123';
const WEBHOOK_SECRET = 'seu_webhook_secret';
const BASE_URL = 'https://api.nfse.io';
function validateSignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
app.post('/webhooks/nfeio', async (req, res) => {
// Validar assinatura
const signature = req.headers['x-nfe-signature'] || '';
if (!validateSignature(req.body, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Assinatura inválida');
}
const payload = JSON.parse(req.body);
const { event, accessKey, type, issuer, totalAmount } = payload;
console.log(`Novo documento: ${accessKey} - ${event}`);
try {
const xmlResponse = await axios.get(
`${BASE_URL}/v2/companies/${COMPANY_ID}/inbound/productinvoices/${accessKey}/xml`,
{ headers: { 'Authorization': `ApiKey ${API_KEY}` }, responseType: 'text' }
);
await saveDocumentToDatabase({ accessKey, type, issuer, totalAmount, xml: xmlResponse.data });
res.status(200).json({ status: 'ok' });
} catch (error) {
console.error('Erro:', error);
res.status(500).json({ error: error.message });
}
});
app.listen(3000);
Exemplo 3 — Listar CT-es do Último Mês com Paginação (Python)
import requests
from datetime import datetime, timedelta
API_KEY = "sk_live_sua_chave"
COMPANY_ID = "comp_123"
BASE_URL = "https://api.nfse.io"
def get_all_ctes_last_month():
"""Busca os CT-es do último mês.
A API não suporta filtro por intervalo de datas (`$filter` aceita apenas
igualdade simples — ver §9). A paginação é cursor-based via `$skiptoken`
(NSU). Portanto, percorremos as páginas em ordem de NSU e filtramos por
data no lado do cliente.
"""
cutoff = datetime.utcnow() - timedelta(days=30)
headers = {"Authorization": API_KEY}
all_ctes = []
skiptoken = None
while True:
url = (
f"{BASE_URL}/v2/companies/{COMPANY_ID}/inbound/odata/TransportationInvoices"
f"?$top=100"
)
if skiptoken is not None:
url += f"&$skiptoken={skiptoken}"
response = requests.get(url, headers=headers)
response.raise_for_status()
page = response.json().get("value", [])
if not page:
break
all_ctes.extend(
c for c in page
if datetime.fromisoformat(c["issuedOn"].replace("Z", "")) >= cutoff
)
# Cursor: NSU do último item desta página (ordenada por NSU ascendente).
skiptoken = page[-1]["nsu"]
print(f"Processados {len(all_ctes)} CT-es do período até agora...")
return all_ctes
ctes = get_all_ctes_last_month()
print(f"Total: {len(ctes)} CT-es")
for cte in ctes[:5]:
print(f" - {cte['accessKey']} | Emitido em: {cte['issuedOn']}")
Exemplo 4 — Ativar Inbound e Aguardar Primeiro Documento (PHP)
<?php
class NfeIoInbound
{
private string $apiKey;
private string $companyId;
private string $baseUrl = 'https://api.nfse.io';
public function __construct(string $apiKey, string $companyId)
{
$this->apiKey = $apiKey;
$this->companyId = $companyId;
}
private function makeRequest(string $method, string $path, ?array $body = null): array
{
$url = "{$this->baseUrl}/v2/companies/{$this->companyId}/{$path}";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => [
"Authorization: {$this->apiKey}",
"Content-Type: application/json"
],
]);
if ($body) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
}
$response = curl_exec($ch);
$statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($statusCode >= 400) {
throw new RuntimeException("API Error {$statusCode}: {$response}");
}
return json_decode($response, true);
}
public function enableNfeInbound(int $startFromNsu = 0): array
{
return $this->makeRequest('POST', 'inbound/productinvoices', [
'StartFromNsu' => $startFromNsu,
'EnvironmentSEFAZ' => 'Production',
'WebhookVersion' => 2,
]);
}
public function getNfeMetadata(string $accessKey): array
{
return $this->makeRequest('GET', "inbound/productinvoices/{$accessKey}");
}
}
// Uso:
$client = new NfeIoInbound('sk_live_abc123', 'comp_123');
$config = $client->enableNfeInbound();
echo "Inbound ativado! Status: " . $config['status'];
Exemplo 5 — Listar todas as NF-e de produção com paginação OData (Node.js)
Este exemplo usa o endpoint ProductInvoices para iterar sobre todas as NF-e da sua empresa no ambiente de produção. Ideal para sincronização de histórico ou auditorias.
const axios = require('axios');
const API_KEY = 'sk_live_abc123';
const COMPANY_ID = 'comp_123';
const BASE_URL = 'https://api.nfse.io';
const PAGE_SIZE = 100;
const http = axios.create({
baseURL: BASE_URL,
headers: { Authorization: API_KEY }
});
async function listAllNFeProducao() {
const allDocuments = [];
let skipToken = null;
let pageNumber = 1;
while (true) {
const params = {
'$filter': 'environmentType eq 1', // 1 = Produção
'$top': PAGE_SIZE
};
if (skipToken !== null) {
params['$skiptoken'] = skipToken;
}
const response = await http.get(
`/v2/companies/${COMPANY_ID}/inbound/odata/ProductInvoices`,
{ params }
);
const items = response.data.value || [];
console.log(`Página ${pageNumber}: ${items.length} NF-e recebidas`);
if (items.length === 0) break;
allDocuments.push(...items);
// Critério de parada: última página veio com menos itens que o $top
if (items.length < PAGE_SIZE) break;
// Próximo cursor = NSU do último item da página atual
skipToken = items[items.length - 1].nsu;
pageNumber++;
}
return allDocuments;
}
// Para cada NF-e, buscar os eventos relacionados
async function listEventsForNFe(accessKey) {
const events = [];
let skipToken = null;
while (true) {
const params = {
'$filter': `accessKey eq '${accessKey}'`,
'$top': 50
};
if (skipToken !== null) params['$skiptoken'] = skipToken;
const response = await http.get(
`/v2/companies/${COMPANY_ID}/inbound/odata/ProductInvoiceEvents`,
{ params }
);
const items = response.data.value || [];
if (items.length === 0) break;
events.push(...items);
if (items.length < 50) break;
skipToken = items[items.length - 1].nsu;
}
return events;
}
// Execução
(async () => {
try {
const notas = await listAllNFeProducao();
console.log(`\nTotal de NF-e encontradas: ${notas.length}`);
// Exemplo: buscar eventos da primeira NF-e
if (notas.length > 0) {
const eventos = await listEventsForNFe(notas[0].accessKey);
console.log(`Eventos da NF-e ${notas[0].accessKey}: ${eventos.length}`);
}
} catch (err) {
console.error('Erro:', err.response?.data || err.message);
}
})();
Pontos importantes deste exemplo:
PAGE_SIZEestá em 100 para equilibrar número de requisições e tamanho de resposta. Você pode subir até 1000.- O critério de parada é duplo: ou a resposta vem vazia, ou vem com menos itens que
$top(indicando fim dos dados). - O
$skiptokensempre recebe onsudo último item da página atual — nunca o primeiro. - A iteração é cursor-based, então é segura mesmo que novos documentos cheguem durante a execução (novos registros terão NSU maior e serão incluídos naturalmente).
- Para buscar eventos de uma NF-e, use
ProductInvoiceEventspassando aaccessKeyda NF-e pai.
Exemplo 6 — Listar NF-e paginado em C# (.NET)
using System.Net.Http.Headers;
using System.Text.Json;
var httpClient = new HttpClient { BaseAddress = new Uri("https://api.nfse.io") };
// A API Key é o valor direto do header Authorization (sem prefixo/esquema).
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", "sk_live_abc123");
var companyId = "comp_123";
var pageSize = 100;
long? skipToken = null;
var allDocuments = new List<JsonElement>();
while (true)
{
var url = $"/v2/companies/{companyId}/inbound/odata/ProductInvoices" +
$"?$filter=environmentType eq 1&$top={pageSize}";
if (skipToken.HasValue)
url += $"&$skiptoken={skipToken.Value}";
var response = await httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json);
var items = doc.RootElement.GetProperty("value").EnumerateArray().ToList();
if (items.Count == 0) break;
allDocuments.AddRange(items);
Console.WriteLine($"Recebidas {items.Count} NF-e (total: {allDocuments.Count})");
if (items.Count < pageSize) break;
// Próximo cursor = NSU (long) do último item
skipToken = items.Last().GetProperty("nsu").GetInt64();
}
Console.WriteLine($"\nTotal final: {allDocuments.Count} NF-e");
14. Perguntas Frequentes (FAQ)
Quanto tempo leva para receber um documento após a emissão?
Em condições normais, entre 30 minutos e 4 horas após a autorização da SEFAZ. O nosso sistema faz polling periódico na SEFAZ; o intervalo depende da configuração, da sincronização entre a SEFAZ de origem e o Ambiente Nacional e da disponibilidade da SEFAZ.
O que é um "resumo" (productInvoiceSummary)?
A SEFAZ disponibiliza o productInvoiceSummary enquanto a NF-e ainda não tem uma manifestação do destinatário registrada — nesse estado, apenas os dados básicos da nota são liberados, sem o XML completo. O XML completo só é disponibilizado pela SEFAZ depois que o destinatário registra um evento de manifestação.
Para automatizar esse fluxo, o nosso sistema oferece a configuração AutomaticManifesting: ao receber um productInvoiceSummary, o destinatário pode ter a manifestação "Ciência da Operação" enviada automaticamente em seu nome. Assim que a SEFAZ processa essa manifestação, ela libera o XML completo, que é entregue por uma nova notificação (product_invoice_inbound / issued_successfully).
Posso buscar documentos históricos?
Sim. A SEFAZ mantém documentos disponíveis por até 90 dias para consulta via NSU. Configure StartFromNsu ao ativar o inbound para sincronizar o histórico.
A URL do XML expira?
Sim. As URLs retornadas em links.xml e links.pdf expiram em 1 hora. Para download, use a URL logo após obtê-la, ou chame o endpoint /xml a qualquer momento para obter uma nova URL.
O que acontece se meu webhook estiver fora do ar?
O sistema tentará reenviar o webhook com backoff exponencial por até 24 horas. Após esse período, você pode usar o endpoint de reprocessamento para solicitar novo envio.
Preciso manifestar todas as NF-es?
Pela legislação brasileira, o destinatário deve registrar uma das 4 manifestações para NF-es de entrada (que você está comprando): Ciência da Operação, Confirmação da Operação, Operação Não Realizada ou Desconhecimento da Operação. Não é obrigatório que seja a Ciência da Operação especificamente.
Para automatizar parte desse fluxo, o sistema oferece a configuração AutomaticManifesting, que envia a Ciência da Operação automaticamente em nome do destinatário. Essa manifestação foi escolhida para automação porque não impede que o destinatário, posteriormente, registre uma das demais manifestações (Confirmação, Operação Não Realizada ou Desconhecimento) caso o contexto fiscal exija.
Como testar sem afetar o ambiente de produção?
Configure com EnvironmentSEFAZ: "Test" para usar o ambiente de homologação SEFAZ. Documentos emitidos em homologação não têm validade fiscal. Ao terminar os testes, basta atualizar a configuração para "Production" — não é necessário deletá-la antes.
Como saber se o inbound está atrasado?
Consulte GET /productinvoices e compare OperationDetail_CurrentNsu com OperationDetail_MaxNsu. Se a diferença for grande e OperationDetail_ExecutedOn for antigo, pode haver um problema. Verifique se a configuração está Active e se o certificado digital não expirou.
Documentação mantida pela equipe de desenvolvimento nfe.io. Para reportar problemas ou solicitar funcionalidades, acesse o suporte em nfe.io/suporte.