Portal do desenvolvedor
A API REST v1 da MadNotas recebe JSON, devolve JSON e fala com a SEFAZ e com a Sefin Nacional por você, para NF-e, NFC-e, NFS-e e as notas recebidas (DF-e). Esta página reúne o que vale para todas as rotas; os guias mostram cada fluxo.
Primeiros passos
Da chave de simulação à primeira NF-e autorizada.
Referência da API
Todas as rotas, campos e respostas, com botão Experimente.
Erros e rejeições
Cada código de rejeição da SEFAZ e da Sefin Nacional explicado, com o que corrigir.
NFS-e Nacional
Nota de serviço: o que é igual à NF-e, o que muda e a primeira nota em simulação.
DF-e: notas recebidas
As NF-e emitidas contra o seu CNPJ: leitura, ciência, XML e manifestação.
Endereço base
https://notas.madbuilder.dev/api/v1
Todas as rotas usam HTTPS, recebem Content-Type: application/json e respondem em UTF-8. Datas seguem ISO 8601 com fuso (2026-10-29T14:03:12-03:00); valores em reais usam ponto decimal e até 2 casas.
Listas (GET /nfe, /nfce, /webhooks, /webhooks/eventos, /nfe/{id}/eventos e as demais) aceitam ?limite de 1 a 100 e devolvem tem_mais e proximo_cursor: para a página seguinte, repita a chamada com ?cursor= e esse valor. As tabelas de /utilitarios aceitam limite, mas não cursor. Parâmetro inválido responde 422 parametro_invalido, com o motivo em detalhes (lista de erros).
Autenticação
Envie a chave de API no cabeçalho Authorization. A chave é criada no painel, em Integração › Chaves de API, e aparece uma única vez: guarde-a no cofre de segredos do seu sistema.
Authorization: Bearer mn_sim_SuaChaveAqui
O prefixo da chave define o ambiente. Não existe parâmetro para trocar de ambiente: quem decide é a chave.
| Prefixo | Ambiente | SEFAZ | Certificado | Conta na franquia |
|---|---|---|---|---|
mn_sim_ | Simulação | Simulada, nunca a real | Não precisa | Não |
mn_hom_ | Homologação | Real, ambiente de testes (tpAmb 2) | A1 do emitente | Não |
mn_prod_ | Produção | Real, com valor fiscal (tpAmb 1) | A1 do emitente | Sim |
Com mn_hom_ ou mn_prod_ e o emitente sem A1 válido, a emissão volta 422 certificado_indisponivel antes de reservar número. O botão Experimente desta documentação usa só chave mn_sim_; as outras são recusadas com 403 origem_docs_nao_permitida.
Teto por chave, útil para revenda. Cada chave pode ter o próprio limite de requisições por minuto, de 1 até o do plano: em Integração › Chaves de API, no campo Limite por minuto ao criar a chave ou na ação de mesmo nome na lista. Vazio usa o do plano. Assim, uma software house dá a cada cliente uma chave com um teto, e um cliente com laço de reenvio não consome o limite dos outros. O teto vale para todas as rotas da chave, e passar dele responde 429 com Retry-After. GET /conta/uso mostra, em chave.limite_efetivo, o limite que vale para a chave usada.
Uma chave pode ficar restrita a um emitente (CNPJ) e a algumas permissões. Chave revogada responde 401 na chamada seguinte. Toda resposta traz o cabeçalho MadNotas-Ambiente com o ambiente da chave usada.
Formato de erro
Erro de requisição (chave, permissão, payload inválido, limite) volta sempre no mesmo envelope, com status HTTP adequado:
{
"erro": {
"tipo": "autenticacao",
"codigo": "chave_invalida",
"mensagem": "Chave de API inválida, expirada ou revogada.",
"detalhes": []
},
"request_id": "req_01jbx4k2m9q7t3v8w5z6y1r0ac"
}| HTTP | Quando |
|---|---|
| 401 | Chave ausente, inválida ou revogada. |
| 402 | Conta suspensa ou limite de notas de produção do período de teste atingido. |
| 403 | Empresa inativa ou chave sem permissão para a rota. |
| 404 | Recurso não existe ou pertence a outra conta. |
| 409 | Mesma ref com conteúdo diferente e a nota não está rejeitada. |
| 413 | Corpo da requisição acima de 5 MB (corpo_muito_grande). Recusado antes de ser lido e antes da autenticação, com o mesmo envelope de erro. |
| 415 | Corpo enviado com Content-Type que não é JSON (tipo_conteudo_invalido). |
| 422 | Payload inválido. Nada foi criado nem numerado; detalhes lista todos os campos com o caminho no JSON. Também quando falta o certificado A1 do emitente em homologação ou produção (certificado_indisponivel). |
| 429 | Limite de requisições por minuto. Espere os segundos de Retry-After. |
Rejeição da SEFAZ não é erro HTTP. A nota foi criada e está no estado rejeitada: a resposta é 201 com a lista erros, e você corrige reenviando com a mesma ref. Veja Erros e rejeições.
Estados da nota
| Status | Significado | Final? |
|---|---|---|
| processando | Recebida e a caminho da SEFAZ ou aguardando resposta. Consulte de novo ou espere o webhook. | Não |
| autorizada | SEFAZ autorizou o uso. XML e DANFE disponíveis. | Pode ser cancelada |
| rejeitada | SEFAZ recusou. Corrija e reenvie: o número é mantido. | Não |
| denegada | Uso denegado por irregularidade fiscal do emitente ou do destinatário. | Sim |
| contingencia | NFC-e emitida offline; será transmitida quando a SEFAZ voltar. | Não |
| cancelada | Cancelamento homologado pela SEFAZ. | Sim |
| descartada | Rejeitada que você desistiu de corrigir. O número volta para uso. | Sim |
Idempotência e reenvio seguro
Toda nota leva uma ref: o identificador do pedido no seu sistema, único por ambiente e modelo, um texto de até 100 caracteres com letras, números e . : / - _. Vazia, só com espaços, com outro caractere ou que não seja texto volta 422 com ref_invalida no campo ref (no /validar, valido: false com o mesmo erro); "ref": null é nota sem ref (e sem a proteção abaixo). Ela torna o reenvio seguro depois de um timeout:
- Mesma
refe mesmo conteúdo: a API devolve a nota já criada (200comIdempotent-Replayed: true). - Mesma
ref, conteúdo novo e notarejeitada: vale como correção, com o mesmo número. - Mesma
ref, conteúdo novo e qualquer outro estado:409.
Nas demais rotas de escrita, envie o cabeçalho Idempotency-Key (até 255 caracteres; acima disso, 400 idempotency_key_invalida). A resposta da primeira chamada fica guardada por 24 horas e é repetida para a mesma chave. A inutilização também é segura para repetir: pedir de novo a mesma faixa já inutilizada devolve 200 com Idempotent-Replayed: true e o mesmo id.
Limites de uso
Cada chave tem um limite de requisições por minuto, definido pelo plano (veja Preços) ou por um teto menor gravado na própria chave (veja Autenticação). As respostas trazem RateLimit-Limit, RateLimit-Remaining e RateLimit-Reset; acima do limite, 429 com Retry-After.
Rastreio de chamadas
Toda resposta traz X-Request-Id. Você pode enviar o seu (8 a 64 caracteres: letras, números, . _ : -) e ele é repetido na resposta e no log. Informe esse valor ao falar com o suporte: no painel, em Integração › Logs da API, ele leva à requisição e à resposta completas.
Teste a chave agora
curl https://notas.madbuilder.dev/api/v1/ping \ -H "Authorization: Bearer $MADNOTAS_CHAVE"
$ch = curl_init('https://notas.madbuilder.dev/api/v1/ping');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('MADNOTAS_CHAVE')],
CURLOPT_RETURNTRANSFER => true,
]);
$resposta = json_decode(curl_exec($ch), true);
echo $resposta['ambiente']; // simulacaoconst r = await fetch('https://notas.madbuilder.dev/api/v1/ping', {
headers: { Authorization: `Bearer ${process.env.MADNOTAS_CHAVE}` },
});
console.log(await r.json()); // { ok: true, ambiente: 'simulacao', ... }import os, requests
r = requests.get(
"https://notas.madbuilder.dev/api/v1/ping",
headers={"Authorization": f"Bearer {os.environ['MADNOTAS_CHAVE']}"},
)
print(r.json()["ambiente"]) # simulacao