API de emissão de NFS-e 1.3.0
Esta página monta a referência interativa no navegador. O guia completo e o contrato OpenAPI, que é a mesma fonte usada aqui, estão em notaguard.com.br/api/openapi-emissao.json.
Rotas
GET /v1/me: Dados da chave, da empresa e do ambientePOST /v1/nfse/preparar: Prepara um lote e devolve o que assinarPOST /v1/nfse/emitir: Transmite o lote assinado à SEFAZPOST /v1/nfse/cancelamento/preparar: Prepara um lote de cancelamentos e devolve o que assinarPOST /v1/nfse/cancelamento/registrar: Registra os cancelamentos assinados na SEFAZGET /v1/nfse/por-referencia/{ref}: O que o ledger sabe de uma referência
Guia
Interface HTTP para emitir NFS-e pelo Padrão Nacional em nome de terceiros, sem
custodiar o certificado digital deles. O emitente assina cada DPS na própria
infraestrutura; o NotaGuard transmite à SEFAZ sob o certificado dele, como
transmissor. Emitente e transmissor são papéis distintos e o Padrão Nacional
aceita que sejam CNPJs diferentes.
## Visão geral
| | |
| --- | --- |
| URL base | `https://api-emissao.notaguard.com.br` (produção, notas com valor fiscal) e `https://api-emissao-teste.notaguard.com.br` (produção restrita). **Nenhum dos dois é `api.notaguard.com.br`**, que atende a API de leitura |
| Versão | `v1` |
| Autenticação | Bearer token no header `Authorization` |
| Ambiente | Determinado pela chave e pelo host, que andam juntos. `nge_test_` emite em produção restrita, no host de teste; `nge_live_` emite nota com valor fiscal, no host de produção |
| Formato | JSON. Datas em ISO 8601 |
| Lote | Até 10 notas por chamada |
| Validade do token | 24 horas entre `preparar` e `emitir` |
| CORS | Não habilitado. As chamadas partem do seu servidor |
## Autenticação
```
Authorization: Bearer nge_test_sua_chave_aqui
```
### Onde criar a chave
No painel, em **Configurações > Integrações > API de emissão**, botão "Nova
chave". Só proprietário ou administrador da empresa vê a opção. Comece pelo
ambiente **produção restrita** para integrar: a chave nasce com prefixo
`nge_test_` e as notas não têm valor fiscal. Quando a integração estiver
pronta, crie a chave de **produção** (`nge_live_`) na mesma tela. A URL base
de cada ambiente aparece ao lado da chave.
O plano da empresa tem que incluir acesso por API; sem isso a criação é recusada
com o motivo na tela.
O token completo aparece uma única vez, na criação. Perdido, revogue a chave e
crie outra: rotacionar cria a nova e revoga a antiga no mesmo passo.
O prefixo é topologia, não parâmetro: uma chave `nge_test_` é recusada no host
de produção, e vice-versa, antes de qualquer chamada à SEFAZ (`acesso_negado`,
403).
Chave de emissão (`nge_`) e chave de leitura (`ng_live_`) são de APIs
diferentes, em hosts diferentes. As duas publicam `/v1/me`, então apontar a
chave de emissão para o host de leitura devolve **200** e passa a impressão de
que a integração está de pé. Confira a URL base antes da primeira chamada.
O host da emissão com valor fiscal é `https://api-emissao.notaguard.com.br`. Uma chave `nge_test_` é recusada nele, e o contrário também.
## Primeira chamada
`GET /v1/me` valida a chave e diz contra qual ambiente ela emite. Não toca a
SEFAZ e não é cobrada.
```bash
curl "https://api-emissao-teste.notaguard.com.br/v1/me" \
-H "Authorization: Bearer nge_test_sua_chave_aqui"
```
```json
{
"chave": { "id": "…", "prefixo": "nge_test_abc", "escopos": ["nfse:emitir"] },
"empresa": { "id": "EXEMPL1", "nome": "Empresa Exemplo Ltda", "documento": "11222333000181" },
"plano": { "tier": "standard" },
"ambiente": { "codigo": "2", "rotulo": "producao restrita", "emite_com_valor_fiscal": false }
}
```
## O fluxo tem duas fases
A assinatura acontece entre elas, do seu lado, e é por isso que não existe um
endpoint único.
1. **`POST /v1/nfse/preparar`** recebe o conteúdo fiscal em JSON e o
certificado público do emitente. Valida, monta o DPS e devolve o
`SignedInfo` a assinar, além de um `token` que sela o XML gerado.
2. Você assina o `SignedInfo` localmente com a chave privada do emitente.
3. **`POST /v1/nfse/emitir`** recebe apenas o `token` e as assinaturas. O XML
não volta na requisição: ele já está selado no token, o que impede que o
documento transmitido seja diferente do que foi assinado.
4. Se o `emitir` não fechar (`nao_enviada`), **`GET /v1/nfse/por-referencia/{ref}`**
diz o que o ledger sabe daquela nota, sem tocar a SEFAZ e sem custo.
O token carrega o ambiente e a **empresa**, não a chave que o criou: ele abre em
qualquer chave da mesma empresa, no mesmo ambiente. Se você separa sistemas por
chave, o token não é a fronteira entre eles.
### Uma nota, do começo ao fim
O menor payload que emite. Prestador optante do Simples, serviço tributado no
município, sem retenção. Não há `pAliq` aqui de propósito: nessa combinação a
alíquota do ISSQN é do município e informá-la é rejeição (ver "Simples
Nacional").
Os curls abaixo mostram o formato de cada requisição. **Para rodar de ponta a
ponta**, incluindo os dois valores que dependem do seu certificado
(`certificado_publico` e `assinatura`), use o script Node do fim desta seção.
```bash
curl -X POST "https://api-emissao-teste.notaguard.com.br/v1/nfse/preparar" \
-H "Authorization: Bearer nge_test_sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"notas": [{
"referencia": "pedido-4821",
"certificado_publico": "MIIH…",
"dps": {
"serie": "900",
"nDPS": "1041",
"dCompet": "2026-08-15",
"tpEmit": "1",
"cLocEmi": "3205309",
"prestador": {
"CNPJ": "11222333000181",
"regTrib": { "opSimpNac": "3", "regApTribSN": "1", "regEspTrib": "0" }
},
"tomador": { "CNPJ": "11444777000161", "xNome": "Cliente Exemplo Ltda" },
"servico": {
"locPrest": { "cLocPrestacao": "3205309" },
"cServ": { "cTribNac": "010601", "xDescServ": "Consultoria em informática" }
},
"valores": {
"vServPrest": { "vServ": "1500.00" },
"trib": {
"tribMun": { "tribISSQN": "1", "tpRetISSQN": "1" },
"totTrib": { "pTotTribSN": "6.00" }
}
}
}
}]
}'
```
A resposta traz o `token` e, por nota, o `assinar`:
```json
{
"token": "TkdUMQEC…",
"expira_em": "2026-08-16T12:05:16.000Z",
"notas": [{ "referencia": "pedido-4821", "assinar": "PGRzOlNpZ25lZEluZm8…", "dps_xml": "<DPS …" }]
}
```
Assine o `assinar` (ver "O campo `assinar`") e transmita:
```bash
curl -X POST "https://api-emissao-teste.notaguard.com.br/v1/nfse/emitir" \
-H "Authorization: Bearer nge_test_sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"token": "TkdUMQEC…",
"notas": [{ "referencia": "pedido-4821", "assinatura": "Vk9…" }]
}'
```
```json
{
"notas": [{
"referencia": "pedido-4821",
"status": "autorizada",
"cobrada": true,
"chave_acesso": "32053092211222333000181000000000000826082840614132",
"nfse_xml": "H4sIAAAAAAAAA…"
}]
}
```
**Guarde o `nfse_xml`.** É o XML autorizado da NFS-e, em gzip + base64, e é a
única cópia que você vai ter: a nossa expira, e a SEFAZ não deixa o transmissor
consultar o documento depois. Ele sai em toda nota autorizada e **não volta no
reenvio idempotente do mesmo token**, então grave na primeira resposta.
Dentro dele há coisas que não estão na resposta HTTP e costumam surpreender:
- **A NFS-e tem numeração própria**, `nNFSe` (sequencial do emitente) e
`nDFSe` (do sistema nacional), **diferentes do seu `nDPS`**. Quem reconcilia
com o próprio sistema precisa dos três.
- **A SEFAZ preenche os dados do emitente** a partir do cadastro dela: nome,
endereço, telefone e e-mail aparecem no XML mesmo que você não os tenha
enviado. Não é vazamento do seu payload; é o registro dela.
- `cStat` `100` é a autorização, e `dhProc`, `verAplic` e `ambGer` dizem
quando, por qual versão e em qual ambiente ela foi processada.
### O mesmo fluxo, executável
Os `MIIH…` e `Vk9…` dos curls acima saem do seu certificado, então não há como
publicá-los aqui. O script abaixo roda as duas fases de ponta a ponta e é o
caminho mais curto entre a chave e a primeira nota autorizada.
**Antes de rodar**, três coisas suas: a chave (`nge_test_`), o `.pfx` do
prestador com a senha, e os dados fiscais da nota. Precisa de Node 18 ou mais
novo, por causa do `fetch`.
```bash
mkdir emissao-teste && cd emissao-teste
npm init -y
npm install git+https://github.com/notaguard/assinador-nfse.git#v1.1.0
cp /caminho/do/seu/certificado.pfx .
export NOTAGUARD_API_KEY='nge_test_sua_chave_aqui'
export SENHA_PFX='senha-do-seu-certificado'
```
Salve como `emitir.js` e rode com `node emitir.js`.
```js
const fs = require("fs");
const {
carregarPfx,
certificadoBase64Der,
assinarPreparacao,
AssinadorErro,
} = require("@notaguard/assinador-nfse");
const BASE = "https://api-emissao-teste.notaguard.com.br"; // producao: https://api-emissao.notaguard.com.br, com chave nge_live_
const PFX = fs.readFileSync("certificado.pfx");
// TROQUE por dados seus. O prestador.CNPJ tem que ser o titular do
// certificado: neste fluxo quem recusa primeiro é o preparar, com 422 e
// certificado_diverge_prestador, e a lib faz a mesma checagem na hora de
// assinar. serie + nDPS sao seu sequencial: repetir o par devolve E0014.
// pTotTribSN é a alíquota efetiva do Simples DA SUA EMPRESA, não um exemplo:
// o valor abaixo vai impresso na nota como tributo informado ao tomador.
// Para mandar a nota por e-mail ao tomador, acrescente email: { anexos:
// ["xml"] } ao lado de dps e preencha tomador.email (ver "E-mail ao
// tomador"). Sem o objeto email, nada e enviado.
const dps = {
serie: "900",
nDPS: "1041",
dCompet: "2026-08-15",
tpEmit: "1",
cLocEmi: "3205309",
prestador: {
CNPJ: "11222333000181",
regTrib: { opSimpNac: "3", regApTribSN: "1", regEspTrib: "0" },
},
tomador: { CNPJ: "11444777000161", xNome: "Cliente Exemplo Ltda" },
servico: {
locPrest: { cLocPrestacao: "3205309" },
cServ: { cTribNac: "010601", xDescServ: "Consultoria em informática" },
},
valores: {
vServPrest: { vServ: "1500.00" },
trib: {
tribMun: { tribISSQN: "1", tpRetISSQN: "1" },
totTrib: { pTotTribSN: "6.00" },
},
},
};
const chamar = async (rota, corpo) => {
const r = await fetch(`${BASE}${rota}`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.NOTAGUARD_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(corpo),
// O emitir fala com a SEFAZ e a borda corta em 29s (ver "Tempo de
// resposta"). Node nao tem timeout de leitura por padrao.
signal: AbortSignal.timeout(35000),
});
// A borda pode responder sem corpo JSON (429, 503): ler como texto primeiro
// evita trocar o erro da API por um erro de parse.
const texto = await r.text();
let json;
try {
json = texto ? JSON.parse(texto) : {};
} catch {
throw new Error(`${rota} -> ${r.status}: ${texto.slice(0, 200)}`);
}
if (!r.ok) throw new Error(`${rota} -> ${r.status}: ${JSON.stringify(json)}`);
return json;
};
(async () => {
// A parte pública do certificado, em base64 do DER, que o preparar exige.
const { certificate } = carregarPfx(PFX, process.env.SENHA_PFX);
const preparacao = await chamar("/v1/nfse/preparar", {
notas: [{
referencia: "pedido-4821",
certificado_publico: certificadoBase64Der(certificate),
dps,
}],
});
// Assina o SignedInfo já canonizado, sem recanonizar, e confere o digest do
// infDPS antes de tocar a chave privada. Erro tipado, nada é assinado.
let corpoEmitir;
try {
corpoEmitir = assinarPreparacao({
pfx: PFX,
senha: process.env.SENHA_PFX,
preparacao,
});
} catch (e) {
if (e instanceof AssinadorErro) {
console.error(`assinatura recusada [${e.codigo}]: ${e.message}`);
process.exit(1);
}
throw e;
}
const { notas } = await chamar("/v1/nfse/emitir", corpoEmitir);
console.log(notas[0]);
// Reconciliacao: o que o ledger guardou daquela referencia. Gratis, nao toca
// a SEFAZ. E a saida quando o emitir responde nao_enviada.
const consulta = await fetch(`${BASE}/v1/nfse/por-referencia/pedido-4821`, {
headers: { Authorization: `Bearer ${process.env.NOTAGUARD_API_KEY}` },
});
console.log(consulta.status, await consulta.json());
})();
```
O primeiro `console.log` imprime a nota: `status`, `chave_acesso` quando
autorizada, e `erro` com código da SEFAZ quando rejeitada. O segundo mostra a
mesma nota lida pela rota de consulta.
Referência completa da lib, incluindo lote grande, reaproveitamento do PFX e os
erros tipados: [README do `assinador-nfse`](https://github.com/notaguard/assinador-nfse#readme).
### Escolhas mutuamente exclusivas
O schema aceita os campos irmãos, mas a validação recusa a combinação:
| Grupo | Informe |
| --- | --- |
| `prestador` | `CNPJ` **ou** `CPF`. O prestador é sempre nacional: `NIF` e `cNaoNIF` voltam como `Campo desconhecido` |
| `tomador`, `intermediario` | `CNPJ` **ou** `CPF` **ou** `NIF` **ou** `cNaoNIF`, nunca dois |
| `valores.trib.totTrib` | `pTotTribSN` (optante do Simples) **ou** `vTotTrib` **ou** `pTotTrib` **ou** `indTotTrib` |
| `servico.locPrest` | `cLocPrestacao` (Brasil) **ou** `cPaisPrestacao` (exterior) |
## Simples Nacional
Quatro campos descrevem o regime, e eles se condicionam.
| Campo | Onde | O que diz |
| --- | --- | --- |
| `opSimpNac` | `prestador.regTrib` | 1 não optante, 2 MEI, 3 ME/EPP optante |
| `regApTribSN` | `prestador.regTrib` | regime de apuração; obrigatório quando `opSimpNac` é 3 |
| `pTotTribSN` | `valores.trib.totTrib` | alíquota efetiva do Simples, exclusiva de optante (`E0713`) |
| `pAliq` | `valores.trib.tribMun` | alíquota do ISSQN, proibida na combinação abaixo (`E0625`) |
Optante do Simples precisa informar o total dos tributos, então `indTotTrib`
está fora para ele (`E0712`). Quem não é optante não pode usar `pTotTribSN`
(`E0713`). Os dois são a mesma escolha, vista dos dois lados do regime.
`pTotTribSN` e `pAliq` não se substituem: o primeiro é o total estimado de
tributos que a nota informa ao tomador, o segundo é a alíquota do ISSQN.
### Quando `pAliq` é proibida
Com o ISSQN apurado pelo Simples, a alíquota é a que o município registrou, e
informá-la volta como `E0625`. A rejeição exige as cinco condições ao mesmo
tempo:
1. `prestador.regTrib.opSimpNac` = `3`, na competência da DPS;
2. `prestador.regTrib.regApTribSN` = `1`;
3. o município onde o ISSQN incide tem convênio ativo no Sistema Nacional;
4. não há benefício municipal, ou o que há não é isenção nem alíquota diferenciada;
5. `valores.trib.tribMun.tpRetISSQN` = `1` (sem retenção).
Omita `pAliq` nesse caso. O `preparar` barra antes da SEFAZ quando o payload
prova as cinco, o que cobre o caminho comum: serviço tributado no município do
próprio prestador, sem benefício municipal declarado. Com o ISSQN incidindo em
outro município, o convênio dele não está no payload, e aí quem responde é a
SEFAZ.
Fora dessa combinação a alíquota continua sua: com retenção (`tpRetISSQN` 2 ou
3), com `regApTribSN` 2 ou 3, ou fora do Simples, `pAliq` é informada
normalmente.
## O campo `assinar`
O valor de `assinar` é o `SignedInfo` **já canonizado**, em base64. Assine
esses bytes exatamente como vieram, com RSA-SHA256, e devolva a assinatura em
base64.
```
assinatura = base64( RSA-SHA256( base64_decode(assinar), chave_privada ) )
```
A canonicalização é **C14N 1.0 inclusiva**
(`http://www.w3.org/TR/2001/REC-xml-c14n-20010315`), que é o que a SEFAZ exige
e o que aparece no `CanonicalizationMethod` do `SignedInfo` que você recebe.
Não é a exclusiva (`xml-exc-c14n#`).
Não recanonize. O `SignedInfo` cru e o canonizado têm tamanhos diferentes (a
C14N expande tags auto-fechadas), e o `emitir` verifica a assinatura contra a
versão canonizada, antes da SEFAZ: assinar outros bytes devolve
`assinatura_invalida` na nota, sem transmissão e sem cobrança.
A biblioteca Node `@notaguard/assinador-nfse` faz essa etapa. Antes de tocar a
chave privada, ela recomputa o digest do `infDPS` a partir do `dps_xml` e o
compara com o `DigestValue` do `SignedInfo`; divergência aborta a assinatura
com `digest_divergente`. A checagem não tem como desligar: sem ela, um servidor
comprometido poderia exibir uma nota de R$ 100 e pedir assinatura no digest de
outra.
Código-fonte público, instalado por tag:
```bash
npm install git+https://github.com/notaguard/assinador-nfse.git#v1.1.0
```
## Campos que o servidor controla
`tpAmb`, `verAplic` e `dhEmi` são carimbados na preparação e **recusados** se
enviados no payload. O `dhEmi` é o instante deste servidor no fuso de Brasília,
que é a régua da SEFAZ: o relógio da sua máquina não entra na nota.
A `dCompet` continua sua, e não pode ser posterior ao dia de hoje em Brasília.
## E-mail ao tomador
A nota autorizada sai por e-mail para o tomador quando a nota carrega o objeto
`email`, irmão de `dps`. Sem ele, nada é enviado. Está incluso no preço.
```json
{
"referencia": "pedido-4821",
"certificado_publico": "MIIH…",
"email": { "anexos": ["xml"] },
"dps": {
"tomador": { "CNPJ": "11444777000161", "email": "financeiro@cliente.com.br" }
}
}
```
O objeto `email` diz **se** envia; o documento diz **para quem**. O endereço
sai do `tomador.email` do XML assinado, nunca deste corpo, então o único
destino possível é o tomador da nota. Por isso `tomador.email` sozinho não liga
o envio, e `email` sem `tomador.email` é pendência do `preparar`
(`email_do_tomador_ausente`), na fase que não cobra.
`anexos` aceita `["xml"]`, que é o padrão, e `[]`, que avisa o tomador com
a chave de acesso e sem anexo. `pdf` devolve `anexo_indisponivel`: o DANFSe
ainda não é gerado por esta API.
O envio acontece depois da autorização, fora da resposta do `emitir`, **em
lote uma vez por dia**: entre a nota ser autorizada e o tomador receber pode
passar até 24 horas. Não é caminho para aviso imediato; se o seu produto
promete e-mail na hora, mande o seu e use este como cópia fiscal. A resposta não
confirma entrega, e não há rota de consulta de e-mail nesta versão.
## Numeração
`serie` e `nDPS` são obrigatórios e seus. O NotaGuard não mantém contador:
quem controla o certificado controla o sequencial. O par `serie` + `nDPS` para
o mesmo prestador identifica a DPS, e repeti-lo devolve `E0014`. A colisão
acontece dentro do par, então uma série exclusiva da integração não conflita com
a numeração que o emitente usa em outros sistemas.
A `serie` sai do documento **preenchida com zeros à esquerda**, em 5 dígitos:
`900` vira `00900` no XML e na NFS-e. Quem reconcilia por série contra o
próprio sistema compara os dois formatos.
Rejeição não consome número. DPS recusada pela SEFAZ pode ser corrigida e
reenviada com a mesma `serie` e o mesmo `nDPS`: o `E0014` fala de nota já
autorizada, e o registro de uma rejeição não bloqueia a tentativa seguinte.
## Idempotência e reconciliação
O ledger indexa cada nota por `<sua empresa>#<id da DPS>`. Reenviar o mesmo token
depois de uma emissão bem-sucedida devolve o resultado guardado, com
`cobrada: false`, sem retransmitir à SEFAZ.
Quando `emitir` responde `nao_enviada`, o estado na SEFAZ é **indeterminado**:
a nota pode ter sido autorizada. Não reemita com outro número, porque isso cria
uma segunda nota. Reenvie o **mesmo token**: a trava do ledger resolve o caso em
que a nota já existe.
### Se a nota ficar presa
Enquanto o token valer, reenviar o **mesmo token** é a saída para qualquer
`nao_enviada`: ele retransmite, ou devolve o resultado já guardado.
`nao_enviada` com `motivo: "em_processamento"` diz que outra tentativa desta
mesma DPS não fechou. Enquanto ela não fechar, repetir devolve a mesma resposta.
Uma tentativa que morreu no meio deixa de bloquear depois de 15 minutos, e a
partir daí um `preparar` novo da mesma série e número volta a transmitir.
`resposta_perdida` e `duplicidade_apos_reenvio` são os dois casos em que a
nota pode já estar autorizada na SEFAZ sem que tenhamos a chave: o envio saiu
daqui e a resposta não voltou. Nenhum dos dois é cobrado.
Não emita com outro número para contornar: o número novo cria uma segunda nota,
e a primeira pode ter sido autorizada. Consulte
`GET /v1/nfse/por-referencia/{ref}`: se ela responder `autorizada`, a
`chave_acesso` está lá. O `nfse_xml` **não** volta nem na consulta nem no
reenvio do mesmo token (o reenvio idempotente devolve só `chave_acesso`):
grave-o na primeira resposta autorizada. Se a consulta responder
`enviando` depois de 15 minutos, ou se o token já expirou sem desfecho, fale
com o suporte informando a `referencia`, a série e o número da DPS.
### Consulta por referência
`GET /v1/nfse/por-referencia/{ref}` devolve o registro mais recente que o
ledger tem daquela `referencia` **para a sua empresa**: `status`
(`enviando`, `autorizada` ou `rejeitada`), `chave_acesso`, `cStat`
(código da SEFAZ quando rejeitada), `id_dps`, `tipo` e `ts`. Não devolve
o XML: ele sai uma única vez, na resposta autorizada do `emitir`. Não toca a
SEFAZ, não é cobrada, e uma referência de outra empresa responde 404 como se
não existisse.
```bash
curl "https://api-emissao-teste.notaguard.com.br/v1/nfse/por-referencia/pedido-4821" \
-H "Authorization: Bearer nge_test_sua_chave_aqui"
```
```json
{ "status": "autorizada", "chave_acesso": "3205309221…", "cStat": null, "id_dps": "DPS3205309…", "tipo": "emissao", "ts": "2026-08-18T14:03:11.000Z" }
```
Se a mesma `referencia` foi usada em mais de uma nota, volta a mais recente:
referência única por nota é o que faz esta rota servir de reconciliação.
## Erros
Todo erro traz um código estável, mensagem em português e `id_requisicao`. Os
da aplicação trazem também dois campos que existem por causa da cobrança:
| Campo | O que diz |
| --- | --- |
| `responsabilidade` | `cliente`, `notaguard`, `sefaz` ou `ninguem` |
| `cobrada` | se a chamada entrou no consumo faturável |
A taxonomia determina o que entra na fatura:
- **`E0xxx`** é regra de negócio sobre os seus dados. Rejeição cobrável, no
máximo uma por DPS por dia.
- **`E1200` a `E1209`** é o certificado de transmissão do NotaGuard. Problema
nosso, nunca cobra.
- **5xx, timeout ou SEFAZ fora** não cobra ninguém.
O campo `sefaz` aparece quando a recusa veio de lá, com o código cru e a
mensagem original, para você não depender da nossa tradução.
As respostas que a borda devolve antes da aplicação (as três primeiras da tabela
seguinte, mais o 429 e o 503) não trazem `responsabilidade` nem `cobrada`.
Nenhuma delas cobra.
### Códigos do envelope
Da requisição inteira. Os três primeiros vêm da borda, que responde com corpo
fixo: cada um agrupa várias causas e o corpo não diz qual foi. Quem precisa
distinguir informa o `id_requisicao` ao suporte.
| `codigo` | HTTP | Quando |
| --- | --- | --- |
| `nao_autorizado` | 401 | chave ausente, inválida, revogada ou expirada |
| `acesso_negado` | 403 | a chave não tem `nfse:emitir`, é de outro ambiente, o IP não está liberado, ou o plano não inclui a API |
| `requisicao_invalida` | 403 | caminho ou método que esta API não publica. Outros 4xx da borda (corpo grande demais, `Content-Type` não suportado) usam o mesmo código, com o status original |
| `token_expirado` | 401 | passaram as 24h; refaça o preparar |
| `token_de_outro_cliente` / `token_de_outro_ambiente` | 403 | token de outra empresa, ou de outro ambiente |
| `json_invalido` / `token_invalido` / `requisicao_invalida` | 400 | corpo ou token malformado |
| `lote_vazio` / `lote_grande` | 422 | zero notas, ou mais de 10 |
| `notas_invalidas` | 422 | alguma nota tem pendência; veja `notas[].pendencias` |
| `validacao_indisponivel` / `erro_interno` | 500 | falha nossa; nada foi selado nem cobrado |
`requisicao_invalida` aparece duas vezes na tabela, com status diferentes: 403
quando a borda recusa o caminho ou o método, 400 quando o corpo chega
malformado. Ramifique pelo par `codigo` + status HTTP, nunca só pelo código.
Em `notas[].pendencias[].codigo` no 422 do preparar: `nota_invalida`,
`referencia_ausente`, `referencia_invalida`, `certificado_ausente`,
`dps_ausente`, `email_invalido`, `email_anexos_invalido`,
`anexo_indisponivel`, `email_do_tomador_ausente`,
`campo_invalido` (traz `campo`), `dados_invalidos`,
`certificado_invalido`, `certificado_sem_documento`,
`certificado_diverge_prestador`, `montagem_falhou`, `fora_do_schema`.
Em `notas[].erro.codigo` no 207 do emitir: `assinatura_ausente`,
`assinatura_invalida`, `ambiente_inconsistente`, `montagem_falhou` e os
códigos de recusa da SEFAZ, que trazem o bloco `sefaz`. Estes últimos nomeiam
a causa quando ela é conhecida, e caem em `rejeitada_pela_sefaz` quando não é.
Recusa por defeito de montagem ou de transmissão nossa sai como
`erro_no_envio` com `responsabilidade: "notaguard"` e nunca é cobrada.
| `codigo` | Dispara quando |
| --- | --- |
| `duplicidade_de_dps` | já existe NFS-e com esta `serie` e este `nDPS` para este prestador |
| `municipio_nao_aderente` | o município do prestador não emite pelo sistema nacional |
| `prestador_nome_indevido` | `tpEmit` é `1` e o payload mandou `prestador.xNome` ou `prestador.end` |
| `endereco_tomador_obrigatorio` | o imposto é devido no domicílio do tomador e o endereço dele não veio |
| `nbs_obrigatorio_com_ibscbs` | a nota declara o grupo IBS/CBS sem o código NBS do serviço |
| `simples_exige_total_tributos` | optante do Simples usou `indTotTrib` em vez de informar o total |
| `aliquota_simples_indevida` | `pTotTribSN` informada por quem não é optante do Simples |
| `aliquota_issqn_indevida` | `pAliq` na combinação descrita em "Simples Nacional" |
| `assinatura_recusada` | a SEFAZ não aceitou a assinatura da nota |
## Cancelamento
Cancelar uma NFS-e emitida por esta API segue o mesmo desenho de duas fases da
emissão, num par de rotas próprio:
1. `POST /v1/nfse/cancelamento/preparar` (grátis): você manda a
`chave_acesso`, o motivo (`cMotivo` `1` erro na emissão, `2` serviço
não prestado, `9` outros) e a descrição (`xMotivo`, 15 a 255 caracteres).
O servidor monta o pedido de evento e devolve o `assinar`.
2. `assinarCancelamentos` da `@notaguard/assinador-nfse` (v1.1.0) assina
localmente. A lib só assina se o autor do evento e o prestador embutido na
chave forem o titular do certificado.
3. `POST /v1/nfse/cancelamento/registrar`: o servidor confere a assinatura e
registra o evento na SEFAZ. `registrado` traz o `protocolo` e o
`evento_xml`.
O prazo para cancelar é **regra de cada município** (`prazo_cancelamento_expirado`,
E0822, quando vence). Cancelar de novo a mesma nota devolve o desfecho guardado,
sem nova transmissão. Um token de emissão não abre as rotas de cancelamento nem
vice-versa (`token_de_outra_operacao`).
## Substituição
Substituir **não é rota**: é emitir uma nota nova com o grupo `subst`
(`chSubstda`, `cMotivo`, `xMotivo`) apontando para a chave da antiga. Quando
a SEFAZ autoriza a substituta, ela mesma registra o cancelamento por substituição
(e105102) na nota antiga; não há evento a enviar. A substituta é cobrada como
emissão. A chave em `chSubstda` tem que ser de nota do próprio prestador
(conferida no `preparar`) e existir na SEFAZ (`substituida_invalida`, E0042,
quando não existe).
## O que esta versão não faz
Dito na cara, para você não procurar: não há geração de DANFSe em PDF (o
`preparar` recusa `anexos: ["pdf"]` com `anexo_indisponivel`, em vez de
mandar e-mail sem o arquivo pedido) nem consulta de consumo pela API: o consumo é
lido no painel.
## Limites de uso
O limite é por **segundo**, aplicado por chave na borda:
| Rota | Ambiente de teste | Produção |
| --- | --- | --- |
| `POST /v1/nfse/emitir` | 1/s (rajada 2) | 1/s (rajada 2) |
| `POST /v1/nfse/preparar` | 2/s (rajada 4) | 5/s (rajada 10) |
| `POST /v1/nfse/cancelamento/registrar` | 1/s (rajada 2) | 1/s (rajada 2) |
| `POST /v1/nfse/cancelamento/preparar` | 2/s (rajada 4) | 2/s (rajada 4) |
| `GET /v1/nfse/por-referencia/{ref}` | 5/s (rajada 10) | 5/s (rajada 10) |
| `GET /v1/me` | 1/s (rajada 2) | 2/s (rajada 4) |
| Teto por chave | 1/s (rajada 2) | 2/s (rajada 4) |
Os limites são **por chave**, não por empresa: duas chaves da mesma empresa têm
cada uma o seu ritmo e a sua cota mensal.
| Resposta | Código | Header | O que fazer |
| --- | --- | --- | --- |
| **429** | `limite_de_requisicoes_excedido` | `Retry-After: 30` | reduzir o ritmo e repetir |
| **429** | `cota_mensal_esgotada` | nenhum | repetir não resolve; fale com o suporte |
| **503** | `servico_indisponivel` | `Retry-After: 5` | indisponibilidade momentânea nossa; repetir resolve |
O **503** também cobre a queda do serviço que autoriza a chave: a chave não é o
problema, e conferi-la não muda nada.
## Tempo de resposta
O `emitir` fala com a SEFAZ, então é lento por natureza: a borda corta em **29
segundos** e o servidor devolve antes disso, marcando como `nao_enviada` o que
não coube no orçamento.
O timeout de leitura do seu cliente HTTP precisa ficar **acima de 29 segundos**
(35 serve). Menos que isso aborta a conexão enquanto a nota está sendo
transmitida, que é o estado indeterminado da seção anterior.
## Municípios fora do Emissor Nacional
Nem todo município aderiu ao Emissor Nacional. Prestador em município fora dele
recebe `E0039` e não há contorno pela API. A situação de cada município está em
[notaguard.com.br/guias/municipios-habilitados-nfse-nacional](https://notaguard.com.br/guias/municipios-habilitados-nfse-nacional).