Documentação técnica

API de emissão do EasyNotas

Uma API REST para o seu sistema emitir, consultar e cancelar notas fiscais, inutilizar numeração de NF-e e ler as notas que fornecedores emitiram contra os seus clientes. O EasyNotas cuida da comunicação com a SEFAZ e com as prefeituras.

Versão 1.1 Atualizada em 30/09/2026 Base: https://pufsgcaxubzxdtmsenjx.supabase.co/functions/v1/api-v1

Novidades de setembro/2026

Tudo que o painel do EasyNotas ganhou desde julho agora também funciona pela API. Integrações que já existem continuam funcionando sem mudança: todos os campos novos são opcionais.

Reforma Tributária na NFS-e

NBS, indicador de operação e CST/classificação de IBS/CBS por nota. Sem eles, vale o padrão cadastrado na empresa.

Retenções na NFS-e

ISS retido, IR, PIS, COFINS e CSLL. IRRF de 1,5% automático para tomador PJ nos serviços de intermediação.

Mais campos na NFS-e

Informações complementares, município da prestação e tomador não identificado (consumidor final).

NF-e mais completa

Série própria da empresa, devolução com nota referenciada por item, cBenef e PIS/COFINS por item.

Inutilização de numeração

Inutilize na SEFAZ uma faixa de números de NF-e que foram pulados.

Notas recebidas

Liste as notas emitidas contra as empresas do escritório e faça a manifestação do destinatário.

Chaves existentes também ganharam as permissões novas. Toda chave ativa passou a ter os escopos numbering:write, received:read e received:manifest. Inutilização e manifestação são eventos irreversíveis na SEFAZ: se o seu sistema não deve fazê-los, gere uma chave nova só com os escopos que usa e revogue a antiga. Os Termos de Uso da API foram atualizados (versão 2026-09-30).

Visão geral

Seu sistema chama a API com os dados da nota. A API valida autenticação, pagamento do escritório, habilitação da empresa e as regras fiscais, coloca a nota na fila e responde na hora com um id. A autorização na SEFAZ ou na prefeitura acontece em seguida, e o resultado chega por webhook ou por consulta.

Os dados do emitente nunca vêm do seu sistema. Você informa só o cnpj_emitente, e regime tributário, inscrições, endereço, certificado e padrões fiscais saem do cadastro da empresa no EasyNotas. Se o cadastro estiver errado, a nota sai errada: corrija no painel, não no payload.

O que a API faz

RecursoRotaSituação
Emitir NFS-e (Nacional ou municipal de São Paulo)POST /v1/invoicesEm produção
Emitir NF-e modelo 55, inclusive devoluçãoPOST /v1/invoicesEm produção
Emitir CT-e, CT-e OS, CT-e Simplificado, MDF-e, NFCom e DC-ePOST /v1/invoicesDisponível, sem emissão real pela API ainda
Consultar e listar notas, com PDF, XML e chave de acessoGET /v1/invoicesEm produção
Cancelar e consultar a janela de cancelamentoPOST /v1/invoices/{id}/cancelEm produção
Webhooks de status assinadosURL cadastrada no painelEm produção
Inutilizar numeração de NF-ePOST /v1/nfe/inutilizacoesnovo
Listar notas recebidas (Buscador XML)GET /v1/receivednovo
Manifestação do destinatárioPOST /v1/received/{chave}/manifestnovo

Quem usa

ERPs e sistemas de gestão que precisam emitir a nota logo depois do faturamento, plataformas que vendem serviços e querem emitir a NFS-e do cliente, e escritórios contábeis que automatizam a rotina dos clientes. A integração exige trabalho de desenvolvimento do lado de quem consome a API. O EasyNotas fornece a API, esta documentação e o suporte.

Primeiros passos

Gerar uma chave sandbox no painel
Chamar /v1/health
Emitir uma NFS-e de teste
Receber o webhook e validar a assinatura
Gerar a chave de produção

1. Gere a chave

No EasyNotas, o dono do escritório abre Configurações → API / Desenvolvedores, escolhe o ambiente, aceita os Termos de Uso da API e clica em Gerar nova API key. A chave de sandbox começa com enk_test_ e a de produção com enk_live_.

O segredo aparece uma única vez. O EasyNotas guarda só um hash dele. Copie para um cofre de segredos ou variável de ambiente do servidor. Nunca coloque a chave em front-end, aplicativo ou repositório.

2. Teste a conexão

bash
curl -i https://pufsgcaxubzxdtmsenjx.supabase.co/functions/v1/api-v1/v1/health

Resposta 200 {"status":"ok"}. Essa rota não exige autenticação.

3. Emita a primeira nota

bash
curl -i -X POST 'https://pufsgcaxubzxdtmsenjx.supabase.co/functions/v1/api-v1/v1/invoices' \
  -H 'Authorization: Bearer enk_test_SUA_CHAVE' \
  -H 'X-EasyNotas-Environment: sandbox' \
  -H 'Idempotency-Key: fatura-2026-000123-teste' \
  -H 'Content-Type: application/json' \
  -d '{
    "tipo": "nfse_nacional",
    "cnpj_emitente": "12345678000190",
    "tomador": {
      "documento": "11222333000181",
      "razao_social": "Cliente Exemplo LTDA",
      "email": "financeiro@exemplo.com.br"
    },
    "servico": {
      "discriminacao": "Consultoria mensal em tecnologia - setembro/2026",
      "valor_servicos": 1500.00,
      "codigo_tributacao_nacional": "010701"
    }
  }'

A resposta é 202 Accepted com o id da nota e status: "queued". Guarde o id: é por ele que você acompanha a nota. O 202 quer dizer "aceita e na fila", não "autorizada".

4. Acompanhe o resultado

Configure a URL de webhook no mesmo painel e o EasyNotas avisa quando a nota for autorizada, rejeitada ou cancelada (veja Webhooks). Se preferir consultar, use GET /v1/invoices/{id}: enquanto a nota está em processing a resposta traz Retry-After: 5; antes disso, na fila, consultar a cada poucos minutos basta.

5. Vá para produção

Gere uma chave de produção, troque o header para production e confirme no cadastro que a empresa está habilitada para o tipo de nota e tem certificado digital válido.

Autenticação e escopos

HeaderQuandoValor
AuthorizationToda rota, menos /v1/healthBearer enk_live_… ou Bearer enk_test_…
X-EasyNotas-EnvironmentToda rota, menos /v1/healthsandbox ou production. Precisa bater com o ambiente da chave, senão 403 environment_mismatch.
Idempotency-KeySó em POST /v1/invoicesTexto único por nota, formato ^[A-Za-z0-9_-]{16,64}$
Content-TypeTodo POSTapplication/json

Escopos

Cada chave carrega uma lista de permissões. Uma rota chamada sem o escopo responde 403 insufficient_scope.

EscopoLibera
invoices:writeEmitir notas
invoices:readConsultar e listar notas, janela de cancelamento e histórico de inutilizações
invoices:cancelCancelar notas. Chaves geradas antes de 20/07/2026 não têm este escopo: gere uma nova.
numbering:write novoInutilizar numeração de NF-e
received:read novoListar e consultar notas recebidas, com download do XML
received:manifest novoManifestação do destinatário

Ambientes

AmbienteChaveO que acontece
sandboxenk_test_Emite em homologação, sem valor fiscal e sem consumir o plano. Inutilização vai para a homologação e não entra no histórico do cliente. Manifestação é bloqueada.
productionenk_live_Emite documentos fiscais reais e consome o plano do escritório.
Use o sandbox para integrar, não para validar layout fiscal. A homologação da SEFAZ nem sempre repete as regras da produção. Acompanhe de perto a primeira nota real de cada tipo e de cada empresa.

Idempotência, fila e limites

Idempotência

Emitir nota é irreversível. Se a rede cair no meio da chamada e você reenviar, a Idempotency-Key impede a segunda nota. Use um valor estável do seu sistema, como o número da fatura.

  • Mesma chave e mesmo corpo: a API devolve a resposta original, com o header Idempotency-Replayed: true.
  • Mesma chave e corpo diferente: 409 idempotency_conflict, mas só quando a chave já gerou um 202. Se a primeira tentativa foi recusada (4xx ou 5xx), corrija o corpo e reenvie com a mesma chave.
  • A proteção vale por 30 dias. Depois disso a mesma chave pode ser aceita como uma nota nova.
  • Mesma chave enquanto a primeira chamada ainda roda: 409 idempotency_in_flight. Espere e repita com a mesma chave.

Fila de emissão

A nota entra na fila e a fila roda a cada 10 minutos. Depois do despacho, a SEFAZ ou a prefeitura costuma responder em segundos. Não trave a tela do seu usuário esperando a autorização.

Limites

LimiteValorAo passar
Requisições por chave60 por minuto429 rate_limited com Retry-After
Emissões por CNPJ emitente20 por hora (janela deslizante)429 sefaz_quota_exceeded com Retry-After

O limite por CNPJ protege o certificado do cliente contra bloqueio na SEFAZ Ele conta as emissões feitas pela API; emissões pelo painel, por lote e por recorrência não entram nessa conta.

Emitir nota

POST/v1/invoicesescopo invoices:write

O campo tipo, na raiz do corpo, escolhe o documento e as regras aplicadas.

tipoDocumentoExige
nfse_nacionalNFS-e (roteada pelo município da empresa: Ambiente Nacional ou emissor de São Paulo)Empresa habilitada para NFS-e
nfeNF-e modelo 55Empresa habilitada para NF-e
cte, cte_os, cte_simp, mdfe, nfcom, dceDocumentos de transporte e comunicaçãoHabilitação e add-on contratado

Resposta 202:

json
{
  "id": "920d1d7a-f721-40d7-a44b-f79af97dd208",
  "status": "queued",
  "idempotency_key": "fatura-2026-000123",
  "created_at": "2026-09-30T13:47:21.816Z"
}
// Header: Location: /v1/invoices/920d1d7a-f721-40d7-a44b-f79af97dd208

NFS-e

Um só tipo para NFS-e. O EasyNotas decide o emissor pelo município da empresa: São Paulo capital usa o sistema da prefeitura, os demais usam o Ambiente Nacional.

json
{
  "tipo": "nfse_nacional",
  "cnpj_emitente": "12345678000190",
  "tomador": {
    "documento": "11222333000181",
    "razao_social": "Cliente Exemplo LTDA",
    "email": "financeiro@exemplo.com.br",
    "endereco": {
      "logradouro": "Rua das Flores", "numero": "100", "complemento": "Sala 2",
      "bairro": "Centro", "municipio": "Araçatuba", "uf": "SP", "cep": "16010000"
    }
  },
  "servico": {
    "discriminacao": "Intermediação na venda do veículo Onix 2022",
    "valor_servicos": 1500.00,
    "codigo_tributacao_nacional": "100501",
    "aliquota": 2.0,
    "informacoes_complementares": "Veículo placa ABC1D23, chassi 9BG...",
    "codigo_municipio_prestacao": "3502804",
    "codigo_nbs": "102010000",
    "codigo_indicador_operacao": "100301",
    "ibs_cbs_situacao_tributaria": "000",
    "ibs_cbs_classificacao_tributaria": "000001",
    "iss_retido": false
  }
}

Tomador

CampoObrig.Regra
tomadornãoOmitido, null ou {} = tomador não identificado (consumidor final). Tomador parcial, como nome sem documento, é recusado.
tomador.documentose houver tomadorCPF (11) ou CNPJ (14). Pontuação é ignorada.
tomador.razao_socialse houver tomadorAté 150 caracteres.
tomador.emailnãoRecebe o PDF e o XML da nota.
tomador.endereconãoTudo ou nada. Se enviar, logradouro, cep (8 dígitos), municipio e uf são obrigatórios; numero, complemento e bairro são opcionais. Sem endereço a nota sai com o tomador identificado só por documento e nome.

Serviço

CampoObrig.Regra
discriminacaosim15 a 2000 caracteres.
valor_servicossimMaior que zero.
codigo_tributacao_nacionalsim6 dígitos. Pode vir pontuado (10.05.01).
aliquotanãoAlíquota do ISS em %.
item_lista_servicoSP capitalObrigatório quando a empresa é de São Paulo capital (código de serviço de 5 dígitos da prefeitura).
codigo_tributacao_municipionãoCódigo municipal, usado no emissor de São Paulo.
informacoes_complementares novonãoAté 2000 caracteres. No Ambiente Nacional vai no campo próprio da nota; em São Paulo é anexado à discriminação.
codigo_municipio_prestacao novonãoCódigo IBGE (7 dígitos) da cidade onde o serviço foi prestado. Sem ele, vale a cidade da empresa.
codigo_nbs novonão9 dígitos. Reforma Tributária.
codigo_indicador_operacao novonão6 dígitos. Com ele preenchido a SEFAZ Nacional passa a exigir o endereço completo do tomador.
ibs_cbs_situacao_tributaria novonãoCST do IBS/CBS, 3 dígitos.
ibs_cbs_classificacao_tributaria novonãocClassTrib, 6 dígitos.
iss_retido novonãotrue quando o tomador retém o ISS. Padrão false.
retencoes novonãoObjeto { valor_ir, valor_pis, valor_cofins, valor_csll } em reais. Veja abaixo.

Os quatro campos da Reforma Tributária seguem a regra do painel: o que vier na nota vale; o que não vier sai do padrão cadastrado na empresa. Quando qualquer um deles está presente, o EasyNotas preenche sozinho a finalidade, o consumidor final e o indicador de destinatário exigidos pelo layout novo.

Retenções federais

Só se aplicam a emitente do Lucro Presumido ou Lucro Real. Retenção com valor para empresa do Simples ou MEI é recusada com 422 retencao_not_applicable.

Você enviaO EasyNotas faz
retencoes com valoresUsa os valores enviados. A soma precisa ser menor que o valor do serviço. No Ambiente Nacional, PIS, COFINS e CSLL retidos vão somados num só campo de retenção, como a SEFAZ Nacional exige, com CST de PIS/COFINS 01 e alíquotas de 0,65% e 3%; esse CST não é configurável pela API.
retencoes ausenteAplica o IRRF automático de 1,5%, igual ao painel, quando: empresa no Lucro Presumido/Real, serviço do item 10 da LC 116 (exceto 10.08, publicidade), tomador com CNPJ e IR acima de R$ 10,00 (na prática, nota a partir de R$ 667,00).
retencoes: {} ou tudo zeroNenhuma retenção, nem a automática.
Confirme as alíquotas com o contador. A API aplica os valores e monta a estrutura que a SEFAZ aceita (validada em produção com IRRF), mas a obrigação de reter e o percentual dependem do serviço e do tomador.

Campos de regime (codigo_opcao_simples_nacional, regime_especial_tributacao e parecidos) não são aceitos no corpo, porque vêm do cadastro. Enviar um deles responde 422 field_inherited_from_company.

NF-e modelo 55

json
{
  "tipo": "nfe",
  "cnpj_emitente": "12345678000190",
  "destinatario": {
    "nome": "Cliente Exemplo Ltda",
    "documento": "98765432000188",
    "ie": "123456789",
    "contribuinte": true,
    "email": "financeiro@exemplo.com.br",
    "endereco": { "logradouro": "Rua das Palmeiras", "numero": "450", "bairro": "Centro",
                  "municipio": "Campinas", "uf": "SP", "cep": "13010000" }
  },
  "itens": [{
    "codigo": "SKU-1042", "descricao": "Camiseta algodão branca M",
    "quantidade": 2, "valor_unitario": 49.90,
    "ncm": "61091000", "unidade": "UN", "cst_csosn": "102", "origem": 0
  }],
  "pagamento": [{ "forma": "01", "valor": 99.80 }],
  "operacao": { "informacoes_adicionais_contribuinte": "Pedido 5531" }
}

Destinatário e pagamento

CampoObrig.Regra
destinatario.nomesimAté 150 caracteres.
destinatario.documentosimCPF (11) ou CNPJ (14).
destinatario.enderecosimlogradouro, numero, bairro, municipio, uf e cep obrigatórios; complemento opcional.
destinatario.iecontribuinteInscrição estadual. CNPJ contribuinte de SP sem IE é rejeitado pela SEFAZ.
destinatario.contribuintenãofalse para consumidor final. true sem IE válida gera rejeição.
pagamento[]não{ forma, valor }. Omitido, vira um pagamento único em dinheiro (01) no valor total. A forma 90 (sem pagamento) sempre vai com valor zero. A soma precisa bater com o total. Formas: 01, 02, 03, 04, 05, 10, 11, 12, 13, 15, 16, 17, 18, 19, 90, 99.

Itens

De 1 a 200 itens por nota.

CampoObrig.Regra
codigosimCódigo do produto no seu sistema, até 60 caracteres.
descricaosim
quantidade, valor_unitariosimQuantidade maior que zero; valor maior ou igual a zero.
cst_csosnsimValidado contra o regime da empresa. Simples/MEI: CSOSN 101, 102, 103, 201, 202, 203, 300, 400, 500, 900. Regime normal: CST 00, 10, 20, 30, 40, 41, 50, 51, 60, 70, 90.
ncmnão8 dígitos. Sem ele vale o NCM padrão da empresa.
cest, unidadenãoCEST malformado é recusado antes da SEFAZ.
cfopnão4 dígitos. Sem ele, derivado de operação interna ou interestadual. Se informado, o primeiro dígito é ajustado à operação: 5202 vira 6202 para destinatário de outra UF, e vice-versa.
origemnão0 a 8. Padrão 0.
aliquota_icmsnão0 a 100. Sem ela, vale a alíquota interna da empresa; se a empresa não tiver uma cadastrada, 18%. Confirme com o contador.
numero_item_referenciado novodevoluçãoNúmero do item (1 a 990) na NF-e original.
codigo_beneficio_fiscal novonãocBenef, até 10 caracteres.
pis_cst, cofins_cst novonãoCST de 2 dígitos. Sem eles vale o padrão do regime.
pis_aliquota, cofins_aliquota novonão0 a 100.

Operação (opcional)

CampoRegra
serie novoSem ela, a NF-e usa a série exclusiva cadastrada na empresa. Isso evita colisão de numeração com outro emissor do cliente (rejeição 539).
numeroNumeração manual. Sem ela, a numeração segue automática.
natureza_operacaoPadrão "Venda de mercadoria".
finalidade_emissao1 normal (padrão), 2 complementar, 3 ajuste, 4 devolução.
chave_nfe_referenciadaChave de 44 dígitos da nota original. Obrigatória na devolução.
tipo_documento0 entrada, 1 saída (padrão).
consumidor_final, presenca_compradorDerivados do destinatário quando omitidos.
modalidade_fretePadrão 9 (sem transporte).
valor_frete, valor_seguro, valor_outras_despesas, valor_descontoEm reais.
informacoes_adicionais_contribuinteAté 5000 caracteres, no rodapé do DANFE. Texto maior é cortado.
informacoes_adicionais_fiscoAté 2000 caracteres. Texto maior é cortado.
data_emissaoGerada pelo servidor no horário de São Paulo. Não recomendamos enviar.

NF-e de devolução

A devolução tem quatro exigências da SEFAZ que o EasyNotas aplica sozinho. Você só precisa informar a finalidade 4, a chave da nota original e o item correspondente.

Exigência da SEFAZO que o EasyNotas faz
CFOP de devolução só com finalidade 4Recusa antes de enviar quando o CFOP é de devolução e a finalidade não é 4.
Nota referenciada por item (DFeReferenciado)Na devolução, referencia só por item. Mandar a referência no cabeçalho e no item ao mesmo tempo gera a rejeição 1010.
Grupo de ST retido em CSOSN 500 / CST 60Envia o grupo zerado, como a nota original.
Devolução sem pagamentoForça a forma 90 (sem pagamento), seja qual for o pagamento enviado.
json
{
  "tipo": "nfe",
  "cnpj_emitente": "12345678000190",
  "destinatario": {
    "nome": "Supermercado Exemplo Ltda", "documento": "11444777000161",
    "ie": "111222333444", "contribuinte": true,
    "endereco": { "logradouro": "Av. Brasil", "numero": "1000", "bairro": "Centro",
                  "municipio": "Araçatuba", "uf": "SP", "cep": "16010000" }
  },
  "itens": [{
    "codigo": "FERM-01", "descricao": "Fermento biológico", "quantidade": 10,
    "valor_unitario": 9.98, "ncm": "21023000", "cst_csosn": "900",
    "aliquota_icms": 18, "origem": 3, "cfop": "5202",
    "numero_item_referenciado": 2
  }],
  "operacao": {
    "natureza_operacao": "Devolução de compra",
    "finalidade_emissao": 4,
    "chave_nfe_referenciada": "35260911444777000161550010000123451123456780"
  }
}

Neste exemplo, o CSOSN 900 com alíquota de 18% destaca base de R$ 99,80 e ICMS de R$ 17,96. A chave de 44 dígitos, agrupada de 4 em 4, fica assim: 3526 0911 4447 7700 0161 5500 1000 0123 4511 2345 6780.

Transporte e comunicação

Para cte, cte_os, cte_simp, mdfe, nfcom e dce, o corpo fiscal vai dentro de documento, no mesmo formato que o painel usa. O bloco do emitente é sempre montado a partir do cadastro e substitui qualquer empresa enviada.

json
{
  "tipo": "cte",
  "cnpj_emitente": "12345678000190",
  "documento": { /* campos do CT-e, iguais aos da tela de emissão */ }
}
Sob consulta. O formato de documento de cada tipo é passado pelo suporte do EasyNotas a quem for integrar transporte; esta página ainda não traz esses campos. Ainda sem emissão real pela API. Esses tipos usam os mesmos validadores do painel, mas nenhum foi emitido pela API em produção até agora. Teste em sandbox e acompanhe a primeira emissão real. Cada tipo exige o add-on correspondente no escritório (403 addon_required).

Consultar e listar

GET/v1/invoices/{id}escopo invoices:read
json
{
  "id": "920d1d7a-f721-40d7-a44b-f79af97dd208",
  "status": "authorized",
  "tipo": "nfe",
  "numero": "26",
  "pdf_url": "https://…/danfe.pdf",
  "xml_url": "https://…/35260911444777000161550010000123451123456780-nfe.xml",
  "chave_acesso": "35260911444777000161550010000123451123456780",
  "chave_nfe": "35260911444777000161550010000123451123456780",
  "mensagem": null,
  "created_at": "2026-09-30T13:47:21Z",
  "updated_at": "2026-09-30T13:50:03Z"
}

chave_acesso novo vem preenchida para documentos com chave de 44 dígitos (NF-e, CT-e, MDF-e…). Para NFS-e é null. chave_nfe continua existindo para integrações antigas. mensagem traz o motivo quando o status é rejected ou failed_internal.

GET/v1/invoices?status=&tipo=&page=&page_size=escopo invoices:read

Lista as notas do escritório, das mais novas para as mais antigas. page_size vai até 100 (padrão 20). A resposta traz data, page, page_size e has_more.

GET/v1/invoices/{id}/cancellation-windowescopo invoices:read

Diz se a nota ainda está no prazo estimado de cancelamento. É informativo e não bloqueia nada. Leia o campo confiavel: quando é false, o prazo é estimativa. NF-e: 24 horas. Transporte: 7 dias. NFS-e: null, porque o prazo é municipal, e há prefeituras que não aceitam cancelamento por sistema.

Cancelar nota

POST/v1/invoices/{id}/cancelescopo invoices:cancel
json
{ "justificativa": "Serviço não executado conforme contrato" }

Justificativa de 15 a 255 caracteres. A resposta é 202 com status: "processing", e a confirmação chega pelo webhook invoice.cancelled, normalmente em segundos. O 202 quer dizer "pedido enviado", não "cancelada": até a confirmação, o GET continua mostrando authorized. Nota já cancelada responde 200 com already_cancelled: true. Nota que não está autorizada responde 409 not_cancelable.

Inutilizar numeração de NF-e novo

Quando um número de NF-e foi pulado, a SEFAZ exige que ele seja inutilizado. A chamada é síncrona e a resposta já traz o resultado da SEFAZ.

Não repita depois de um timeout sem conferir. Esta rota não usa Idempotency-Key. Se a conexão cair, consulte o histórico (GET /v1/nfe/inutilizacoes) antes de enviar de novo.
POST/v1/nfe/inutilizacoesescopo numbering:write
json
{
  "cnpj_emitente": "12345678000190",
  "serie": "2",
  "numero_inicial": 15,
  "numero_final": 17,
  "justificativa": "Numeração pulada por falha no sistema de origem"
}

Regras: série de 1 a 3 dígitos, números inteiros maiores que zero, no máximo 1000 números por chamada e justificativa de 15 a 255 caracteres. Se algum número da faixa já foi transmitido pelo EasyNotas, inclusive em nota cancelada, a API recusa com 422 number_already_used antes de chegar à SEFAZ.

Autorizada, responde 201; recusada pela SEFAZ, 422. O corpo é o mesmo nos dois casos:

json
{
  "id": "7c1e…",
  "environment": "production",
  "status": "authorized",
  "serie": "2", "numero_inicial": 15, "numero_final": 17,
  "protocolo_sefaz": "135260001234567",
  "status_sefaz": "102",
  "mensagem_sefaz": "Inutilização de número homologado"
}
Inutilização não tem volta. Em sandbox a chamada vai para a homologação da SEFAZ e não entra no histórico (id é null).
GET/v1/nfe/inutilizacoes?cnpj_emitente=escopo invoices:read ou numbering:write

Histórico de inutilizações de produção da empresa, feitas pelo painel ou pela API (campo origem: app ou api). Tentativas recusadas também aparecem, com status: "rejected". Paginado com page e page_size.

Notas recebidas novo

O Buscador XML do EasyNotas acompanha na SEFAZ as notas que fornecedores emitem contra o CNPJ de cada empresa do escritório. A API lê o que já foi sincronizado; ela não dispara uma nova busca.

GET/v1/received?cnpj_emitente=&tipo=&desde=&ate=&manifestacao=escopo received:read
ParâmetroRegra
cnpj_emitenteObrigatório. CNPJ da sua empresa, a destinatária das notas. O nome segue o padrão das outras rotas.
tiponfe, nfce, cte ou nfse. Sem filtro, lista todos menos eventos.
desde, ateData de emissão, formato AAAA-MM-DD, fuso de São Paulo.
manifestacaopendente (sem manifestação), ciencia, confirmacao, desconhecimento ou nao_realizada.
page, page_sizePadrão 1 e 20; máximo 100.
json
{
  "data": [{
    "chave": "35260944555666000177550010000360621003500819",
    "tipo": "nfe",
    "emitente": { "cnpj": "44555666000177", "razao_social": "Distribuidora Exemplo Ltda" },
    "cnpj_destinatario": "12345678000190",
    "valor": 1280.40,
    "data_emissao": "2026-09-12T10:31:00-03:00",
    "situacao": "autorizada",
    "nivel": "resumo",
    "manifestacao": null,
    "xml_disponivel": false,
    "recebida_em": "2026-09-12T13:40:11Z"
  }],
  "page": 1, "page_size": 20, "has_more": false
}
GET/v1/received/{chave}escopo received:read

Busca a nota pela chave em todas as empresas do escritório. A chave tem 44 dígitos (NF-e, NFC-e, CT-e) ou 50 dígitos (NFS-e do Ambiente Nacional). Quando o XML completo existe, a resposta traz xml_url, um link de download válido por 10 minutos (xml_url_expires_in: 600). Baixe na hora e não guarde o link.

Por que o XML às vezes não vem. A SEFAZ só entrega o XML completo depois da manifestação. Antes disso a nota fica em nivel: "resumo", com dados básicos.

Manifestação do destinatário novo

POST/v1/received/{chave}/manifestescopo received:manifest
json
{
  "cnpj_emitente": "12345678000190",
  "tipo": "ciencia"
}
tipoDocumentoJustificativaLibera o XML
cienciaNF-enãosim
confirmacaoNF-e15 a 255 caracteressim
desconhecimentoNF-enãonão
nao_realizadaNF-e15 a 255 caracteresnão
desacordoCT-e15 a 255 caracteresnão

Resposta 200:

json
{
  "chave": "35260944555666000177550010000360621003500819",
  "manifestacao": "ciencia",
  "xml_disponivel": true,
  "consumiu_franquia": true
}

É o mesmo processo do botão do painel: registra o evento na SEFAZ, baixa o XML quando liberado e consome a franquia do plano quando o XML é baixado. Uma nota só pode ser manifestada uma vez: a segunda chamada responde 409 already_manifested.

Evento real e irreversível. Por isso a manifestação é bloqueada em sandbox (403 not_available_in_sandbox). No log do EasyNotas o evento fica registrado com origem api, em nome do dono do escritório que gerou a chave.

Webhooks

Cadastre uma URL em Configurações → API / Desenvolvedores → Webhook. O EasyNotas faz um POST quando uma nota criada por aquela chave chega a um estado final.

EventoQuando
invoice.authorizedNota autorizada; PDF e XML disponíveis.
invoice.rejectedRejeitada pela SEFAZ ou pela prefeitura.
invoice.cancelledCancelamento confirmado.
http
POST https://seu-sistema.com.br/webhooks/easynotas
X-EasyNotas-Event: invoice.authorized
X-EasyNotas-Delivery: 9f2c6a1e-…        // igual em todas as tentativas desta entrega
X-EasyNotas-Signature: t=1790780400,v1=8a1f…

{
  "event": "invoice.authorized",
  "created_at": "2026-09-30T13:50:03Z",
  "data": {
    "id": "920d1d7a-…", "status": "authorized", "tipo": "nfe",
    "environment": "production", "numero_nota": "26",
    "chave_nfe": "35260911444777000161550010000123451123456780",
    "pdf_url": "https://…", "xml_url": "https://…",
    "emitida_at": "2026-09-30T13:50:01Z", "cancelada_at": null
  }
}

O webhook traz a chave de acesso no campo chave_nfe (só dígitos), o mesmo valor que o GET devolve em chave_acesso.

Validando a assinatura

A assinatura é um HMAC-SHA256 de {t}.{corpo}, em hexadecimal, com o segredo whsec_… mostrado ao cadastrar o webhook. Cada vez que a URL é salva, um segredo novo é gerado e o anterior deixa de valer: atualize os dois juntos no seu sistema. Use o corpo bruto, exatamente como chegou, e compare em tempo constante.

node.js
const crypto = require('crypto');

function assinaturaValida(rawBody, header, secret) {
  const partes = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const esperado = crypto.createHmac('sha256', secret)
    .update(`${partes.t}.${rawBody}`)
    .digest('hex');
  const a = Buffer.from(partes.v1 || '', 'hex');
  const b = Buffer.from(esperado, 'hex');
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return false;
  return Math.abs(Date.now() / 1000 - Number(partes.t)) < 300; // recusa avisos com mais de 5 min
}

Reentrega

A entrega pode chegar mais de uma vez. Responda 2xx em até 10 segundos e processe depois. São 6 tentativas no total: a primeira na hora e as outras após 30 s, 2 min, 8 min, 32 min e cerca de 2 h. Depois disso a entrega é dada como falha. Redirecionamentos (301/302) contam como falha: a URL precisa responder 2xx direto. As entregas chegam com User-Agent: EasyNotas-Webhooks/1. Use X-EasyNotas-Delivery para descartar duplicatas. A URL precisa ser HTTPS pública na porta 443; endereços internos (localhost, 10.x, 172.16–31.x, 192.168.x, 169.254.x, 100.64.x, IPv6 local, domínios .local/.internal) e URLs com usuário e senha são recusados.

failed_internal não gera webhook. Se uma nota falhar antes de chegar à SEFAZ, nenhum aviso é enviado. Para notas sem evento depois de 30 minutos, consulte GET /v1/invoices/{id}.

Estados fiscais

StatusSignificadoO que fazer
queuedNa fila de emissão.Aguardar.
processingEm autorização.Consultar respeitando Retry-After.
authorizedAutorizada; PDF e XML disponíveis.Usar pdf_url e xml_url.
rejectedRejeitada pela SEFAZ ou prefeitura.Ler mensagem, corrigir e emitir com outra Idempotency-Key.
failed_internalFalha antes de chegar à SEFAZ. Não gera webhook.Reenviar ou acionar o suporte.
cancelledCancelada.Nada.

Códigos de erro

Todo erro vem como { "error": { "code", "message", "field"? } }. Nos erros de validação, field diz exatamente qual campo corrigir.

HTTPcodeQuando
400missing_environmentFalta o header X-EasyNotas-Environment.
400invalid_idempotency_keyHeader ausente ou fora do formato.
400invalid_jsonCorpo não é JSON válido.
400invalid_status_filter, invalid_manifestacao_filter, invalid_date_filterFiltro inválido numa listagem.
400invalid_tipo_filterFiltro tipo inválido em /v1/received.
400missing_cnpj_emitenteFalta ?cnpj_emitente= nas rotas de inutilização e recebidas.
401missing_api_key, invalid_api_key, expiredChave ausente, inválida ou vencida.
402payment_requiredEscritório bloqueado ou com pagamento pendente.
403revoked, environment_mismatchChave revogada, ou header de ambiente diferente do ambiente da chave.
403insufficient_scopeA chave não tem o escopo da rota.
403company_not_in_officeO CNPJ não é de uma empresa do seu escritório.
403note_type_not_enabled, addon_requiredEmpresa não habilitada para o tipo, ou add-on de transporte não contratado.
403not_available_in_sandbox novoManifestação chamada com chave de sandbox.
404not_found, route_not_foundNota ou rota inexistente.
409idempotency_conflict, idempotency_in_flightVeja Idempotência.
409not_cancelableA nota não está autorizada.
409already_manifested novoA nota recebida já tem manifestação.
422invalid_payloadCampo ausente ou inválido. Veja field.
422invalid_fiscal_payloadO validador fiscal recusou (soma de pagamentos, CFOP de devolução sem finalidade 4, etc.).
422field_inherited_from_companyCampo de regime enviado no corpo.
422unsupported_note_typetipo fora da lista.
422retencao_not_applicable novoRetenção federal para empresa do Simples ou MEI.
422invalid_justificativaJustificativa de cancelamento fora de 15–255 caracteres.
422number_already_used novoFaixa de inutilização contém número já transmitido.
422invalid_manifest_type, manifest_rejected novoTipo de manifestação incompatível com o documento, ou recusa da SEFAZ.
424certificate_missingEmpresa sem certificado digital.
424focus_not_configured, focus_token_missing novoEmpresa ainda não configurada no emissor fiscal. Acione o suporte.
429rate_limited, sefaz_quota_exceededRespeite Retry-After. O rate_limited também traz X-RateLimit-Limit e X-RateLimit-Remaining.
500internal_error e similaresFalha do EasyNotas. Se persistir, acione o suporte.
502cancel_dispatch_failedFalha ao encaminhar o cancelamento. A nota segue ativa; tente de novo.
503quota_unavailable, cancel_unavailable, numbering_unavailable, manifest_unavailableIndisponibilidade temporária. Tente de novo com backoff.

Limitações conhecidas

  • Documentos de transporte ainda sem emissão real pela API.
  • Carta de correção (CC-e) é feita só pelo painel.
  • tomador.codigo_municipio é validado, mas o município do tomador é resolvido pelo nome da cidade.
  • Os prazos de cancelamento da janela são estimativas; quem decide é a SEFAZ ou a prefeitura.
  • A API lê as notas recebidas já sincronizadas; a sincronização com a SEFAZ roda no próprio EasyNotas.
  • O domínio da API é o do provedor de infraestrutura. Um domínio próprio pode ser adotado no futuro, com aviso prévio.

OpenAPI

A especificação OpenAPI 3.1 (versão 1.1.0) cobre todas as rotas, os schemas e os erros. Ela está publicada em /openapi.yaml. Com ela você gera um cliente tipado na sua linguagem:

bash
# TypeScript
npx openapi-typescript openapi.yaml -o easynotas.d.ts

# C#, PHP, Python, Java: troque o -g
npx @openapitools/openapi-generator-cli generate \
  -i openapi.yaml -g csharp -o ./easynotas-client

Para navegar visualmente, abra o arquivo em editor.swagger.io.

Termos de Uso

O uso da API segue os Termos de Uso da API, aceitos por quem gera a chave no painel.

Suporte

Dúvidas de integração, habilitação de tipos de nota e configuração de empresas: fale com o time do EasyNotas pelo canal de suporte do seu escritório.