Integre a emissão de NFS-e em uma tarde.
Uma API REST em JSON. Você envia a venda, o Tulha emite a NFS-e no padrão nacional (ou no sistema da prefeitura), guarda o XML e o PDF e avisa seu sistema por webhook.
- Assíncrona: o
POSTresponde202na hora; a autorização chega por webhook (ou por consulta). - Sem duplicidade: toda emissão leva uma
Idempotency-Key; repetir a chamada devolve a mesma nota. - Códigos fiscais no cadastro: código de tributação, NBS, alíquota e regime ficam configurados no seu emitente. Na chamada vão só valor, serviço e cliente.
- Valores em reais com até 2 casas (
150.00); datas em ISO 8601.
Autenticação e ambientes
Todas as chamadas vão para https://api.tulha.dev/v1 com o token da sua conta no cabeçalho:
Authorization: Bearer SEU_TOKEN
Accept: application/json
Content-Type: application/json
Gerar o token
- Entre no portal do cliente em https://tulha.dev/painel com o acesso que a gente cria para a sua conta.
- Abra Tokens da API e clique em Gerar token.
- Escolha o ambiente (
sandboxouproduction) e dê um nome que identifique a integração (ex.:erp-producao). - Copie o token na hora: ele aparece uma única vez. Se perder, gere outro e revogue o antigo.
Um token por sistema facilita a troca: revogar um token em Tokens da API faz as chamadas com ele receberem 401 na hora, sem afetar os outros. A coluna Último uso mostra se ele ainda está em uso.
Ambientes
O token decide o ambiente; não há parâmetro nem URL diferente:
| Token | O que acontece | Limite |
|---|---|---|
sandbox | Emite no ambiente de testes do emissor. Não tem valor fiscal e não debita saldo. | 60 req/min |
production | Nota real. Debita R$ 0,49 do saldo quando é autorizada. | 600 req/min |
Notas de um ambiente não aparecem no outro, e a numeração é separada. Passou do limite, a resposta é 429.
Sua primeira nota
Com o token de sandbox, emita uma nota de teste:
curl -X POST https://api.tulha.dev/v1/nfse \ -H "Authorization: Bearer $TULHA_TOKEN" \ -H "Idempotency-Key: pedido-1001" \ -H "Content-Type: application/json" \ -d '{ "issuer_cnpj": "12345678000195", "external_ref": "pedido-1001", "service": { "amount": 150.00, "description": "Consultoria - outubro/2026" }, "customer": { "document": "12345678909", "name": "Maria da Silva", "email": "maria@exemplo.com" }, "send_email": true }'
Resposta imediata:
HTTP/1.1 202 Accepted { "id": "9d3f6a2e-0b7c-4a51-9e7a-2f4c1d8b6e10", "status": "queued" }
Em seguida chega o webhook nfse.authorized com a chave de acesso, o número e os links do XML e do PDF. Pronto: é o mesmo fluxo em produção, trocando o token.
POST /nfse — Emitir
Valida tudo de forma síncrona (dados, emitente, certificado, saldo) e só então enfileira. O que entra na fila tem chance real de ser autorizado.
Cabeçalhos
Idempotency-Keyobrigatório, até 191 caracteres. Use o ID do pedido ou da cobrança no seu sistema.
Corpo
| Campo | Tipo | Descrição |
|---|---|---|
issuer_cnpj | string | Obrigatório. CNPJ do emitente (prestador), 14 dígitos sem máscara. |
external_ref | string | Sua referência (pedido, cobrança). Volta nos webhooks e serve de filtro na listagem. Até 191. |
service.amount | número | Obrigatório. Valor do serviço em reais, maior que zero, até 2 casas. |
service.description | string | Descrição na nota. Obrigatória se o serviço cadastrado não tiver descrição padrão. Até 2.000. |
service.code | string | Código de um serviço cadastrado no seu emitente. Sem ele, vale o serviço padrão. |
service.competence | string | AAAA-MM ou AAAA-MM-DD. Padrão: data da emissão. Nunca no futuro. |
service.national_tax_code | string | Código de tributação nacional (6 dígitos), se quiser sobrescrever o do cadastro. |
service.nbs_code | string | Código NBS, se quiser sobrescrever o do cadastro. |
customer.document | string | Obrigatório. CPF ou CNPJ do tomador (com ou sem máscara, validado). |
customer.name | string | Obrigatório. Nome ou razão social. Até 300. |
customer.email | string | E-mail do tomador. Necessário para send_email. |
customer.address | objeto | Opcional. Com ele: zip, street, number e city_ibge (7 dígitos) obrigatórios; district, complement e state opcionais. Sem district, o endereço não vai para a nota. |
send_email | boolean | Envia o PDF e o XML ao tomador quando a nota é autorizada. Padrão false. |
Respostas
202{"id", "status": "queued"}: aceita, saldo reservado.400dados inválidos ·402saldo insuficiente ·409idempotência em conflito ·422emitente ou certificado sem condição de emitir. Detalhes em Erros.
Idempotência
A rede falha. Por isso a mesma chamada pode ser repetida sem medo:
- Mesma chave, mesmo corpo: devolve a nota já criada, com o mesmo
id. Nada é emitido de novo. - Mesma chave, corpo diferente:
409 idempotency_conflict, com odocument_idda nota original.
Do nosso lado, o número da DPS é reservado antes de falar com a prefeitura, e toda nova tentativa começa consultando se a nota já existe. Uma venda, uma nota.
Estados da nota
| Status | Significado | Final? |
|---|---|---|
queued | Aceita, esperando o worker. | Não |
sending | Sendo enviada ao emissor. | Não |
retrying | Emissor fora do ar ou resposta incerta. Vamos conferir e tentar de novo, com o mesmo número. | Não |
authorized | Autorizada. Tem chave de acesso, número, XML e PDF. Debita o saldo. | Sim* |
rejected | Recusada pelo emissor; o motivo vem em errors. A reserva volta para o saldo. | Sim |
failed | Não pôde ser enviada (ex.: certificado vencido). A reserva volta para o saldo. | Sim |
cancelled | Cancelada depois de autorizada. | Sim |
replaced | Substituída por outra nota. | Sim |
* Uma nota authorized ainda pode virar cancelled ou replaced.
GET /nfse/{id} — Consultar
{
"id": "9d3f6a2e-0b7c-4a51-9e7a-2f4c1d8b6e10",
"status": "authorized",
"environment": "production",
"external_ref": "pedido-1001",
"issuer_cnpj": "12345678000195",
"series": "2",
"dps_number": 4,
"access_key": "3168002212345678000195260000000000123426100000000017",
"number": "1234",
"amount": 150.0,
"errors": [],
"attempts": 1,
"replaces": null,
"events": [],
"links": { "self": "…/v1/nfse/9d3f…", "xml": "…/xml", "pdf": "…/pdf" },
"authorized_at": "2026-10-15T14:32:17-03:00",
"cancelled_at": null,
"created_at": "2026-10-15T14:32:14-03:00"
}
GET /nfse — Listar
Filtros opcionais: issuer_cnpj, status, external_ref, from, to (datas) e limit (1 a 100, padrão 50). Mais recentes primeiro, com paginação por cursor:
GET /v1/nfse?status=authorized&limit=50
{ "data": [ … ], "next_cursor": "eyJjcmVhdGVkX2F0Ijo…", "has_more": true }
GET /v1/nfse?status=authorized&cursor=eyJjcmVhdGVkX2F0Ijo…
GET /nfse/{id}/xml · /pdf
Responde 302 para um link temporário (15 minutos) do arquivo. Siga o redirecionamento ou guarde o arquivo do seu lado. Antes de a nota sair, a resposta é 404 xml_not_ready / pdf_not_ready.
POST /nfse/{id}/cancel — Cancelar
Só para notas authorized. Assíncrono: responde 202 e o resultado chega pelo webhook nfse.cancelled.
{ "reason_code": "1", "reason": "Nota emitida com valor incorreto" }
HTTP/1.1 202 Accepted
{ "id": "9d3f…", "event": { "type": "cancelamento", "status": "queued" } }
reason_code:1erro na emissão,2serviço não prestado,9outros.reason: de 15 a 255 caracteres.- Repetir enquanto o cancelamento está em andamento devolve o mesmo evento. Cancelar não gera cobrança nem estorno.
POST /nfse/{id}/replace — Substituir
Emite uma nota nova que substitui a autorizada. O corpo é o mesmo do POST /nfse (com Idempotency-Key), mais o motivo. issuer_cnpj é opcional e, se vier, tem que ser o da nota original.
{
"reason_code": "01",
"service": { "amount": 140.00, "description": "Consultoria - outubro/2026" },
"customer": { "document": "12345678909", "name": "Maria da Silva" }
}
HTTP/1.1 202 Accepted
{ "id": "(nota nova)", "status": "queued", "replaces": "(nota original)" }
reason_code segue a tabela nacional (01 a 05, ou 99 com reason de 15 a 255 caracteres). Quando a nova é autorizada, a original vira replaced e chega o webhook nfse.replaced. A nota nova é cobrada como qualquer emissão.
Portal nacional e prefeituras
A NFS-e de padrão nacional nasce de uma DPS (Declaração de Prestação de Serviço): o Tulha monta a DPS com os dados da chamada e o cadastro do emitente, envia ao emissor e recebe de volta a NFS-e com a chave de acesso de 50 dígitos. Para a sua integração, o caminho é o mesmo em qualquer município: POST /nfse, webhook nfse.authorized. O que muda é para onde o Tulha manda a DPS, e isso é configuração do emitente, não da chamada.
| Onde o município emite | Como o Tulha emite | O que o emitente precisa |
|---|---|---|
| Emissor nacional (município conveniado ao padrão nacional) | Envia a DPS assinada à Sefin Nacional, com mTLS. A nota vai para o Ambiente de Dados Nacional e aparece no portal nacional (nfse.gov.br). | Certificado digital A1 do CNPJ (arquivo .pfx e senha), enviado no portal do Tulha. |
| Sistema próprio da prefeitura (município sem adesão ao emissor nacional) | Envia a mesma DPS do leiaute nacional pela API do sistema da prefeitura, que assina a nota com o certificado dela. A autorização costuma levar alguns segundos a mais: o Tulha consulta até sair. | O token de integração gerado no portal da prefeitura. Não precisa de A1. |
Prefeituras atendidas
- Emissor nacional: municípios conveniados ao padrão nacional que já liberamos. Se o seu ainda não estiver, a emissão responde
422 municipality_not_supported. - Sistemas próprios: E&L. Para usar, em Emitentes › Portal nacional › Onde a nota é emitida escolha Sistema da prefeitura — E&L, informe a URL da API da prefeitura e o token de integração (no portal da prefeitura, em Configurações › Integração). O token fica cifrado e não é exibido de novo.
Prefeituras com o sistema E&L
Em produção no Tulha: Taiobeiras-MG.
Usam o sistema da E&L (206 municípios, levantamento de outubro/2026). Se o seu está aqui, a emissão tende a funcionar só com o token da prefeitura, mas confirme com a gente antes: o município pode já ter aderido ao emissor nacional, e aí a nota sai pela Sefin com o certificado A1.
BA 41
Arataca · Baianópolis · Barra · Barra do Choça · Barreiras · Barro Preto · Buritirama · Caetanos · Caravelas · Castro Alves · Cordeiros · Érico Cardoso · Firmino Alves · Floresta Azul · Guaratinga · Ibicoara · Ibirapitanga · Iguaí · Ipiaú · Itaju do Colônia · Itamaraju · Itapé · Ituaçu · Jequié · Jitaúna · Jussari · Jussiape · Mascote · Medeiros Neto · Nova Canaã · Pau Brasil · Planalto · Poções · Prado · Presidente Jânio Quadros · São José da Vitória · Simões Filho · Ubaitaba · Una · Valença · Vitória da Conquista
CE 1
Eusébio
ES 68
Afonso Cláudio · Água Doce do Norte · Águia Branca · Alegre · Alfredo Chaves · Alto Rio Novo · Anchieta · Apiacá · Aracruz · Baixo Guandu · Barra de São Francisco · Boa Esperança · Bom Jesus do Norte · Castelo · Colatina · Conceição do Castelo · Divino de São Lourenço · Domingos Martins · Dores do Rio Preto · Ecoporanga · Fundão · Governador Lindenberg · Guaçuí · Ibatiba · Ibiraçu · Ibitirama · Iconha · Irupi · Itaguaçu · Itapemirim · Itarana · Iúna · Jaguaré · Jerônimo Monteiro · João Neiva · Laranja da Terra · Mantenópolis · Marataízes · Marechal Floriano · Marilândia · Mimoso do Sul · Montanha · Mucurici · Muniz Freire · Muqui · Nova Venécia · Pancas · Pedro Canário · Pinheiros · Piúma · Ponto Belo · Presidente Kennedy · Rio Bananal · Rio Novo do Sul · Santa Leopoldina · Santa Maria de Jetibá · Santa Teresa · São Domingos do Norte · São Gabriel da Palha · São José do Calçado · São Mateus · São Roque do Canaã · Sooretama · Vargem Alta · Venda Nova do Imigrante · Viana · Vila Pavão · Vila Valério
MA 1
Tuntum
MG 82
Abre Campo · Água Boa · Águas Formosas · Almenara · Araçuaí · Areado · Aricanduva · Barão do Monte Alto · Berizal · Brumadinho · Caiana · Caldas · Caparaó · Capitão Andrade · Carangola · Carbonita · Central de Minas · Confins · Congonhas do Norte · Cordisburgo · Divino das Laranjeiras · Durandé · Espera Feliz · Faria Lemos · Fortaleza de Minas · Frei Gaspar · Frei Lagonegro · Galiléia · Governador Valadares · Guanhães · Ibiraci · Itabirinha · Itambacuri · Itaobim · Jaboticatubas · Jampruca · João Monlevade · José Raydan · Ladainha · Lajinha · Manhuaçu · Martins Soares · Monte Santo de Minas · Morro do Pilar · Nacip Raydan · Nanuque · Ninheira · Nova Resende · Novo Oriente de Minas · Periquito · Ponto dos Volantes · Poté · Presidente Juscelino · Raul Soares · Rio Acima · Rio Piracicaba · Rio Vermelho · Santa Bárbara do Leste · Santa Maria de Itabira · Santa Rita de Minas · Santana do Manhuaçu · Santo Antônio do Jacinto · São Domingos do Prata · São Félix de Minas · São Francisco do Glória · São Geraldo da Piedade · São Gonçalo do Abaeté · São João do Manteninha · São João do Paraíso · São João Evangelista · São José do Divino · São Lourenço · São Pedro dos Ferros · São Sebastião do Maranhão · Senador Modestino Gonçalves · Senhora do Porto · Setubinha · Simonésia · Taiobeiras · Três Corações · Vargem Alegre · Vieiras
PE 1
Petrolina
PR 1
Tuneiras do Oeste
RJ 11
Aperibé · Armação dos Búzios · Cantagalo · Carmo · Cordeiro · Duas Barras · Itaocara · Nova Friburgo · Rio Claro · Sumidouro · Trajano de Moraes
Não sabe qual sistema o seu município usa? Abra o portal de notas da prefeitura: se o endereço termina em cloud.el.com.br (ex.: mg-taiobeiras-pm-nfs.cloud.el.com.br), é E&L. Algumas prefeituras usam domínio próprio na frente da E&L (ex.: nfse.domingosmartins.es.gov.br); na dúvida, mande o endereço para a gente.
Seu município usa outro sistema? Fale com a gente: novas prefeituras entram atrás da mesma API, sem mudar nada na sua integração.
sandbox emite na produção restrita da Sefin, sem valor fiscal. Nos sistemas próprios de prefeitura a disponibilidade de homologação depende do provedor: confirme com a gente antes de testar.Série e numeração
Toda DPS tem uma série (até 5 dígitos) e um número sequencial dentro dela. O par série + número identifica a DPS do seu CNPJ e não pode se repetir: o emissor recusa uma DPS com série e número já usados.
O Tulha reserva o número no momento em que aceita a nota (o 202), antes de falar com o emissor. Repetições e reenvios usam sempre o mesmo número, e a numeração nunca volta. Na resposta e no webhook:
| Campo | O que é |
|---|---|
series · dps_number | Série e número da DPS, definidos pelo Tulha ao aceitar a nota. |
number | Número da NFS-e, atribuído pelo emissor na autorização. |
access_key | Chave de acesso de 50 dígitos da NFS-e autorizada. |
Escolher a série
A série é por emitente: em Emitentes › Portal nacional › Numeração da DPS você define a série e o número da próxima DPS. Vazio, vale a série padrão do Tulha (1).
- Use uma série que nenhum outro sistema do mesmo CNPJ usa. Se o CNPJ também emite pelo emissor web do portal nacional, por outro ERP ou pelo gateway de pagamento, cada um precisa da sua série. O emissor web usa a
70000; para emissão por sistema com certificado, use séries de1a49999. - Migrando de outro emissor? Você pode continuar a série que já usava: informe como número da próxima DPS o último número emitido + 1. Ou comece uma série nova a partir do 1.
- O número da próxima DPS só avança. Voltar geraria DPS duplicada, e o cadastro recusa.
- Cada série tem a sua numeração, e o sandbox tem uma numeração separada da produção, começando em 1.
- Trocar a série vale para as notas aceitas depois da troca. Notas já numeradas seguem com a série e o número que receberam.
Hoje cada emitente usa uma série por vez. Precisa de séries diferentes no mesmo CNPJ (por loja ou por sistema, por exemplo)? Fale com a gente.
Reforma tributária (IBS/CBS)
A reforma do consumo (Emenda Constitucional 132/2023 e Lei Complementar 214/2025) cria a CBS (federal) e o IBS (estados e municípios), que vão substituir PIS, Cofins, ICMS e ISS ao longo da transição até 2033. 2026 é o ano de teste: as notas já podem destacar CBS e IBS com alíquotas simbólicas, e o ISS continua sendo apurado normalmente.
Na NFS-e, a informação vai no grupo IBSCBS da DPS (Nota Técnica 004 do padrão nacional), com campos como:
| Campo | O que é |
|---|---|
CST | Código de situação tributária do IBS/CBS. |
cClassTrib | Classificação tributária do serviço (tabela nacional). |
cIndOp | Indicador da operação, que define o local de incidência. |
finNFSe · indDest | Finalidade da nota e indicador do destinatário. |
Como o Tulha trata
- No cadastro, não na chamada. Como os demais códigos fiscais, o grupo IBS/CBS fica no emitente, em Emitentes › Tributação › Reforma tributária, no formato do grupo
IBSCBSda DPS. Sua integração não muda: nenhum campo novo noPOST /nfse. - Vazio, o grupo não vai na DPS. Enquanto o emissor nacional aceitar a DPS sem ele, deixar em branco é o caminho seguro: o Tulha não preenche código fiscal por conta própria.
- Fotografia na nota. A configuração vigente quando a nota é aceita fica gravada nela. Alterar o cadastro depois não muda nota já numerada.
// Exemplo de formato. Os códigos são ilustrativos: defina os seus com o contador. { "finNFSe": "0", "cIndOp": "000000", "indDest": "0", "valores": { "trib": { "gIBSCBS": { "CST": "000", "cClassTrib": "000001" } } } }
Saldo e emitentes
GET /account/balance
{
"balance": 120.54, "reserved": 0.98, "available": 119.56,
"price_per_document": 0.49, "status": "active",
"last_recharge": { "amount": 100.0, "status": "paid", … }
}
Cada nota aceita reserva R$ 0,49 (reserved). A reserva vira débito na autorização ou volta para o saldo em rejected/failed. A recarga é automática no cartão quando o saldo fica baixo.
GET /issuers
Lista os emitentes da conta, com certificate_required e a validade do certificado A1 (valid_until, days_to_expire). Emitente que emite pelo sistema da prefeitura não precisa de certificado: certificate_required: false.
Webhooks
Cadastre uma URL HTTPS e o Tulha faz um POST em JSON a cada evento da conta.
Cadastrar o endpoint e pegar o secret
- No portal (https://tulha.dev/painel), abra Webhooks e clique em Cadastrar webhook.
- Informe a URL que vai receber os eventos (ex.:
https://seu-sistema.com/webhooks/tulha). - O Tulha mostra o secret do endpoint, no formato
whsec_…, uma única vez. Guarde no seu servidor, por exemplo na variávelTULHA_WEBHOOK_SECRET.
O secret não é o token da API: o token autentica as suas chamadas ao Tulha; o secret prova que a chamada que chegou no seu sistema veio do Tulha. Cada endpoint tem o seu.
Perdeu o secret ou desconfia que vazou? Em Webhooks › Gerar novo secret. O antigo deixa de valer na hora e as próximas entregas, inclusive as repetições pendentes, já saem assinadas com o novo: atualize o seu sistema logo em seguida. No mesmo lugar dá para trocar a URL e desativar o endpoint. Para mandar de novo o webhook de uma nota, use Notas › Reenviar webhook.
Todo endpoint ativo recebe todos os eventos abaixo, dos dois ambientes. Nos eventos de nota, use data.environment para separar sandbox de production.
Eventos
| Evento | Quando |
|---|---|
nfse.authorized | Nota autorizada: chave, número, links de XML e PDF. |
nfse.rejected | Recusada pelo emissor, com errors. |
nfse.failed | Não pôde ser enviada. |
nfse.cancelled | Cancelamento registrado. |
nfse.replaced | Substituição concluída. |
certificate.expiring | Certificado A1 perto de vencer. |
wallet.low_balance · wallet.credited · wallet.recharge_failed | Movimentos da carteira. |
POST https://seu-sistema.com/webhooks/tulha
X-Tulha-Event: nfse.authorized
X-Tulha-Delivery: 5b1d0c8e-…
X-Tulha-Signature: 3f7a…
{
"event": "nfse.authorized",
"occurred_at": "2026-10-15T14:32:17-03:00",
"data": {
"id": "9d3f…", "external_ref": "pedido-1001", "status": "authorized",
"environment": "production", "access_key": "3168…", "number": "1234",
"xml_url": "https://api.tulha.dev/v1/nfse/9d3f…/xml", "pdf_url": "https://api.tulha.dev/v1/nfse/9d3f…/pdf",
"pdf_public_url": "https://tulha.dev/nfse/9d3f…/danfse?signature=…"
}
}
Confira a assinatura
X-Tulha-Signature é o HMAC-SHA256, em hexadecimal minúsculo, do corpo exato da requisição, usando o secret do endpoint como chave. Calcule com os bytes brutos do corpo, antes de decodificar o JSON: reformatar ou reordenar o JSON muda a assinatura. Compare em tempo constante e responda 401 se não bater:
// PHP $corpo = file_get_contents('php://input'); $esperada = hash_hmac('sha256', $corpo, getenv('TULHA_WEBHOOK_SECRET')); if (! hash_equals($esperada, $_SERVER['HTTP_X_TULHA_SIGNATURE'] ?? '')) { http_response_code(401); exit; } // Node.js (Express com express.raw) const esperada = crypto.createHmac('sha256', process.env.TULHA_WEBHOOK_SECRET).update(req.body).digest('hex'); const ok = crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(req.get('X-Tulha-Signature') || ''));
Entrega e repetição
- Responda
2xxem até 10 segundos. Qualquer outra coisa é tentada de novo até 10 vezes: depois de 1 min, 5 min, 30 min, 2 h, 6 h, 12 h e então a cada 24 h. - A mesma entrega pode chegar mais de uma vez: use
X-Tulha-Delivery(ou oidda nota + o evento) para ignorar repetidas. - Perdeu algo?
GET /nfse/{id}sempre tem o estado atual.
Erros
Todo erro vem no mesmo envelope:
HTTP/1.1 402 Payment Required { "error": { "code": "insufficient_balance", "message": "Saldo insuficiente para emitir…", "details": [ { "available": 0.2, "required": 0.49 } ] } }
| HTTP | code | O que fazer |
|---|---|---|
| 400 | validation_error | Corrija os campos listados em details (field, message). |
| 400 | idempotency_key_required | Envie o cabeçalho Idempotency-Key. |
| 401 | invalid_token | Token ausente, inválido ou revogado. |
| 402 | insufficient_balance | Saldo acabou. Aguarde a recarga confirmar. |
| 402 | account_suspended | Fale com o suporte. |
| 404 | not_found | Nota não existe nesta conta e neste ambiente. |
| 404 | asaas_integration_not_found | Nota antecipada do Asaas: a conta não tem integração Asaas ativa no ambiente do token. |
| 409 | idempotency_conflict | Mesma chave com outro corpo. Use outra chave ou repita o corpo original. |
| 409 | invalid_state | Só notas authorized podem ser canceladas ou substituídas. |
| 422 | issuer_not_found · issuer_inactive | CNPJ não cadastrado nesta conta, ou emitente inativo. |
| 422 | certificate_missing · certificate_expired · certificate_not_yet_valid · certificate_invalid | Envie um certificado A1 válido do emitente. |
| 422 | asaas_integration_ambiguous | Nota antecipada do Asaas: a conta tem mais de uma integração. Envie issuer_cnpj. |
| 422 | municipality_not_supported | O município do emitente ainda não é atendido. Fale com a gente. |
| 429 | — | Limite de requisições. Espere e tente de novo. |
Rejeições do emissor (ex.: código de serviço inválido para o município) não são erros HTTP: a nota vira rejected e o motivo vem em errors, com o código oficial.
Integração com o Asaas (sem código)
Se você cobra pelo Asaas, nem precisa chamar a API: cada cobrança paga vira uma NFS-e automaticamente.
- Gere uma chave de API no Asaas sem permissão de saque e envie para nós. Usamos só para ler os dados do cliente da cobrança.
- Você recebe uma URL de webhook exclusiva e um token.
- No Asaas, em Integrações › Webhooks, crie um webhook com essa URL, o token de autenticação e os eventos
PAYMENT_CONFIRMEDePAYMENT_RECEIVED. - Se o Asaas também emite notas para você, desligue a emissão de lá, para não sair nota em dobro.
Uma cobrança gera uma nota só, mesmo que o Asaas mande os dois eventos. A nota leva external_ref = id da cobrança e segue o mesmo fluxo de webhooks.
Nota antes do pagamento
Se o cliente precisa da nota antes de pagar o boleto ou o Pix, peça pela API com o id da cobrança do Asaas. Quando o pagamento for confirmado, não sai outra nota.
POST /v1/asaas/payments/pay_i1ui2qryo9oelo2d/nfse Authorization: Bearer <token>
Responde 202 com {"payment_id", "status": "received", "nfse_id": null, "reason": null} e a nota segue o fluxo normal de webhooks. Se a cobrança já tem nota (ou foi ignorada, como a 2ª parcela de um parcelamento), responde 200 com o estado atual. Com mais de uma integração Asaas na conta, envie issuer_cnpj no corpo. A competência da nota é o dia da emissão.
Pronto para integrar?
Entre na lista de espera e receba seu token de sandbox.