emitti

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).

Cobertura nacional: atendemos todo o Brasil. Cidades ativas emitem na hora; as demais ativamos no seu onboarding (até 2 dias úteis). Veja o status em Cobertura. Emitimos NFS-e (serviço) e NFC-e (varejo/consumidor, modelo 65) — a mesma API assíncrona, webhooks e SDKs.

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...
Cada emissão devolve o campo 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.
🔒 Nunca exponha a chave secreta no frontend ou em repositórios públicos.

Quickstart

Três passos para a primeira nota:

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.

{
  "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:

Emitir uma NFS-e

POST/v1/nfse

Headers

HeaderObrigatórioDescrição
AuthorizationSimBearer da sua API key.
Idempotency-KeyRecomendadoUUID ú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: QUEUEDPROCESSING 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
    }
  }
}
Diferencial Emitti: sempre devolvemos 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ódigoSignificadoCausa típica
400Bad RequestJSON malformado ou campo inválido.
401UnauthorizedAPI key ausente, inválida ou revogada.
402Payment RequiredLimite do plano atingido.
409ConflictIdempotency-Key reutilizada com payload diferente.
422UnprocessableDado semanticamente inválido (ex.: CNPJ com DV errado, emitente inexistente).
429Too Many RequestsRate limit excedido.
5xxServer ErrorFalha 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.

Precisa de um município específico ou da NFC-e com prioridade? Fale com a gente — o roadmap segue a demanda dos clientes.

emitti · infraestrutura fiscal para o Brasil