Documentação da API
A API do Emitti abstrai a complexidade de emitir notas fiscais de serviço no Brasil. Você envia JSON; nós cuidamos da fila, retentativas, assinatura digital (XMLDSig), envelopamento SOAP e do webhook de confirmação. Toda emissão é assíncrona.
Introdução
Base URL: https://api.emitti.com.br/v1. Respostas em JSON e códigos HTTP padrão. O fluxo é sempre: você faz o POST, recebe 202 Accepted na hora, e o resultado final chega por webhook (ou consulta).
Autenticação
Autentique com sua API key via Bearer token. Chaves sk_test_ usam o sandbox; sk_live_ emitem contra a prefeitura. Gere e revogue chaves no painel.
Authorization: Bearer sk_live_a1b2c3d4...ambiente (homologacao | producao) na resposta e no webhook. Só a nota emitida em producao tem valor fiscal — confira o campo antes de entregá-la ao seu cliente.Quickstart
Três passos para a primeira nota:
- No painel, gere uma API key e cadastre um emitente com o certificado A1 (.pfx).
- Faça o
POST /v1/nfsecom os dados do serviço. - Receba o resultado no seu webhook (ou consulte por
GET).
Sandbox
Chaves sk_test_ processam em sandbox: respostas determinísticas, sem tocar a prefeitura nem exigir certificado. Use para testar sua integração de ponta a ponta sem custo.
- Por padrão a nota é autorizada (número
SANDBOX-*). - Gatilho de rejeição: tomador com documento todo zeros (ex.
00000000000000) → respostaREJECTED, para você exercitar o tratamento de erro.
{
"status": "REJECTED",
"sandbox": true,
"resultado": {
"codigo_emitti": "tomador_documento_invalido",
"mensagem": "[sandbox] CPF/CNPJ do tomador inválido (gatilho de teste)."
}
}Cobertura e ativação
O Emitti cobre todo o Brasil. Cada cidade tem um status: cidades ativas emitem na hora; as demais a gente ativa no seu onboarding — em até 2 dias úteis, com os dados do seu emitente. Você cadastra agora e entra na fila; avisamos quando ativar.
Consulte a cobertura na página emitti.com.br/cobertura ou pela API (público, sem autenticação):
GET/v1/municipios?q=&uf=&status=
[
{
"codigo": "3550308",
"nome": "São Paulo",
"uf": "SP",
"status": "ativa",
"emite_agora": true,
"provedor": "PMSP (São Paulo)",
"sla": "até 2 dias úteis após o onboarding"
}
]Status: ativa (emite na hora), homologacao, em_integracao, sob_demanda. Também há GET /v1/municipios/{codigo} para uma cidade.
Cobertura da NFC-e (por UF)
A NFC-e é estadual (SEFAZ), então a cobertura é por UF, não por município. Mesmo modelo sob demanda: UFs ativas emitem na hora; as demais ativamos no onboarding do lojista (configurando a NFC-e do emitente com IE + CSC). Consulte por UF (público, sem autenticação):
GET/v1/municipios/nfce/ufs
[
{ "uf": "RS", "nome": "Rio Grande do Sul", "status": "sob_demanda",
"emite_agora": false, "autorizador": null,
"sla": "até 2 dias úteis após o onboarding" }
]Ativando sua cidade
Se sua cidade ainda não está ativa, a ativação é simples e você participa:
- Cadastre o emitente no painel escolhendo sua cidade — você entra na fila automaticamente.
- Suba o certificado A1 (.pfx) do emitente.
- Em alguns municípios é preciso solicitar a liberação do WebService junto à prefeitura — a gente te orienta qual e como.
- Integramos e validamos em homologação e te avisamos quando a cidade ficar ativa.
Emitir uma NFS-e
POST/v1/nfse
Headers
| Header | Obrigatório | Descrição |
|---|---|---|
Authorization | Sim | Bearer da sua API key. |
Idempotency-Key | Recomendado | UUID único — evita nota duplicada em retry. |
Exemplo — cURL
curl -X POST https://api.emitti.com.br/v1/nfse \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7f3a-...-91b" \
-d '{
"referencia_externa": "pedido-2025-00871",
"prestador": { "cnpj": "12345678000190", "inscricao_municipal": "1122334" },
"tomador": {
"razao_social": "Cliente Exemplo LTDA",
"cnpj": "98765432000110",
"email": "financeiro@cliente.com.br"
},
"servico": {
"codigo_municipio": "3550308",
"codigo_servico": "01.05",
"discriminacao": "Assinatura mensal do plano SaaS - Junho/2025.",
"valor_servicos": 499.90,
"aliquota_iss": 2.0,
"iss_retido": false
}
}'Exemplo — Node.js
const res = await fetch("https://api.emitti.com.br/v1/nfse", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.EMITTI_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
prestador: { cnpj: "12345678000190", inscricao_municipal: "1122334" },
tomador: { razao_social: "Cliente Exemplo LTDA", cnpj: "98765432000110" },
servico: {
codigo_municipio: "3550308",
codigo_servico: "01.05",
discriminacao: "Assinatura mensal do plano SaaS",
valor_servicos: 499.9,
aliquota_iss: 2.0,
},
}),
});
const emissao = await res.json(); // { emissao_id, status: "QUEUED" }Resposta — 202 Accepted
{
"emissao_id": "emi_8f3a9c2e1b7d4f60",
"status": "QUEUED",
"referencia_externa": "pedido-2025-00871",
"created_at": "2025-06-24T14:30:00Z"
}Estados possíveis: QUEUED → PROCESSING → AUTHORIZED | REJECTED | FAILED_INTERNAL.
Simples Nacional não vai no corpo da nota. O regime é atributo do emitente cadastrado (campo regime_tributario: simples_nacional, mei ou normal), definido no Dashboard. É dele que sai o <OptanteSimplesNacional> do RPS — Simples e MEI emitem como optantes. O campo é obrigatório no cadastro: não escolhemos por você, porque um chute erra o ISS. Se o seu emitente foi criado antes disso, confira o regime no Dashboard antes de emitir — sem ele, a nota sai como não-optante e a prefeitura calcula o ISS pela alíquota cheia.
Consultar uma emissão
GET/v1/nfse/{emissao_id}
Útil para reconciliação. Recomendamos confiar nos webhooks como fonte primária.
{
"emissao_id": "emi_8f3a9c2e1b7d4f60",
"status": "AUTHORIZED",
"resultado": {
"numero_nfse": "00012845",
"codigo_verificacao": "ABCD-1234"
}
}Cancelar uma NFS-e
DELETE/v1/nfse/{emissao_id}
Assíncrono: gera o cancelamento na prefeitura. Só é possível cancelar uma nota AUTHORIZED. O resultado chega por webhook (nfse.canceled).
Substituir uma NFS-e
POST/v1/nfse/{emissao_id}/substituicao
Emite uma nova nota (corpo igual ao de POST /v1/nfse) referenciando a antiga; ao autorizar a nova, a antiga é cancelada automaticamente (exigência de muitas prefeituras).
Baixar PDF e XML
GET/v1/nfse/{emissao_id}/pdf— DANFE/RPS renderizado
GET/v1/nfse/{emissao_id}/xml— XML autorizado
Emitir uma NFC-e
POST/v1/nfce— modelo 65, varejo/consumidor (SEFAZ)
Mesmo fluxo assíncrono da NFS-e (202 Accepted + webhook), mas para venda de mercadoria ao consumidor. Os dados fiscais do emitente (inscrição estadual e CSC) ficam no cadastro do emitente — no corpo você envia só os itens e os pagamentos. A UF vem do emitente (ou do campo uf). Requer que a UF esteja ativa.
Exemplo — cURL
curl -X POST https://api.emitti.com.br/v1/nfce \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9c2e-...-7a1" \
-d '{
"referencia_externa": "pdv-2025-04412",
"prestador": { "cnpj": "12345678000190" },
"uf": "RS",
"itens": [
{ "codigo": "SKU1", "descricao": "Camiseta", "ncm": "61091000",
"cfop": "5102", "quantidade": 1, "valor_unitario": 59.90 }
],
"pagamentos": [ { "forma": "01", "valor": 59.90 } ]
}'Itens: codigo, descricao, ncm, cfop, quantidade, valor_unitario (e csosn/origem/ean opcionais). Pagamentos: forma (tPag: 01=dinheiro, 03=crédito, 17=Pix…) e valor. O resultado (chave de acesso, protocolo, QR Code) chega por webhook.
O regime tributário também vem do cadastro do emitente (regime_tributario) e define o CRT da NFC-e: simples_nacional → 1, mei → 4, normal → 3. Hoje a NFC-e suporta apenas Simples Nacional e MEI (ICMS via CSOSN); emitente de regime normal é rejeitado com regime_normal_nao_suportado_nfce — o ICMS por CST está no roadmap. Fale com a gente se você precisa disso.
Modo síncrono (PDV / baixa latência)
POST/v1/nfce?sync=1— resposta na hora (200), sem esperar webhook
Para o caixa (PDV), onde o cupom precisa sair com o cliente esperando: com ?sync=1 a resposta é síncrona (200) e traz status, numero_nfse (chave de acesso), codigo_verificacao (protocolo) e qr_code — normalmente em 1–3 s. Se a SEFAZ estiver instável e o emitente tiver contingência habilitada, a nota sai offline na hora (contingencia: true) e é transmitida depois. Sem sync, o padrão é assíncrono (202 + webhook), ideal para integrações em lote.
Cancelar uma NFC-e
DELETE/v1/nfce/{emissao_id}
Evento de cancelamento na SEFAZ (assíncrono). Exige uma justificativa de 15 a 255 caracteres (regra da SEFAZ). Consulte o status por GET /v1/nfse/{emissao_id} (a chave é o emissao_id, vale para NFS-e e NFC-e).
curl -X DELETE https://api.emitti.com.br/v1/nfce/emi_8f3a9c2e1b7d4f60 \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "justificativa": "Cancelamento a pedido do cliente no PDV." }'DANFE-NFC-e (cupom em PDF)
GET/v1/nfce/{emissao_id}/pdf— cupom ~80mm p/ impressora térmica
Devolve o DANFE-NFC-e (cupom) em PDF: emitente, itens, totais, chave de acesso, protocolo e o QR Code — pronto para imprimir ou exibir ao consumidor. Disponível após a nota ser autorizada. Em homologação o cupom traz o aviso “SEM VALOR FISCAL”.
Webhooks
Quando a emissão chega a um estado final, enviamos um POST para a URL configurada no painel. Todo webhook traz o header X-Emitti-Signature (HMAC-SHA256 do corpo). Sempre valide a assinatura antes de confiar no payload.
Payload — autorizada
{
"event_type": "nfse.authorized",
"data": {
"emissao_id": "emi_8f3a9c2e1b7d4f60",
"status": "AUTHORIZED",
"nfse": { "numero": "00012845", "codigo_verificacao": "ABCD-1234",
"url_pdf": "https://files.emitti.com.br/...pdf" }
}
}Payload — rejeitada (erro traduzido)
{
"event_type": "nfse.rejected",
"data": {
"emissao_id": "emi_2d1c0b9a8f7e",
"status": "REJECTED",
"erro": {
"codigo_emitti": "tomador_documento_invalido",
"mensagem": "O CNPJ do tomador é inválido ou não está na Receita Federal.",
"sugestao": "Verifique o CNPJ '98765432000110' e reenvie.",
"retentavel": false
}
}
}codigo_emitti (estável), mensagem humana, sugestao e o flag retentavel.Verificando a assinatura — Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
function verificar(req) {
const assinatura = req.headers["x-emitti-signature"];
const esperado = createHmac("sha256", process.env.EMITTI_WEBHOOK_SECRET)
.update(req.rawBody)
.digest("hex");
return timingSafeEqual(Buffer.from(assinatura), Buffer.from(esperado));
}Erros
| Código | Significado | Causa típica |
|---|---|---|
400 | Bad Request | JSON malformado ou campo inválido. |
401 | Unauthorized | API key ausente, inválida ou revogada. |
402 | Payment Required | Limite do plano atingido. |
409 | Conflict | Idempotency-Key reutilizada com payload diferente. |
422 | Unprocessable | Dado semanticamente inválido (ex.: CNPJ com DV errado, emitente inexistente). |
429 | Too Many Requests | Rate limit excedido. |
5xx | Server Error | Falha interna do Emitti (não sua). |
Roadmap
O que está no ar e o que vem a seguir. Ordem e datas podem mudar conforme a demanda.
- ✅ NFS-e — São Paulo (capital): emissão, cancelamento e substituição em produção.
- ✅ NFC-e (modelo 65, varejo/consumidor): emissão e cancelamento na API, webhooks, DANFE-NFC-e (PDF) e SDKs (Node, Python, Go). Ativação por UF sob demanda no onboarding — veja Emitir NFC-e.
- ✅ Pronto para PDV e alto volume: modo síncrono (resposta na hora), contingência offline (emite mesmo com a SEFAZ fora e transmite depois) e preço por uso (pay-as-you-go) para o grande varejo.
- 🔜 Mais municípios (NFS-e) e mais UFs (NFC-e): expansão da cobertura conforme a demanda;
codigo_municipio(IBGE) seleciona a prefeitura eufseleciona a SEFAZ.
emitti · infraestrutura fiscal para o Brasil