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 atual: NFS-e de São Paulo (capital), em expansão para outros municípios. O codigo_municipio (IBGE) define a prefeitura.

Autenticação

Autentique com sua API key via Bearer token. Chaves sk_test_ usam o sandbox; sk_live_ emitem de verdade. Gere e revogue chaves no painel.

Authorization: Bearer sk_live_a1b2c3d4...
🔒 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)."
  }
}

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-2026-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/2026.",
      "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-2026-00871",
  "created_at": "2026-06-24T14:30:00Z"
}

Estados possíveis: QUEUEDPROCESSING AUTHORIZED | REJECTED | FAILED_INTERNAL.

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

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

emitti · infraestrutura fiscal para o Brasil