Pular para o conteúdo
tulha
Documentação

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 POST responde 202 na 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

  1. Entre no portal do cliente em https://tulha.dev/painel com o acesso que a gente cria para a sua conta.
  2. Abra Tokens da API e clique em Gerar token.
  3. Escolha o ambiente (sandbox ou production) e dê um nome que identifique a integração (ex.: erp-producao).
  4. 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:

TokenO que aconteceLimite
sandboxEmite no ambiente de testes do emissor. Não tem valor fiscal e não debita saldo.60 req/min
productionNota 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.

Guarde o token como senha. Ele fica no seu servidor, nunca no navegador nem no app do seu cliente.

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-Key obrigatório, até 191 caracteres. Use o ID do pedido ou da cobrança no seu sistema.

Corpo

CampoTipoDescrição
issuer_cnpjstringObrigatório. CNPJ do emitente (prestador), 14 dígitos sem máscara.
external_refstringSua referência (pedido, cobrança). Volta nos webhooks e serve de filtro na listagem. Até 191.
service.amountnúmeroObrigatório. Valor do serviço em reais, maior que zero, até 2 casas.
service.descriptionstringDescrição na nota. Obrigatória se o serviço cadastrado não tiver descrição padrão. Até 2.000.
service.codestringCódigo de um serviço cadastrado no seu emitente. Sem ele, vale o serviço padrão.
service.competencestringAAAA-MM ou AAAA-MM-DD. Padrão: data da emissão. Nunca no futuro.
service.national_tax_codestringCódigo de tributação nacional (6 dígitos), se quiser sobrescrever o do cadastro.
service.nbs_codestringCódigo NBS, se quiser sobrescrever o do cadastro.
customer.documentstringObrigatório. CPF ou CNPJ do tomador (com ou sem máscara, validado).
customer.namestringObrigatório. Nome ou razão social. Até 300.
customer.emailstringE-mail do tomador. Necessário para send_email.
customer.addressobjetoOpcional. 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_emailbooleanEnvia o PDF e o XML ao tomador quando a nota é autorizada. Padrão false.

Respostas

  • 202 {"id", "status": "queued"}: aceita, saldo reservado.
  • 400 dados inválidos · 402 saldo insuficiente · 409 idempotência em conflito · 422 emitente 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 o document_id da 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

StatusSignificadoFinal?
queuedAceita, esperando o worker.Não
sendingSendo enviada ao emissor.Não
retryingEmissor fora do ar ou resposta incerta. Vamos conferir e tentar de novo, com o mesmo número.Não
authorizedAutorizada. Tem chave de acesso, número, XML e PDF. Debita o saldo.Sim*
rejectedRecusada pelo emissor; o motivo vem em errors. A reserva volta para o saldo.Sim
failedNão pôde ser enviada (ex.: certificado vencido). A reserva volta para o saldo.Sim
cancelledCancelada depois de autorizada.Sim
replacedSubstituí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: 1 erro na emissão, 2 serviço não prestado, 9 outros.
  • 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 emiteComo o Tulha emiteO 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: no emissor nacional, o token 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:

CampoO que é
series · dps_numberSérie e número da DPS, definidos pelo Tulha ao aceitar a nota.
numberNúmero da NFS-e, atribuído pelo emissor na autorização.
access_keyChave 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 de 1 a 49999.
  • 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:

CampoO que é
CSTCódigo de situação tributária do IBS/CBS.
cClassTribClassificação tributária do serviço (tabela nacional).
cIndOpIndicador da operação, que define o local de incidência.
finNFSe · indDestFinalidade 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 IBSCBS da DPS. Sua integração não muda: nenhum campo novo no POST /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" } } }
}
Quem define os códigos é o seu contador. CST, classificação tributária e indicador da operação dependem do serviço, do regime e do município. O Tulha leva para a DPS exatamente o que estiver no cadastro.

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

  1. No portal (https://tulha.dev/painel), abra Webhooks e clique em Cadastrar webhook.
  2. Informe a URL que vai receber os eventos (ex.: https://seu-sistema.com/webhooks/tulha).
  3. O Tulha mostra o secret do endpoint, no formato whsec_…, uma única vez. Guarde no seu servidor, por exemplo na variável TULHA_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

EventoQuando
nfse.authorizedNota autorizada: chave, número, links de XML e PDF.
nfse.rejectedRecusada pelo emissor, com errors.
nfse.failedNão pôde ser enviada.
nfse.cancelledCancelamento registrado.
nfse.replacedSubstituição concluída.
certificate.expiringCertificado A1 perto de vencer.
wallet.low_balance · wallet.credited · wallet.recharge_failedMovimentos 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 2xx em 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 o id da 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 } ] } }
HTTPcodeO que fazer
400validation_errorCorrija os campos listados em details (field, message).
400idempotency_key_requiredEnvie o cabeçalho Idempotency-Key.
401invalid_tokenToken ausente, inválido ou revogado.
402insufficient_balanceSaldo acabou. Aguarde a recarga confirmar.
402account_suspendedFale com o suporte.
404not_foundNota não existe nesta conta e neste ambiente.
404asaas_integration_not_foundNota antecipada do Asaas: a conta não tem integração Asaas ativa no ambiente do token.
409idempotency_conflictMesma chave com outro corpo. Use outra chave ou repita o corpo original.
409invalid_stateSó notas authorized podem ser canceladas ou substituídas.
422issuer_not_found · issuer_inactiveCNPJ não cadastrado nesta conta, ou emitente inativo.
422certificate_missing · certificate_expired · certificate_not_yet_valid · certificate_invalidEnvie um certificado A1 válido do emitente.
422asaas_integration_ambiguousNota antecipada do Asaas: a conta tem mais de uma integração. Envie issuer_cnpj.
422municipality_not_supportedO 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.

  1. 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.
  2. Você recebe uma URL de webhook exclusiva e um token.
  3. No Asaas, em Integrações › Webhooks, crie um webhook com essa URL, o token de autenticação e os eventos PAYMENT_CONFIRMED e PAYMENT_RECEIVED.
  4. 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.

Entrar na lista de espera