# Simplifisca API — documentação completa

> API de nota fiscal para SaaS. NF-e (modelo 55) e NFS-e Padrão Nacional via REST ou MCP. Este arquivo foi escrito para ser colado no contexto de agentes de IA (Cursor, Claude Code, Lovable, Bolt, v0, Windsurf). Siga exatamente os nomes de campos daqui: campos desconhecidos são rejeitados com erro 422.

## Resumo rápido para agentes

1. Leia a chave da variável de ambiente `SIMPLIFISCA_API_KEY`. Nunca exponha a chave no front-end: chame a API só do backend.
2. Todas as requisições: `Authorization: Bearer $SIMPLIFISCA_API_KEY` e `Content-Type: application/json`.
3. Base URL: `https://app.simplifisca.com.br/api/v1`.
4. Emissão é ASSÍNCRONA: `POST /nfe` ou `POST /nfse` responde `202` com `status: "processando"`. O resultado chega por webhook (`nfe.autorizada`, `nfe.rejeitada`...) ou por `GET /nfe/{id}`.
5. Sempre envie `Idempotency-Key` (ex.: id do pagamento/pedido) para nunca emitir nota duplicada em retentativas.
6. Valide o webhook com HMAC-SHA256 (ver seção Webhooks) antes de confiar no conteúdo.
7. Dados fiscais (NCM, CFOP, CSOSN/CST, código de serviço, alíquota de ISS) vêm do contador do usuário. Não invente esses valores para produção; use os exemplos só em homologação.

## Conceitos

- **Emitente**: a empresa (CNPJ) que emite a nota. Uma conta pode ter vários emitentes (ex.: um SaaS que emite em nome dos clientes). Cada emitente precisa de um certificado digital A1 (.pfx) e tem seu próprio `ambiente`.
- **Ambiente**: `homologacao` (testes, sem valor fiscal, ilimitado) ou `producao` (vale de verdade, conta na cota do plano). Troque com `PATCH /emitentes/{id}` `{"ambiente": "producao"}`.
- **Catálogo fiscal**: produtos (NF-e) e serviços (NFS-e) com a tributação já configurada (pelo contador, no painel ou via API). Com o catálogo, a nota só precisa do código: `{"produto": "SKU-001", "quantidade": 2}` ou `{"servico": {"codigo": "PLANO-PRO"}}`.
- **Documento**: uma NF-e ou NFS-e. Tem `id` (UUID), `status`, `chaveAcesso`, `protocolo`, `links.xml` e `links.pdf`.

## Autenticação

Crie chaves no painel: https://app.simplifisca.com.br/painel/chaves/. A chave (`sf_live_...`) aparece uma única vez. Envie em `Authorization: Bearer sf_live_...` (ou `X-Api-Key`). Limite: 120 requisições por minuto por chave.

```bash
curl https://app.simplifisca.com.br/api/v1/conta -H "Authorization: Bearer $SIMPLIFISCA_API_KEY"
```

`GET /api/v1/conta` → plano, status da assinatura e uso do mês:

```json
{"empresa": "Minha Empresa", "assinatura": {"status": "trial", "ativa": true, "fimTrial": "2026-10-16T10:00:00-03:00", "proximaCobranca": null}, "plano": {"slug": "trial", "nome": "Teste grátis", "notasMes": 20, "emitentes": 1}, "uso": {"mes": "2026-10", "notasProducao": 3, "emitentesAtivos": 1}}
```

## Emitentes

### Cadastrar — `POST /api/v1/emitentes` (201)

```json
{
  "cnpj": "11222333000181",
  "razaoSocial": "Minha Empresa LTDA",
  "nomeFantasia": "Minha Empresa",
  "inscricaoEstadual": "123456789",
  "inscricaoMunicipal": "12345",
  "regimeTributario": 1,
  "ambiente": "homologacao",
  "endereco": {"logradouro": "Rua das Flores", "numero": "100", "complemento": "Sala 2", "bairro": "Centro", "codigoMunicipio": "3550308", "municipio": "São Paulo", "uf": "SP", "cep": "01001000"},
  "email": "fiscal@minhaempresa.com.br",
  "telefone": "11999999999",
  "nfe": {"serie": "1", "proximoNumero": 1},
  "nfse": {"serie": "1", "proximoNumero": 1}
}
```

Campos:
- `cnpj` (obrigatório, 14 dígitos válidos), `razaoSocial` (obrigatório).
- `inscricaoEstadual`: obrigatória para NF-e. `inscricaoMunicipal`: obrigatória para NFS-e.
- `regimeTributario` (CRT): `1` Simples Nacional, `2` Simples com excesso de sublimite, `3` Regime normal (Lucro Presumido/Real), `4` MEI.
- `endereco.codigoMunicipio`: código IBGE de 7 dígitos (ex.: São Paulo 3550308). `cep`: 8 dígitos. `uf`: sigla.
- `nfe.proximoNumero` / `nfse.proximoNumero`: se a empresa já emitia notas em outro sistema, informe o próximo número da série para não colidir.

### Certificado A1 — `PUT /api/v1/emitentes/{id}/certificado`

```json
{"arquivoBase64": "<conteúdo do .pfx em base64>", "senha": "senha-do-certificado"}
```

O certificado é validado (senha, validade e CNPJ) e guardado criptografado. Gerar o Base64: `base64 -i certificado.pfx` (macOS) ou `base64 -w0 certificado.pfx` (Linux). Em Node: `fs.readFileSync('cert.pfx').toString('base64')`.

### Outros

- `GET /api/v1/emitentes` — lista paginada.
- `GET /api/v1/emitentes/{id}` — `{id}` aceita o UUID ou o CNPJ.
- `PATCH /api/v1/emitentes/{id}` — atualiza qualquer campo (exceto CNPJ).
- `DELETE /api/v1/emitentes/{id}` — remove (ou desativa se já emitiu notas).

## Catálogo fiscal (recomendado)

Cadastre a tributação uma vez por produto/serviço e emita só com o código. Campos enviados na nota sobrescrevem os do catálogo (ex.: outro `valorUnitario` ou `cfop`).

### Produtos — `POST /api/v1/produtos` (201) ou `PUT /api/v1/produtos/{codigo}` (upsert)

```json
{"codigo": "SKU-001", "descricao": "Camiseta básica", "ncm": "61091000", "cfop": "5102", "unidade": "UN", "valorUnitario": 59.90, "impostos": {"icms": {"origem": 0, "csosn": "102"}, "pis": {"cst": "07"}, "cofins": {"cst": "07"}}}
```

### Serviços — `POST /api/v1/servicos` ou `PUT /api/v1/servicos/{codigo}`

```json
{"codigo": "PLANO-PRO", "descricao": "Assinatura mensal do plano Pro (software como serviço)", "codigoTributacaoNacional": "010501", "codigoNbs": "115013000", "aliquotaIss": 2.0, "valor": 99.90}
```

Também: `GET /api/v1/produtos?busca=camiseta`, `GET/PATCH/DELETE /api/v1/produtos/{codigo}` (idem `/servicos`). `valorUnitario`/`valor` são opcionais; sem eles, informe o preço na nota.

### Emitir usando o catálogo

```json
{"emitente": "11222333000181", "referencia": "pedido-1043", "destinatario": { ... }, "itens": [{"produto": "SKU-001", "quantidade": 2}], "pagamentos": [{"forma": "17", "valor": 119.80}]}
```

```json
{"emitente": "11222333000181", "referencia": "assinatura-779", "tomador": { ... }, "servico": {"codigo": "PLANO-PRO"}}
```

Dica para SaaS: use o id do plano/produto do seu sistema como `codigo` no catálogo.

## NF-e (produto) — `POST /api/v1/nfe`

```json
{
  "emitente": "11222333000181",
  "referencia": "pedido-1042",
  "naturezaOperacao": "Venda de mercadoria",
  "destinatario": {
    "cpfCnpj": "11144477735",
    "nome": "Cliente Exemplo",
    "email": "cliente@email.com",
    "indicadorIe": 9,
    "endereco": {"logradouro": "Av. Brasil", "numero": "500", "bairro": "Jardins", "codigoMunicipio": "3550308", "municipio": "São Paulo", "uf": "SP", "cep": "01430000"}
  },
  "itens": [{
    "codigo": "SKU-001", "descricao": "Camiseta básica", "ncm": "61091000", "cfop": "5102",
    "unidade": "UN", "quantidade": 2, "valorUnitario": 59.90,
    "impostos": {"icms": {"origem": 0, "csosn": "102"}, "pis": {"cst": "07"}, "cofins": {"cst": "07"}}
  }],
  "pagamentos": [{"forma": "17", "valor": 119.80}]
}
```

Header recomendado: `Idempotency-Key: pedido-1042`.

Campos da NF-e:
- `emitente` (obrigatório): UUID ou CNPJ do emitente.
- `referencia`: seu identificador (até 100 caracteres). Filtrável em `GET /nfe?referencia=`.
- `naturezaOperacao`: padrão "Venda de mercadoria".
- `finalidade`: `1` normal (padrão), `2` complementar, `3` ajuste, `4` devolução.
- `consumidorFinal`: `true` (padrão).
- `presencial`: `2` internet (padrão), `1` presencial, `9` outros.
- `modalidadeFrete`: `9` sem frete (padrão), `0` emitente, `1` destinatário.
- `informacoesComplementares`, `informacoesFisco`: texto livre.
- `destinatario.cpfCnpj`: CPF ou CNPJ válido. `indicadorIe`: `9` não contribuinte (padrão; use para pessoa física), `1` contribuinte (exige `inscricaoEstadual`), `2` isento.
- `itens[]` (1 a 990) — cada item usa `produto` (código do catálogo) OU os campos completos abaixo:
  - `codigo`, `descricao`, `ncm` (8 dígitos), `cfop` (4 dígitos; `5102` venda dentro do estado, `6102` para outro estado), `unidade` (padrão `UN`), `quantidade`, `valorUnitario`, `desconto` (opcional), `ean` (padrão `SEM GTIN`), `valorTributosAprox` (opcional, Lei da Transparência).
  - `impostos.icms`: informe **exatamente um** entre:
    - `csosn` (Simples Nacional): `101`, `102`, `103`, `300`, `400`. Para `101` informe `aliquotaCredito`.
    - `cst` (regime normal): `00` e `20` exigem `aliquota` (ex.: 18); `20` aceita `reducaoBc`; `40`, `41`, `50` sem alíquota. `baseCalculo` e `valor` são calculados se omitidos.
    - `origem`: `0` nacional (padrão).
  - `impostos.pis` e `impostos.cofins`: `cst` obrigatório. `04`–`09` sem valores (ex.: `07` isenta, comum no Simples). `01`/`02` exigem `aliquota` (ex.: PIS 1.65, COFINS 7.6). `49`/`99` aceitam `aliquota` opcional.
- `pagamentos[]`: `forma` (`01` dinheiro, `03` cartão de crédito, `04` débito, `15` boleto, `17` PIX, `90` sem pagamento, `99` outros), `valor`, `aPrazo` (bool). A soma deve cobrir o total da nota.

## NFS-e (serviço, Padrão Nacional) — `POST /api/v1/nfse`

```json
{
  "emitente": "11222333000181",
  "referencia": "assinatura-778",
  "tomador": {
    "cpfCnpj": "11144477735",
    "nome": "Fulano de Tal",
    "email": "fulano@email.com",
    "endereco": {"logradouro": "Rua A", "numero": "10", "bairro": "Centro", "codigoMunicipio": "3550308", "uf": "SP", "cep": "01001000"}
  },
  "servico": {
    "descricao": "Assinatura mensal de software (SaaS)",
    "codigoTributacaoNacional": "010501",
    "codigoNbs": "115013000"
  },
  "valores": {"servico": 99.90, "aliquotaIss": 2.0, "issRetido": false}
}
```

Campos da NFS-e:
- `tomador`: quem contrata o serviço. `endereco` é opcional para pessoa física em vários municípios, mas recomendado.
- `servico.codigoTributacaoNacional` (cTribNac, 6 dígitos): item (2) + subitem (2) + desdobramento (2) da LC 116. Exemplos: `010101` análise e desenvolvimento de sistemas; `010501` licenciamento/cessão de uso de software (SaaS); `170601` propaganda e publicidade.
- `servico.codigoNbs` (opcional, 9 dígitos), `codigoTributacaoMunicipal` (opcional), `municipioPrestacao` (IBGE; padrão = município do emitente).
- `valores.servico` (obrigatório), `deducoes`, `descontoIncondicionado`, `aliquotaIss` (percentual; ignorada para MEI), `issRetido`.
- `competencia` (AAAA-MM-DD, padrão hoje), `informacoesComplementares`.
- Funciona para emitentes em municípios aderentes à NFS-e Padrão Nacional.

## Resposta de documento (NF-e e NFS-e)

```json
{
  "id": "8f3c2a1e-5b7d-4c1a-9f0e-2d6b8a4c1e90",
  "tipo": "nfe",
  "status": "autorizada",
  "ambiente": "homologacao",
  "referencia": "pedido-1042",
  "emitente": {"id": "0b9e...", "cnpj": "11222333000181"},
  "numero": 1,
  "serie": "1",
  "chaveAcesso": "35261011222333000181550010000000011234567890",
  "protocolo": "135260000000001",
  "valorTotal": "119.80",
  "sefaz": {"codigo": "100", "mensagem": "Autorizado o uso da NF-e"},
  "tentativas": 0,
  "criadoEm": "2026-10-09T14:03:11-03:00",
  "autorizadoEm": "2026-10-09T14:03:14-03:00",
  "canceladoEm": null,
  "links": {"self": "/api/v1/nfe/8f3c...", "xml": "/api/v1/nfe/8f3c.../xml", "pdf": "/api/v1/nfe/8f3c.../pdf"}
}
```

NFS-e também traz `numeroNfse` (número oficial atribuído pelo sistema nacional). Os links `xml`/`pdf` exigem o mesmo header de autenticação; baixe no backend e sirva ao seu usuário (ou guarde no seu storage).

### Status

| status | significado | o que fazer |
|---|---|---|
| `processando` | na fila / enviando à SEFAZ | aguarde webhook ou consulte de novo em alguns segundos |
| `autorizada` | nota válida | salve `chaveAcesso` e baixe PDF/XML |
| `rejeitada` | SEFAZ/prefeitura recusou | leia `sefaz.codigo` e `sefaz.mensagem`, corrija e envie NOVA requisição (nova Idempotency-Key) |
| `erro` | falha técnica após 6 tentativas automáticas | tente de novo mais tarde |
| `cancelando` | cancelamento em andamento | aguarde |
| `cancelada` | cancelada | — |

Se a SEFAZ estiver fora do ar, o documento continua `processando` e é retentado automaticamente (30s, 2min, 10min, 30min, 2h, 6h). Antes de cada retentativa a API consulta a SEFAZ para garantir que não haverá nota em duplicidade.

### Consultar, baixar, listar, cancelar

- `GET /api/v1/nfe/{id}` (ou `/nfse/{id}`)
- `GET /api/v1/nfe/{id}/xml` — XML autorizado (`?tipo=cancelamento` para o XML do evento de cancelamento)
- `GET /api/v1/nfe/{id}/pdf` — DANFE (NF-e) ou DANFSe (NFS-e)
- `GET /api/v1/nfe?status=autorizada&referencia=pedido-1042&emitente=11222333000181&criadoDe=2026-10-01&criadoAte=2026-10-31&pagina=1&porPagina=50`
- `POST /api/v1/nfe/{id}/cancelamento` `{"justificativa": "Pedido cancelado pelo cliente final"}` (15 a 255 caracteres) → `202` com `status: "cancelando"`. NF-e pode ser cancelada em até 24h após a autorização na maioria das UFs.

Listas retornam `{"dados": [...], "paginacao": {"pagina": 1, "porPagina": 50, "total": 120, "temProxima": true}}`.

## Idempotência

Envie `Idempotency-Key: <até 100 caracteres>` no `POST /nfe` e `POST /nfse`. Se a mesma chave for reenviada, a API devolve o documento original com status HTTP `200` e header `Idempotent-Replayed: true`, sem criar outra nota. Use o id do pagamento ou do pedido.

## Webhooks

`POST /api/v1/webhooks`:

```json
{"url": "https://meusaas.com/webhooks/simplifisca", "eventos": ["nfe.autorizada", "nfe.rejeitada", "nfse.autorizada", "nfse.rejeitada"]}
```

Resposta (201) inclui `segredo` (`whsec_...`), exibido só agora. `eventos` vazio = todos. Eventos: `nfe.autorizada`, `nfe.rejeitada`, `nfe.erro`, `nfe.cancelada`, `nfse.autorizada`, `nfse.rejeitada`, `nfse.erro`, `nfse.cancelada`. Teste com `POST /api/v1/webhooks/{id}/teste` (envia evento `ping`). Remova com `DELETE /api/v1/webhooks/{id}`.

Corpo enviado (POST, JSON):

```json
{"id": "evt_3f9a...", "evento": "nfe.autorizada", "criadoEm": "2026-10-09T14:03:14-03:00", "dados": { ...objeto documento... }}
```

Headers: `X-Simplifisca-Evento`, `X-Simplifisca-Evento-Id`, `X-Simplifisca-Assinatura: t=<unix>,v1=<hex>`.

Verificação: `v1 == HMAC_SHA256(chave=segredo, mensagem=f"{t}.{corpo_bruto}")` em hexadecimal, onde `segredo` é a string inteira `whsec_...` e `corpo_bruto` é o corpo exatamente como recebido (não re-serialize o JSON). Rejeite se `t` tiver mais de 5 minutos.

Node (Express):

```js
import crypto from "node:crypto";
app.post("/webhooks/simplifisca", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("X-Simplifisca-Assinatura") || "";
  const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const esperado = crypto.createHmac("sha256", process.env.SIMPLIFISCA_WEBHOOK_SECRET)
    .update(`${t}.${req.body.toString("utf8")}`).digest("hex");
  const valido = v1 && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(esperado));
  if (!valido || Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.status(400).end();
  const evento = JSON.parse(req.body.toString("utf8"));
  // evento.evento === "nfe.autorizada" → salvar evento.dados.chaveAcesso etc.
  res.status(200).end();
});
```

Python:

```python
import hmac, hashlib, time
def webhook_valido(corpo_bruto: bytes, header: str, segredo: str) -> bool:
    partes = dict(p.split("=", 1) for p in header.split(","))
    esperado = hmac.new(segredo.encode(), f"{partes['t']}.".encode() + corpo_bruto, hashlib.sha256).hexdigest()
    return hmac.compare_digest(esperado, partes.get("v1", "")) and abs(time.time() - int(partes["t"])) <= 300
```

Responda `2xx` em até 10 segundos. Sem `2xx`, reenviamos até 8 vezes (10s, 1min, 5min, 30min, 1h, 2h, 6h, 12h). O mesmo evento pode chegar mais de uma vez: use `id` do evento para deduplicar.

## Erros

```json
{"erro": {"codigo": "validacao", "mensagem": "Dados inválidos.", "detalhes": [{"campo": "itens.0.ncm", "mensagem": "deve ter 8 dígitos"}], "requestId": "req_3f9a1c7e2b8d4a6051c0e9f2"}}
```

Toda resposta traz o header `X-Request-Id`. Ao reportar um problema ao suporte, envie o `requestId`: com ele localizamos a requisição exata.

| HTTP | codigo | quando |
|---|---|---|
| 400 | json_invalido | corpo não é JSON |
| 401 | nao_autenticado | chave ausente, inválida ou revogada |
| 402 | assinatura_inativa | trial expirado / pagamento pendente |
| 402 | limite_plano | cota mensal de notas em produção ou de emitentes atingida |
| 404 | nao_encontrado | id/CNPJ inexistente nesta conta |
| 409 | conflito | ex.: cancelar documento não autorizado, CNPJ já cadastrado |
| 422 | validacao | campo inválido/ausente/desconhecido (veja `detalhes`) |
| 422 | certificado_ausente / certificado_invalido / emitente_incompleto | emitente não pronto para emitir |
| 429 | limite_requisicoes | mais de 120 req/min |
| 500 | erro_interno | tente de novo |

## MCP (agentes de IA)

Endpoint: `https://app.simplifisca.com.br/mcp` (Streamable HTTP, JSON-RPC 2.0). Autenticação: header `Authorization: Bearer sf_live_...`.

Claude Code:

```bash
claude mcp add --transport http simplifisca https://app.simplifisca.com.br/mcp --header "Authorization: Bearer sf_live_SUA_CHAVE"
```

Cursor (`.cursor/mcp.json`):

```json
{"mcpServers": {"simplifisca": {"url": "https://app.simplifisca.com.br/mcp", "headers": {"Authorization": "Bearer sf_live_SUA_CHAVE"}}}}
```

Ferramentas: `conta`, `listar_emitentes`, `cadastrar_emitente`, `enviar_certificado`, `listar_catalogo`, `salvar_produto`, `salvar_servico`, `emitir_nfe`, `emitir_nfse` (aceitam `idempotencyKey`), `consultar_documento`, `listar_documentos`, `cancelar_documento`, `exemplo_payload` (JSON válido de nfe, nfse, emitente, produto, servico, nfe_catalogo, nfse_catalogo).

## Receita: emitir nota quando o pagamento for confirmado

0. (Uma vez) Cadastre seus planos/produtos no catálogo com o mesmo código usado no seu sistema.
1. No webhook do seu gateway (Stripe, Mercado Pago, Asaas, AbacatePay...), quando o pagamento for aprovado, chame `POST https://app.simplifisca.com.br/api/v1/nfse` com `servico.codigo` = código do plano (ou `/nfe` com `itens[].produto`) e `Idempotency-Key: <id do pagamento>` e `referencia: <id do pedido>`.
2. Salve o `id` retornado no seu pedido, com status "processando".
3. No webhook `nfse.autorizada`/`nfe.autorizada` da Simplifisca (validando a assinatura), localize o pedido por `dados.referencia`, salve `chaveAcesso` e baixe `links.pdf` para enviar ao cliente.
4. Em `*.rejeitada`, mostre `dados.sefaz.mensagem` para o admin corrigir o cadastro.

## Planos

- Start: R$ 49,90/mês — 100 notas/mês em produção, 3 emitente(s).
- Pro: R$ 149,90/mês — 1000 notas/mês em produção, 20 emitente(s).
- Scale: R$ 449,90/mês — 5000 notas/mês em produção, emitentes ilimitados.
- Teste grátis: 7 dias, até 20 notas em produção. Homologação é ilimitada em todos os planos. Pagamento via PIX.

