# TexFiscal: emissão de NFS-e por API > Emissão, consulta e cancelamento de Nota Fiscal de Serviço eletrônica pelo seu > sistema. A API fala o contrato REST v2 de NFS-e que a maioria dos ERPs já > integra: mesmos caminhos, mesmos campos, mesmos literais de status. Quem já > emite por API troca a URL base e o token, sem mexer no código. Ambientes: produção em https://nfse.seusaude.com, homologação em https://homologacao-nfse.seusaude.com. Cada ambiente tem o seu token; o token de um apresentado no outro recebe 401. Autenticação e HTTP Basic com o token no usuário e senha VAZIA. ## As três regras que evitam quase todo erro de integração 1. A `ref` (na query do POST) e a chave de idempotencia. Repetir o POST com a mesma `ref` não emite de novo, devolve a nota existente. Derive a `ref` da sua transação, nunca de relogio nem de aleatorio. 2. Nota em `processando_autorizacao` NUNCA se reenvia. Só a consulta resolve. Reenviar duplica documento fiscal. 3. `alíquota` e PERCENTUAL (2.00 = dois por cento) e `discriminacao` tem MÍNIMO de 10 caracteres. Os dois são causa comum de recusa da prefeitura, e recusa da prefeitura consome numeracao de RPS. ## Reforma Tributária: IBS e CBS O objeto `serviço.ibs_cbs` e OPCIONAL até 31/12/2026 e passa a ser EXIGIDO em 01/01/2027 para empresas do Simples Nacional (01/10/2026 fora do Simples). Enquanto for opcional, omitir e válido. E TUDO OU NADA: `cst` (3 dígitos), `classificacao_tributaria` (6) e `indicador_operacao` (6) vao juntos, ou nenhum vai. Meio grupo e recusado pelo schema do provedor com o RPS já consumido, então a recusa acontece aqui antes de reservar número. Os três podem ficar no cadastro do emissor; o payload sempre vence o cadastro. Quais códigos usar e enquadramento tributario: e conversa com o contador, não há default e não escolhemos por ninguém. ## Contrato - [OpenAPI 3.1](https://nfse.seusaude.com/openapi.json): definicao completa de rotas, campos e erros. - [Guia de integração](https://nfse.seusaude.com/llms-full.txt): este arquivo com o guia inteiro embutido. - [Municípios atendidos](https://nfse.seusaude.com/v2/municipios): cobertura verificada, com a data de cada verificação. - [Saúde](https://nfse.seusaude.com/health): sem token. ## Rotas - `POST /v2/nfse?ref=`: emite. Repetir com a MESMA ref não reemite: devolve a nota que já existe (e assim que o retry após timeout fica seguro). A exceção e ref cuja nota foi CANCELADA: ela responde 409 `ref_encerrada`, porque a nota cancelada continua existindo como documento fiscal. Para substituir uma nota cancelada, use uma ref NOVA, por exemplo `PED-4471-R2`. A ref DISTINGUE MAIUSCULA de minuscula: `ped-1` e `PED-1` sao duas notas, e dois documentos fiscais para a mesma venda. Derive sempre da mesma forma. - `GET /v2/nfse/`: consulta (pergunta a prefeitura quando está em processamento). - `DELETE /v2/nfse/`: cancela. Corpo: `justificativa` (texto, mínimo 15 caracteres) e `motivo` (opcional, 1, 2 ou 3, default 1). Os dois NÃO sao a mesma coisa e confundi-los declara errado ao município: a `justificativa` e registro NOSSO e nunca chega a prefeitura (o padrão ABRASF não tem campo livre para ela); o `motivo` E a declaração ao fisco, e vale 1 erro na emissão, 2 serviço não prestado, 3 erro de assinatura. O PRAZO para cancelar e definido por cada PREFEITURA, não por nós, e não há nada aqui que o conheca: passado o prazo, a resposta e 422 `cancelamento_recusado` vinda do município. Não planeje um botao de cancelar que valha para sempre. - `GET /v2/nfse//pdf`: DANFSe. Exige token, não e link público. - `GET /v2/nfse//xml`: XML autorizado. - `GET /v2/uso?mes=AAAA-MM`: uso do mês, com o campo `cobravel` calculado pelo MESMO criterio do fechamento que gera a fatura: `numero IS NOT NULL AND numero <> ''` (só em produção; em homologação `cobravel` e sempre 0). Repare que **nota cancelada continua contando**: o cancelamento troca o status e não devolve o número da prefeitura. Nota recusada não conta, porque nunca recebeu número. - `GET /v2/municipios`: cobertura verificada. ## Literais de status - `autorizado`: A prefeitura autorizou. Há número, código de verificação, XML e DANFSe. - `processando_autorizacao`: Enviada, sem veredito ainda. Consulte ou espere o webhook. NUNCA reenvie: reenvio duplica documento fiscal. - `erro_autorizacao`: A prefeitura recusou. Corrija e reenvie com a MESMA ref. - `cancelado`: Cancelada com sucesso. - `processando_cancelamento`: Pedido de cancelamento sem veredito. Repita o DELETE ou consulte. ## Códigos de erro Trate pelo `código`, nunca pelo texto da `mensagem`. - `nao_autorizado` (HTTP 401): Token ausente, inválido, ou de um ambiente diferente do endereço chamado. - `emissor_bloqueado` (HTTP 403): O contrato do emissor está suspenso, encerrado ou ainda não foi ligado. Emitir e cancelar ficam barrados; CONSULTAR continua liberado, inclusive o XML e o PDF das notas já emitidas. - `ref_invalida` (HTTP 422): A ref não passou na regra: até 64 caracteres entre letras, números, ponto, hífen e sublinhado, sem começar com ponto e sem sufixo de arquivo reservado. - `json_invalido` (HTTP 422): O corpo não e um objeto JSON válido. - `requisicao_invalida` (HTTP 422): O payload foi recusado na validação. O array erros[] traz campo, mensagem e correção. Nada foi transmitido a prefeitura e nenhum RPS foi consumido. - `emissor_nao_configurado` (HTTP 422): O emissor do token está sem certificado, sem inscrição municipal, com o certificado vencido ou com o cadastro incompleto. Emissor SUSPENSO não cai aqui: cai em emissor_bloqueado (403). - `estado_invalido` (HTTP 422): A operação não cabe no estado atual da nota (cancelar nota que não está autorizada, por exemplo). - `justificativa_invalida` (HTTP 422): A justificativa de cancelamento tem menos de 15 caracteres. - `motivo_invalido` (HTTP 422): O campo motivo do cancelamento aceita 1 (erro na emissão), 2 (serviço não prestado) ou 3 (erro de assinatura). - `cancelamento_recusado` (HTTP 422): A prefeitura recusou o cancelamento. - `mes_invalido` (HTTP 422): O parâmetro mês não está em AAAA-MM. - `ref_encerrada` (HTTP 409): Esta referência já tem uma nota CANCELADA e não pode ser reutilizada. A nota cancelada continua existindo como documento fiscal: emita a substituta com uma referência NOVA (por exemplo, sufixo -R2). - `nao_encontrado` (HTTP 404): Não existe nota com essa ref neste emissor e ambiente. - `xml_indisponivel` (HTTP 404): Não há XML autorizado guardado para essa ref. - `danfse_indisponivel` (HTTP 404): Não há nota autorizada para essa ref. Situação permanente enquanto a nota não autorizar. - `danfse_falhou` (HTTP 422): A nota está autorizada mas o DANFSe não pode ser gerado agora. O XML autorizado continua em /xml. Tente o PDF de novo em instantes. - `cobertura_indisponivel` (HTTP 422): A lista de cidades atendidas não pode ser lida agora. - `contrato_indisponivel` (HTTP 404): O arquivo de contrato pedido (OpenAPI, Postman, llms.txt) não está disponível. - `servico_indisponivel` (HTTP 422): O serviço não respondeu a tempo ou está fora do ar. O DESFECHO DESTA CHAMADA E DESCONHECIDO: consulte GET /v2/nfse/{a mesma ref} antes de qualquer reenvio, e nunca reenvie com ref nova. - `rota_inexistente` (HTTP 404): Caminho não atendido por este serviço. - `metodo_nao_permitido` (HTTP 405): Método errado na rota. O cabecalho Allow diz quais valem. - `corpo_excede_limite` (HTTP 413): Corpo acima de 512 KB. - `limite_de_requisicoes` (HTTP 429): Acima de 10 requisicoes por segundo por IP. - `falha_interna` (HTTP 500): Falha não prevista. O incidente foi registrado. ## Compatibilidade: o que pode mudar sem aviso e o que não muda Esta seção e um compromisso, não uma observacao. Escreva seu cliente contando com ela e ele não quebra quando o serviço evoluir. O que PODE aparecer a qualquer momento, e por isso não quebra nada: - códigos de erro novos. Trate código desconhecido pelo STATUS HTTP: 4xx e problema no pedido ou no cadastro e não adianta repetir igual; 5xx e nosso. - campos novos na resposta. IGNORE campo que você não conhece, nunca recuse a resposta inteira por causa dele. - rotas novas e parâmetros de consulta OPCIONAIS novos. O que NÃO muda em `/v2`, e só mudaria com aviso de 90 dias e os cabecalhos `Deprecation` e `Sunset` antes: - caminho e método das rotas que já existem; - nome e significado dos campos que já existem; - os literais de `status` (`autorizado`, `processando_autorizacao`, `erro_autorizacao`, `cancelado`, `processando_cancelamento`); - o status HTTP que um código de erro já existente devolve. Duas coisas que valem repetir porque sao onde mais se erra: 202 NÃO e falha (a nota está em processamento e reenviar duplica documento fiscal), e um `código` que você não reconhece nunca deve virar retry automático. ## Webhook: o aviso que o serviço manda para você Você pode cadastrar UMA url por ambiente. Assim que a nota muda de desfecho, o serviço faz um POST nela com `Content-Type: application/json`. Eventos: `nfse_autorizada`, `nfse_erro`, `nfse_cancelada` e `nfse_cancelamento_nao_efetivado`. O último e o mais importante: ele significa que o cancelamento que você pediu NÃO valeu na prefeitura e a nota voltou a estar autorizada. Ele existe para pedir que alguém aja. Corpo: { "evento": "nfse_autorizada", "ref": "venda-000123", "evento_id": 4471, "ocorrido_em": "2026-09-03T01:20:00-03:00", ... os mesmos campos de GET /v2/nfse/ } Cabecalhos: `X-TexFiscal-Evento`, `X-TexFiscal-Tentativa` e `X-TexFiscal-Assinatura`, que e o HMAC-SHA256 do CORPO CRU com o segredo do seu webhook. Confira a assinatura antes de confiar no conteúdo, e compare com `hash_equals` ou equivalente, nunca com `==`. Três regras do receptor, e a segunda já causou prejuizo em outros integradores: 1. Responda 2xx rápido. Timeout nosso: 5 s na tentativa imediata. Faca o trabalho pesado depois de responder. 2. DESCARTE evento fora de ordem. Uma entrega que falhou e reagendada pode chegar DEPOIS de um evento mais novo da mesma ref: por exemplo, uma `nfse_autorizada` reagendada chegando depois da `nfse_cancelada`. Guarde o `ocorrido_em` do último evento aplicado por ref e ignore o que for mais antigo. Sem isso você marca como autorizada uma nota cancelada. 3. Trate entrega REPETIDA. Reenviamos até 16 vezes com espera crescente enquanto você não responder 2xx, e uma entrega pode chegar duas vezes se a sua resposta se perder. Use o `evento_id`, que e único e estável, como chave de deduplicação. Não ter webhook cadastrado e uma escolha valida: nesse caso consulte `GET /v2/nfse/`. Mas não fique consultando em laco apertado, porque cada consulta de nota pendente pergunta a prefeitura. Espere 2 s, depois 4, 8, 16, até 60 s entre consultas, e desista de consultar depois de 5 minutos: o serviço resolve sozinho e o próximo GET traz o desfecho. ## O que este serviço nunca faz - Responder 502, 503 ou 504 em rota de API. Falha de dependencia sai como 422 com corpo próprio, porque a borda da Cloudflare troca o corpo de 5xx pela página dela e o `fetch` do cliente cairia no catch sem a mensagem. - Reenviar sozinho uma nota sem resposta conclusiva. - Truncar ou "arrumar" dado fiscal em silencio: o que passa do limite e recusado com mensagem e correção, porque cortar um nome ou arredondar um valor produz uma nota diferente da que o cliente pediu e ele só descobre na apuracao. - Guardar o seu token em claro, ou o seu certificado fora do cofre. - Entregar PDF ou XML por link público. # Guia de integração completo O texto abaixo e o MESMO guia que uma pessoa le em docs/integracao-cliente.md. Ele fica aqui embutido de proposito: guia para maquina e guia para gente que divergem produzem integração que passa no teste e falha na prefeitura. --- # TexFiscal: guia de integração para o cliente Serviço de emissão de NFS-e da SeuSaúde. A API fala o contrato REST v2 de NFS-e que a maioria dos ERPs já integra (mesmos caminhos, campos e literais de `status`): quem já emite por API troca a URL base e o token. ## Endereços | Ambiente | URL base | Para que serve | |---|---|---| | Homologação | `https://homologacao-nfse.seusaude.com` | Testes. As notas vão ao ambiente de teste da prefeitura e não têm valor fiscal. | | Produção | `https://nfse.seusaude.com` | Notas reais. | Cada ambiente tem o próprio token. Um token de homologação apresentado em produção (ou vice-versa) recebe 401. ## Autenticação HTTP Basic com o token como usuário e senha vazia, no mesmo formato do contrato v2: ``` Authorization: Basic base64("SEU_TOKEN:") ``` Exemplo com curl: `curl -u "SEU_TOKEN:" https://homologacao-nfse.seusaude.com/v2/nfse/venda-123` O token é entregue uma vez e não pode ser recuperado (guardamos só o hash). Peça outro se perder. ## Emitir `POST /v2/nfse?ref=` com o JSON abaixo. A `ref` é a chave de idempotência: repetir o POST com a mesma `ref` **não** emite de novo, devolve a nota que já existe. Até 64 caracteres entre letras, números, ponto, hífen e sublinhado; maiúsculas e minúsculas são diferentes. Não pode começar com ponto nem terminar com sufixo de arquivo reservado (`.env`, `.ini`, `.log`, `.sql`, `.md`, `.bak`, `.old`, `.key`, `.pem`, `.p12`, `.pfx` e afins): essas refs são recusadas com 422 na emissão. ```json { "data_emissao": "2026-08-27T10:00:00", "optante_simples_nacional": true, "regime_especial_tributacao": 6, "prestador": { "cnpj": "11521336000116" }, "tomador": { "cpf": "52998224725", "razao_social": "Maria de Assunção Silva", "email": "maria@exemplo.com", "endereco": { "logradouro": "Rua das Flores", "numero": "100", "bairro": "Centro", "codigo_municipio": "2910800", "uf": "BA", "cep": "44001000" } }, "servico": { "valor_servicos": 150.00, "aliquota": 2.00, "iss_retido": false, "item_lista_servico": "4.10", "codigo_tributario_municipio": "0410", "discriminacao": "Consulta nutricional." } } ``` Regras que valem a pena saber antes: - Valores com ponto decimal e no máximo duas casas (`150.00`, nunca `"1.500,00"`). - `aliquota` em percentual (`2.00` = dois por cento). Abaixo de 1 é recusada como fração. - `data_emissao` em ISO 8601; em formato brasileiro é recusada. Omita para usar agora. - `prestador.cnpj`, se enviado, precisa ser o do emissor do token. - Limites do padrão ABRASF: nome do tomador 150, logradouro 125, bairro e complemento 60, número 10, e-mail 80, discriminação 2000. O que passa é recusado com mensagem, nunca cortado. - `optante_simples_nacional` e `regime_especial_tributacao` podem ser omitidos: valem os do cadastro do emissor. Respostas: | HTTP | Significado | |---|---| | 200 | Nota autorizada na hora (`status: autorizado`). | | 202 | Em processamento (`status: processando_autorizacao`). Consulte depois ou espere o webhook. | | 422 | Recusada: `codigo`, `mensagem` e `erros[]` com `codigo`, `mensagem` e `correcao` por campo. Nada foi transmitido; corrija e reenvie com a mesma `ref`. | | 401 | Token ausente, inválido ou do outro ambiente. | | 405 | Método não aceito na rota; o cabeçalho `Allow` diz quais valem. | | 429 | Muitas requisições (10 por segundo por IP), com corpo JSON (`codigo: limite_de_requisicoes`). | | 413 | Corpo acima de 512 KB, com corpo JSON (`codigo: corpo_excede_limite`). | A resposta da nota tem os campos: `ref`, `status`, `numero`, `numero_rps`, `serie_rps`, `codigo_verificacao`, `data_emissao`, `url` (PDF), `caminho_danfse`, `caminho_xml_nota_fiscal`, `mensagem`, `erros`. O campo `status_texfiscal` traz o estado interno, só para diagnóstico. Literais de `status`: `autorizado`, `erro_autorizacao`, `processando_autorizacao`, `cancelado`, `processando_cancelamento`. ## Consultar `GET /v2/nfse/`. Se a nota estiver em processamento, a consulta pergunta à prefeitura antes de responder. 404 quando a `ref` não existe. ## PDF e XML - `GET /v2/nfse//pdf`: DANFSe em PDF (gerado do XML autorizado). Exige o token, como tudo aqui: não é link público. - `GET /v2/nfse//xml`: XML da NFS-e autorizada, como a prefeitura devolveu. ## Cancelar `DELETE /v2/nfse/` com `{"justificativa": "texto com ao menos 15 caracteres", "motivo": 1}`. Só nota autorizada cancela. 200 com `status: cancelado`; 202 com `processando_cancelamento` quando a prefeitura não respondeu de forma conclusiva (repita o DELETE ou consulte: o serviço confere e resolve); 422 `cancelamento_recusado` quando a prefeitura recusou. Duas coisas para saber antes de desenhar o seu botão de cancelar: - **Prazo: existe, é de cada PREFEITURA e nós não o conhecemos.** O município define até quando uma NFS-e pode ser cancelada, e esse prazo não está em lugar nenhum desta API: nós transmitimos o pedido e devolvemos a resposta que vier. Quem integra supondo um cancelamento que vale para sempre descobre o prazo levando 422 `cancelamento_recusado` no dia em que já não dá para consertar. Confirme o prazo do seu município, trate a nota como definitiva depois dele e resolva o que passou do prazo pela via administrativa da prefeitura, não pela API. - **São dois campos, e confundi-los declara errado ao município.** O padrão ABRASF não tem campo livre para justificativa: o que vai no XML é um código numérico. Por isso o corpo tem os dois: - `motivo` (opcional, `1`, `2` ou `3`, padrão `1`) **é a declaração ao fisco.** `1` erro na emissão, `2` serviço não prestado, `3` erro de assinatura. Informe o valor correto: `1` e `2` dizem coisas diferentes ao município, e cancelar um serviço que de fato não foi prestado declarando `1` é declaração fiscal errada. Valor fora de `1..3` é recusado com 422 `motivo_invalido`, em vez de virar `1` em silêncio. - `justificativa` (obrigatória, mínimo 15 caracteres) **fica conosco e nunca chega à prefeitura.** Ela é gravada na trilha de eventos da nota no momento do PEDIDO, junto com o motivo, e vale também quando o cancelamento fica em 202 sem desfecho, que é justamente o caso que alguém vai querer auditar depois. Escreva pensando em quem for ler a trilha daqui a dois anos; o que precisar ser dito à prefeitura vai pelo canal dela. ## Webhook (opcional) Cadastramos uma URL sua (https) por ambiente. A cada transição da nota que importa para você (`nfse_autorizada`, `nfse_erro`, `nfse_cancelada` e `nfse_cancelamento_nao_efetivado`, este último quando um pedido de cancelamento em processamento não constou na prefeitura e a nota voltou a autorizada: repita o DELETE) fazemos `POST` nela com o mesmo JSON do GET, mais `evento` e `ref` no topo. Cabeçalhos: - `X-Webhook-Token`: o segredo combinado (o mesmo cabeçalho do contrato v2; se o seu receptor já valida esse cabeçalho, nada muda). - `X-TexFiscal-Assinatura`: HMAC-SHA256 do corpo com o segredo, em hexadecimal, para quem quiser conferir a integridade. - `X-TexFiscal-Evento` e `X-TexFiscal-Tentativa`. Responda 2xx rápido: a primeira tentativa, feita na hora da transição, espera **5 segundos**; as reentregas agendadas esperam 20. Receptor que demora mais que isso na primeira tentativa recebe o aviso pela reentrega, cerca de 1 minuto depois. Sem 2xx, reentregamos com recuo (1, 5, 15, 60 minutos, depois a cada 6 horas) por até 3 dias. Um aviso pode chegar mais de uma vez em situação de falha: trate por `ref` e `status`, de forma idempotente. ## Uso `GET /v2/uso?mes=AAAA-MM`: quantidade e valor por `status` no mês, no ambiente do token. É a mesma base da cobrança. ## Erros que a prefeitura devolve Chegam em `erros[]` com o código da prefeitura (ex.: `E160` XML fora do schema, `E10` RPS já informado, `E221` alíquota informada indevidamente), a mensagem e a correção sugerida. Nota em `erro_autorizacao` pode ser reenviada com a mesma `ref` depois de corrigida. ## O que nunca fazemos - Reenviar uma nota sem resposta conclusiva. Só consulta resolve; reenvio duplica documento fiscal. - Responder 502, 503 ou 504 em rota de API. - Guardar o seu token em claro, ou o seu certificado fora do cofre.