Nesta referênciaIntrodução
  1. Início rápido
  2. Autenticação
  3. Teste e produção
  4. Ciclo de vida da nota
  5. Erros
  6. Limites
  7. NFS-e
  8. Erros
NFS-e
  1. GETBuscar NFS-e por referência
  2. POSTEmitir NFS-e
  3. POSTCancelar NFS-e
  4. GETConsultar NFS-e
  5. GETBaixar o PDF da nota
  6. GETBaixar o XML autorizado
  7. GETListar as atividades em que a empresa pode emitir
Pra máquinas e agentes
  1. openapi.json
  2. llms.txt
  3. llms-full.txt
API de NFS-e — Ext Contabilidade · v1.0.0

Emissão de NFS-e para empresas da Ext Contabilidade. REST, JSON, valores em centavos. A emissão é assíncrona: 202 e depois polling, com um envelope de erro único em toda falha.

Início rápido

  1. POST /v1/nfse com amount_in_cents e description. Mande também reference — o id da fatura no seu sistema —, que é por onde você reencontra a nota se o id se perder. Emitindo fora da atividade principal da empresa, mande cnae (7 dígitos) — e service_code (6) junto, se aquele CNAE tiver mais de uma linha. Omitidos, a nota sai na principal.
  2. Guarde o id do 202. Ele significa ACEITA, não autorizada.
  3. Consulte o header Location no intervalo do Retry-After até o status virar um desfecho.
  4. Em issued, baixe GET /v1/nfse/{id}/pdf e GET /v1/nfse/{id}/xml.

Os dois tropeços mais comuns da primeira integração: amount_in_cents é em CENTAVOS (R$ 1.250,50 = 125050), e failed e indeterminate pedem ações opostas.

Autenticação

Authorization: Bearer ext_sk_...

Não há auto-contratação: quem libera a empresa é o suporte ([email protected]), e antes disso toda rota responde 403 feature_not_enabled. Liberada, as chaves saem em Minha conta → API, exibidas uma única vez.

Teste e produção

A chave carrega o modo, e ele volta em livemode em toda resposta — inclusive no envelope de erro:

  • ext_sk_test_... — a nota é emitida de verdade, na produção restrita do Emissor Nacional: o ambiente do Sistema Nacional da NFS-e sem validade fiscal. Expira em 7 dias, não é cobrada e não consome sequencial da produção. Pode terminar failed — o status vem do SEFIN, o sistema nacional que autoriza as notas, e a rejeição chega em error.message com o código E0xxx.
  • ext_sk_live_... — documento fiscal já na primeira chamada. Desfazer exige cancelamento formal, que tem prazo.

O código é o mesmo nos dois: integre com a chave de teste e troque a variável de ambiente para ir a produção. Os modos não se enxergam — o que não é do modo da chave responde 404.

O modo de teste é MAIS exigente, de propósito: ele recusa nfse.municipal_registration_missing e nfse.nbs_invalid, pendências de cadastro que a emissão real aceita para o SEFIN rejeitar depois. Em troca não prova tudo — o que fica fora do alcance dele está em POST /v1/nfse.

Ciclo de vida da nota

O 202 diz ACEITA, não autorizada. Consulte o Location até chegar a um DESFECHO — nem todo status é um:

statusDesfecho?O que fazer
queuednãoAceita, ainda não enviada. Continue consultando.
processingnãoEnvio em curso. Transitório — nunca o trate como desfecho.
issuedsimAutorizada. access_key e links existem: baixe PDF e XML.
failedsimRecusada, e nenhum documento fiscal existe. Corrija o que error aponta e emita de novo: é seguro.
indeterminatesimPode existir NFS-e autorizada sem registro aqui. Nunca reemita — acione o suporte.
canceledsimCancelamento confirmado. Nota em cancelamento ainda aparece como issued.

Reemitir uma nota já autorizada gera documento fiscal em duplicidade, que só se desfaz com cancelamento formal — e cancelamento tem prazo.

switch (nfse.status) {
  case 'queued':
  case 'processing':
    return agendarNovaConsulta(nfse);  // ainda não é desfecho

  case 'issued':
    return guardar(nfse);

  case 'failed':
    return corrigirEReemitir(nfse);    // não há documento: é seguro

  case 'indeterminate':
    return abrirChamado(nfse);         // pode existir: NÃO reemitir

  default:
    return abrirChamado(nfse);         // status desconhecido: NÃO reemitir
}

O default é a parte que importa: status novo pode aparecer, e o desfecho seguro para um desconhecido é sempre não reemitir.

Erros

Toda falha sai no mesmo envelope: type (família), code (detalhe), message, param, retryable e request_id — cite o request_id ao abrir chamado. Trate pelo code, nunca pelo status HTTP: o mesmo 422 cobre situações muito diferentes.

code é uma lista aberta: código novo nasce quando uma recusa genérica ganha nome próprio, e acrescentar um não é breaking change — por isso o seu switch precisa de um ramo default. Código já publicado nunca é renomeado nem muda de significado. type é fechado: são as famílias do enum no schema, e uma nova só entra com versão nova da API.

O catálogo completo está na seção Erros, ao fim desta referência.

Limites

60 requisições de escrita e 300 de leitura por 60 segundos, por chave, em baldes SEPARADOS: o POST da emissão gasta de um, as consultas e os downloads do outro — o polling não gasta a cota de emissão. São piso garantido, não teto.

Toda resposta traz RateLimit-Limit, RateLimit-Remaining e RateLimit-Reset; excedido o limite, 429 com Retry-After. Corpo de até 10 MB.

No Retry-After: 3 que a API sugere, cada nota em voo gasta 20 leituras por minuto: até 15 notas simultâneas cabem obedecendo à dica ao pé da letra. Acima disso, espace o polling.

NFS-e

Emissão, consulta, cancelamento e download de notas fiscais de serviço. A emissão é assíncrona: o 202 diz que a nota foi aceita, não que foi autorizada.

Erros

Todo code que a API emite hoje, com o status HTTP e o que fazer em cada um. O envelope e a regra de tratamento estão em Erros, na introdução.

codeHTTPO que fazer
unauthorized401Chave ausente, inválida ou revogada.
feature_not_enabled403A empresa não tem a API liberada. Fale com o suporte.
insufficient_scope403A chave não tem o escopo da rota. Gere outra com o escopo certo.
company.issuer_not_enabled422Emissão automática desligada para a empresa.
company.issuer_not_configured422Emissor Nacional não configurado.
company.issuer_not_approved422A empresa ainda não foi liberada para emitir. Acione o suporte.
company.certificate_missing422Certificado ausente ou vencido no cofre.
nfse.cnae_missing_ctribnac422O CNAE principal não tem código de tributação mapeado. Acione o suporte.
nfse.cnae_missing_nbs422O CNAE principal não tem código NBS mapeado. Acione o suporte.
nfse.municipality_not_covered422Cidade/UF do cadastro não resolvem para código IBGE.
nfse.cnae_not_allowed422O cnae enviado não está no cadastro da empresa na Receita, ou a linha padrão dele está bloqueada. Mande outro, ou omita o campo para sair na atividade principal.
nfse.cnae_without_default422O cnae tem linha no catálogo, mas nenhuma é a padrão. Mande o service_code da linha que você quer, ou acione o suporte.
nfse.service_code_unknown422O service_code não existe no catálogo. Ele tem 6 dígitos (010501) — o clássico é mandar aí o CNAE, que tem 7.
nfse.service_code_not_allowed422O service_code existe, mas não está entre as linhas da empresa. Mandando o par, cnae e service_code precisam ser da MESMA linha.
nfse.nbs_invalid422Só em livemode: false. O código NBS do CNAE não consta da tabela oficial. Em produção a nota é aceita e o SEFIN rejeita depois (E0316).
nfse.municipal_registration_missing422Só em livemode: false. O município exige a Inscrição Municipal do prestador e ela não está cadastrada. Em produção a nota é aceita e o SEFIN rejeita depois (E0120).
nfse.customer_document_invalid400CPF/CNPJ com máscara ou fora de 11/14 dígitos.
nfse.customer_address_malformed400customer.address em br com CEP fora de 8 dígitos, state que não é sigla de UF, ou cidade que não resolve na tabela do IBGE. Diferente de nfse.customer_address_invalid — este é erro de FORMA, pego na criação; aquele é o SEFIN recusando o CEP depois.
nfse.customer_required422O município do prestador exige a identificação do tomador e a nota veio sem customer.document. Vale nos dois modos — nos demais municípios, nota sem tomador continua válida.
nfse.customer_name_required422Tomador pessoa física (CPF) sem customer.name: o nome é impresso na nota e não pode ficar vazio, e não há cadastro público de nome de PF de onde tirá-lo. Em CNPJ é opcional — ali o nome sai do cadastro da Receita.
nfse.customer_address_required422Desde o leiaute da NT 009 (IBS/CBS), nos serviços em que o imposto é devido no domicílio de quem contrata (a maioria: TI, consultoria, licenciamento…), a nota precisa do tomador: customer.document sempre e, se for CPF, customer.address com o CEP. Em CNPJ o endereço sai do cadastro da Receita. Serviço presencial (saúde, academia) e exportação não entram. param diz o campo que falta.
nfse.export_incomplete422Falta campo obrigatório da exportação. A mensagem lista todos os ausentes.
nfse.country_unsupported422País do tomador fora da Tabela de Países (ISO alpha-2).
nfse.currency_unsupported422Moeda fora da lista aceita. Use a sigla ISO (USD, EUR, GBP).
nfse.foreign_amount_invalid422Valor na moeda estrangeira ausente, zero ou negativo.
nfse.customer_type_conflict422Campo incompatível com o customer.type declarado.
nfse.competence_invalid400Competência não é uma data existente. Use o formato YYYY-MM-DD (2026-02-30 não existe).
nfse.competence_out_of_window422Competência fora do prazo: vale o mês corrente, e o mês anterior só até o dia 7 (inclusive). Nunca no futuro.
nfse.iss_retido_indisponivel422A emissão com ISS retido pelo tomador ainda não é automática. A Ext emite esta nota para você pelo Emissor Nacional: envie o pedido pelo chat. A nota não foi criada; não reenvie sem o campo, que ela sairia como não retida.
nfse.iss_withholding_not_allowed422A retenção não vale para esta nota, e a message diz por quê: tomador sem CNPJ (CPF, sem tomador ou no exterior), prestador MEI ou regime que ainda não emite com retenção. A nota não foi criada. Siga a orientação da message; não reenvie sem o campo só para passar, que a nota sairia como não retida.
nfse.federal_withholding_unavailable422A emissão com retenção de impostos federais ainda não é automática. A Ext emite esta nota para você pelo Emissor Nacional: envie o pedido pelo chat. A nota não foi criada; não reenvie sem o campo, que ela sairia sem a retenção que o tomador vai fazer.
nfse.federal_withholding_not_allowed422A retenção federal não vale para esta nota, e a message diz por quê: empresa fora do Lucro Presumido ou tomador sem CNPJ no Brasil. A nota não foi criada. Siga a orientação da message.
nfse.service_location_required422A linha da nota exige o local da execução: serviço com ISS devido no local (LC 116, art. 3º) ou atividade que registra o local. Mande service_location_ibge com o código IBGE do município onde o serviço foi executado.
nfse.service_location_invalid422O service_location_ibge tem 7 dígitos, mas não é município da tabela do IBGE. Confira o código do município da execução.
nfse.cancel_window_closed422O prazo de cancelamento passou: até o dia 7 do mês seguinte à competência da nota. Fale com o suporte — ainda pode ser possível.
nfse.not_cancelable409Estado incompatível: nota não autorizada, substituída, ou anexada manualmente. Só nota autorizada por esta API é cancelável por aqui.
nfse.already_canceled409Esta nota já foi cancelada — nada a fazer.
nfse.cancel_in_progress409Já há um pedido de cancelamento em andamento. Não repita o POST: o cancelamento é irreversível, e o segundo pedido seria rejeitado. Consulte a nota para acompanhar.
nfse.not_issued409Pediu PDF ou XML de nota ainda não autorizada. Repetível quando a nota está queued ou processing — ali o envelope vem com retryable: true e Retry-After. Em failed e indeterminate vem retryable: false: o arquivo não nasce sozinho.
nfse.reference_in_use409Já existe nota desta empresa com esse reference, e a mensagem traz o id dela. É a idempotência do reference funcionando. Para uma segunda nota intencional, use outra referência.
duplicate_suspected409Requisição idêntica há menos de 60s. Reenvie com Idempotency-Key própria, ou com reference diferente, se a segunda nota é intencional.
idempotency_key_in_use409A primeira requisição com essa chave, e com o MESMO corpo, ainda está em voo. Repetível.
idempotency_key_reuse409Mesma Idempotency-Key com corpo diferente, em qualquer momento das 24h da chave. Use uma chave nova.
invalid_request400Validação do corpo: campo obrigatório ausente, tipo errado ou campo desconhecido (o clássico é amount em vez de amount_in_cents). message traz a primeira causa; errors[], todas.
invalid_id400O id não é um UUID (com ou sem o prefixo nfse_).
not_found404Não há nota com esse id para a empresa da chave.
route_not_found404Rota inexistente sob /v1.
malformed_json400Corpo não é JSON válido.
payload_too_large413Corpo acima de 10 MB.
unsupported_media_type415Falta Content-Type: application/json.
rate_limit_exceeded429Estourou o limite. Espere o Retry-After. Repetível.
internal_error500Falha nossa, não mapeada. Cite o request_id ao abrir chamado. Repetível.
service_unavailable503Indisponibilidade temporária nossa. Repetível.

As linhas marcadas como repetíveis são as únicas com retryable: true, e nfse.not_issued é CONDICIONAL, pelo status da nota. Não deduza retryable do code: leia o campo do envelope e obedeça ao Retry-After.

Autenticação
Authorization: Bearer ext_sk_...
GET/v1/nfse

Buscar NFS-e por referência

Encontra a nota pelo reference que você enviou na emissão — o caminho de volta quando o id se perde.

Devolve um envelope de lista com no máximo uma nota, já que reference é único por empresa dentro de cada modo. Sem resultado, 200 com data vazio — nunca 404. Como toda leitura desta API, é escopada pelo MODO da chave.

Query

referencestringobrigatório

O reference que você enviou na emissão. Obrigatório — esta rota é busca por referência, não listagem: sem ele a resposta é 400 invalid_request.

Como reference é único por empresa dentro de cada modo, o resultado tem no máximo uma nota. Sem resultado, é 200 com data vazio.

Respostas

200

Envelope de lista. data traz a nota encontrada, ou vem vazio.

400

Payload inválido: campo mal formatado, ausente ou fora do domínio aceito. Corrigir e reenviar resolve.

401

Chave de API ausente, mal formada, revogada ou inexistente. Nenhuma nota é criada.

403

A chave é válida mas não pode fazer isto: falta o escopo exigido, ou a API pública está desligada para a empresa.

429

Limite de requisições por chave excedido. Aguarde o que o header Retry-After indicar.

500

Falha nossa. retryable é true: repita com a MESMA Idempotency-Key para não duplicar a nota, e cite o request_id se persistir.

Requisição
curl 'https://api.extcontabilidade.com.br/v1/nfse?reference=INV-2026-0042' \
  -H 'Authorization: Bearer ext_sk_...'
Resposta · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "object": null,
      "id": null,
      "livemode": null,
      "status": null,
      "amount_in_cents": null,
      "failed_attempts": null
    }
  ]
}
POST/v1/nfse

Emitir NFS-e

Com uma chave ext_sk_live_, esta chamada emite uma NFS-e real, e desfazê-la exige cancelamento formal, que tem prazo. Para ensaiar a integração inteira use ext_sk_test_: mesmo corpo, mesmo 202, mesmo polling, com livemode: false.

Devolve 202 na hora — a emissão é assíncrona. O Location traz a rota de consulta e o Retry-After, o intervalo de polling. O 202 diz ACEITA, não autorizada: acompanhe o status até issued ou failed. Recusas de cadastro chegam aqui, de forma síncrona, antes do 202.

O que o teste não prova

A nota de teste sai na produção restrita do Emissor Nacional: outro ambiente, com cadastro municipal próprio. Ela é emitida de verdade e pode falhar — e uma rejeição de lá é informação sobre o ambiente, não sobre o seu payload. Fora do alcance do teste ficam:

  • Diferenças de cadastro municipal entre os dois ambientes: um município pode estar habilitado num e não no outro, ou exigir dados diferentes em cada um. Nota aceita no teste pode ser rejeitada em produção, e o contrário também.
  • Se o código de tributação bate com o serviço que a sua description narra: ele é resolvido pelo CNAE, nunca pelo texto. Os dois ambientes aceitam a nota do mesmo jeito.
  • Recusa de cancelamento por janela: a nota de teste é apagada em 7 dias, então ensaiar a recusa exige emitir no fim do mês e cancelar nos primeiros dias do seguinte. Fora disso a nota já expirou, e a resposta é 404, não nfse.cancel_window_closed.
  • cancellation.status: "failed" é permanente em produção — um novo POST /cancel responde 409 nfse.cancel_in_progress, sem saída pela API —, mas libera nova tentativa no modo de teste. Não use o teste para prever a produção neste ponto.
  • A RECUSA do cancelamento chega diferente: no teste, cancellation.error.message traz o motivo do SEFIN; em produção é sempre a genérica "não recebemos a confirmação", porque lá a incerteza é real. Não faça parsing da mensagem — trate pelo cancellation.status.

Headers

Idempotency-Keystring

Chave escolhida por você para tornar a emissão segura de repetir. Opcional, e altamente recomendada em qualquer cliente com retry automático. Use uma chave nova por nota.

• Mesmo corpo + mesma chave devolve a primeira resposta, sem emitir de novo — é o que torna o retry de rede seguro.

• Corpo diferente com a mesma chave é 409 idempotency_key_reuse, por toda a janela de 24h: a chave identifica UMA emissão.

• Mesmo corpo, primeira ainda em voo, é 409 idempotency_key_in_use, que é repetível: aguarde e consulte antes de reenviar.

• Janela de 24h. Passada, a chave é esquecida e o mesmo corpo emite uma segunda nota.

Sem o header não há replay. A API ainda barra duplicata evidente — mesmo valor e mesma descrição em menos de um minuto — com 409 duplicate_suspected, mas essa rede é frouxa: não pega o retry que chega depois do minuto nem duas chamadas simultâneas, e recusa duas notas legitimamente iguais no mesmo minuto.

Melhor ainda: mande reference. É a única proteção que não expira — sendo único por empresa, a segunda emissão com a mesma referência é 409 nfse.reference_in_use, trazendo o id da original. Os dois se somam: a Idempotency-Key REPETE a resposta no retry imediato; o reference RECUSA a segunda nota para sempre.

Corpo · application/json

amount_in_centsintegerobrigatório

Valor total do serviço em centavos, inteiro. R$ 1.250,50 se envia como 125050 — nunca 1250.50, que é recusado com 400 em vez de virar R$ 12,50 em silêncio. Mínimo 100 (R$ 1,00), máximo 9007199254740991. É o valor que vai para a nota, sem retenções.

descriptionstringobrigatório

Descrição do serviço, impressa na nota. Entre 1 e 2000 caracteres — o teto é da nota fiscal, não nosso.

Sai como enviado, com UMA exceção: os marcadores {{data}}, {{mes}}, {{mes_extenso}}, {{mes_anterior}}, {{mes_anterior_extenso}} e {{ano}} são substituídos na emissão, resolvidos na data em que a chamada é recebida. Texto sem {{ passa intacto.

customerobjeto

Tomador do serviço. Omitir o objeto inteiro emite uma nota sem tomador, válida só nos serviços em que o IBS/CBS é devido no estabelecimento do prestador (presenciais, como saúde e academia).

Leiaute NT 009 (IBS/CBS): nos serviços em que o imposto é devido no domicílio de quem contrata — a maioria —, a nota sem customer.document, ou de CPF sem customer.address, é recusada na hora com 422 nfse.customer_address_required, antes do 202. Vale já com a chave de teste e, na de produção, a partir da virada do Emissor Nacional (01/12/2026).

Alguns municípios exigem o tomador em qualquer serviço: neles, a nota sem customer.document é recusada com 422 nfse.customer_required.

Tomador no exterior é exportação de serviço, e sai por aqui: mande customer.type: "foreign" com customer.address completo, a identificação fiscal (customer.tax_id ou customer.tax_id_absence_reason) e foreign_amount. Sem o type, o tomador é tratado como brasileiro — omitir customer continua significando *nota sem tomador*, e não *tomador no exterior*.

typestring: br | foreign

Onde está o tomador. br (padrão) é o comportamento de sempre — identificação por CPF/CNPJ, operação doméstica tributável. foreign faz a nota sair como exportação de serviço: sem ISS, com o grupo de comércio exterior, e exigindo customer.address completo, customer.tax_id (ou customer.tax_id_absence_reason) e foreign_amount.

Omitir o campo é o mesmo que br. Nenhum payload que funciona hoje muda de comportamento.

documentstring

CPF (11 dígitos) ou CNPJ (14), apenas dígitos — máscara com pontos, barra ou traço é RECUSADA com 400, não limpa.

namestring

Nome do tomador. Obrigatório quando customer.document é um CPF — o nome é impresso na nota, não há cadastro público de nome de pessoa física, e sem ele a nota é 422 nfse.customer_name_required. Em CNPJ é opcional: vence o nome do cadastro da Receita, e este campo é o fallback se a consulta não resolver.

tax_idstring

Número de identificação fiscal do tomador no país dele (NIF). Exportação exige este campo ou customer.tax_id_absence_reason — nunca os dois vazios.

tax_id_absence_reasonstring: exempt | not_required

Motivo de o tomador não ter identificação fiscal informada. exempt = dispensado; not_required = não exigência no país dele. Use quando não houver customer.tax_id.

addressobjeto

Endereço do tomador. Obrigatório em foreign. Em br, obrigatório para CPF nos serviços em que o IBS/CBS é devido no domicílio de quem contrata (leiaute NT 009: TI, licenciamento, consultoria e a maioria dos serviços; não entram os presenciais, como saúde e academia) — sem ele, 422 nfse.customer_address_required. Nos demais casos é opcional: quando enviado, substitui em bloco o endereço do cadastro da Receita Federal — use quando o CEP cadastral for recusado pela prefeitura (nfse.customer_address_invalid).

streetstringobrigatório

Logradouro do tomador (xLgr da DPS).

numberstringobrigatório

Número do endereço (nro da DPS).

districtstringobrigatório

Bairro ou distrito (xBairro da DPS). Obrigatório pelo leiaute, mesmo onde o endereço local não usa bairro.

citystringobrigatório

Cidade. Em br, usada com state para resolver o município no IBGE — nome oficial, sem abreviação. Em foreign: texto livre (xCidade da DPS).

statestringobrigatório

Estado, província ou região. Em br: sigla da UF (2 letras) — usada para resolver o município no IBGE, junto de city. Em foreign: texto livre (xEstProvReg da DPS).

postal_codestringobrigatório

Código postal. Em br: CEP com 8 dígitos, sem hífen. Em foreign: formato livre (cEndPost da DPS).

countrystringobrigatório

País do tomador em ISO 3166-1 alpha-2 — dois caracteres, como US, PT, BR ou MZ. Em foreign vai para endExt/cPais e para tribMun/cPaisResult, o país em que o resultado do serviço se verifica. Em br o único valor aceito é BR; em foreign, qualquer país que não seja o Brasil.

foreign_amountobjeto

Valor da nota na moeda do faturamento. Obrigatório quando customer.type é foreign; recusado fora da exportação.

Não substitui amount_in_cents. Os dois são exigidos: o valor em reais é o que vai para a nota e para a apuração, e a API não converte câmbio — a taxa é decisão sua.

currencystringobrigatório

Sigla ISO de três letras da moeda do faturamento — USD, EUR, GBP. Não é o código numérico do BACEN: a tradução é nossa. Moeda fora da lista aceita é 422 nfse.currency_unsupported, com as siglas no corpo do erro.

amount_in_centsintegerobrigatório

Valor na moeda estrangeira em centavos, número inteiro. 3.600,00 se envia como 360000.

Duas casas decimais para qualquer moeda, inclusive as que não têm centavo (iene) ou têm três casas (dinar kuwaitiano): é o que a DPS recebe, e prometer outra precisão seria prometer o que a emissão não carrega.

referencestring

Seu identificador para esta nota — tipicamente o id da fatura no seu sistema. Opcional, e a forma mais confiável de reencontrar a nota: o id que devolvemos se perde se o seu processo morrer antes de gravá-lo, e a Idempotency-Key é esquecida em 24h.

• Único por empresa, dentro de cada modo. Reenviar o mesmo é 409 nfse.reference_in_use, com o id da nota que já existe na mensagem — o retry nunca vira nota duplicada, sem prazo de validade. Precisando de duas notas para a mesma fatura, use referências diferentes.

• A mesma referência serve uma vez em teste e uma em produção: os modos não se enxergam.

• Consultável em GET /v1/nfse?reference=....

• Até 64 caracteres, sem espaços — espaço é recusado em vez de aparado, porque "INV-1 " e "INV-1" seriam chaves diferentes.

Com Idempotency-Key junto, os dois se somam: o retry imediato ganha o REPLAY da chave, e só uma referência de verdade repetida cai no 409.

competencestring

Competência da nota (dCompet), no formato YYYY-MM-DD. Opcional.

• Omitido, a competência é a data desta chamada — o certo para a maioria.

• Enviado, precisa ser do mês corrente, ou do mês anterior até o dia 7 (inclusive, 23:59:59 de Brasília). Fora disso é 422 nfse.competence_out_of_window.

• Nunca no futuro — nem um dia futuro do mês corrente.

• Data que não existe (2026-02-30) é 400 nfse.competence_invalid.

O competence da resposta confirma em que competência a nota saiu.

cnaestring

CNAE em que a nota deve sair, 7 dígitos, apenas números (a máscara 6202-3/00 é recusada com 400). Opcional.

• Omitido, a nota sai na atividade principal — o certo para a maioria.

• Enviado, precisa estar no cadastro da empresa na Receita Federal, principal ou secundária; um que não esteja é 422 nfse.cnae_not_allowed.

• A nota sai na linha padrão daquele CNAE; precisando de outra, mande service_code junto. CNAE sem linha padrão é 422 nfse.cnae_without_default.

O cnae e o service_code da resposta confirmam a linha em que a nota saiu.

service_codestring

Código de tributação nacional da linha, 6 dígitos, apenas números. Opcional, e o desempate do cnae — não confunda os dois: aqui são 6 dígitos (010501), não os 7 do CNAE.

A linha da nota sai desta tabela:

o que você mandalinha da nota
nadaa padrão da atividade principal da empresa
cnaea padrão daquele CNAE
cnae + service_codea linha exata
service_codea primeira linha com aquele código, principal antes de secundária

Mande o par quando o seu CNAE tiver mais de uma linha e você precisar da que não é a padrão. Código fora do catálogo é 422 nfse.service_code_unknown; código que existe mas não é da empresa, 422 nfse.service_code_not_allowed.

service_location_ibgestring

Município onde o serviço foi executado: código IBGE de 7 dígitos, apenas números. Opcional na maioria das notas.

• Obrigatório quando o serviço tem o ISS devido no local da execução (LC 116/2003, art. 3º — obras, demolição, limpeza, vigilância, eventos, estacionamento e os demais subitens da regra). Nesses casos o ISS da nota vai para o município informado aqui, e não para o do prestador. Sem o campo, a nota é recusada com 422 nfse.service_location_required. Esta exigência entra junto com a emissão com ISS retido pelo tomador (iss_retido_pelo_tomador); antes dela, o campo só é exigido no caso abaixo.

• Também é exigido quando a atividade da empresa pede o registro do local da execução, mesmo que o ISS continue no município do prestador.

• Código que não existe na tabela do IBGE é 422 nfse.service_location_invalid.

• Nas demais notas o campo é ignorado: o local da prestação é o município do prestador.

A linha da nota (e daí a obrigatoriedade) é a resolvida por cnae e service_code.

iss_retido_pelo_tomadorboolean

O tomador retém o ISS desta nota? Opcional; omitido = não retido.

Marque só quando o tomador é obrigado pela lei do município dele a reter o ISS e pagar direto à prefeitura.

• Com a emissão com retenção habilitada, a nota sai com o ISS retido pelo tomador e, para empresa do Simples Nacional, com a alíquota de ISS da LC 123/2006, art. 21, § 4º, informada no documento. A retenção exige tomador com CNPJ no Brasil e não vale para todo prestador (MEI, por exemplo): quando não vale para a nota, true volta 422 nfse.iss_withholding_not_allowed com o motivo, e a nota não é criada.

• Enquanto ela não estiver habilitada, true volta 422 nfse.iss_retido_indisponivel e a nota não é criada. A Ext emite esta nota para você pelo Emissor Nacional: envie o pedido pelo chat.

retencao_federal_pelo_tomadorboolean

O tomador vai reter impostos federais desta nota (IR e PIS/COFINS/CSLL)? Opcional; omitido = sem retenção. Só para empresa do Lucro Presumido e tomador com CNPJ no Brasil.

• Com a emissão com retenção habilitada, a nota sai com a retenção destacada, calculada pela lei: IR de 1,5% (RIR/2018, art. 714) e PIS/COFINS/CSLL de 4,65% (Lei 10.833/2003, arts. 30 e 31) — a partir da competência de janeiro de 2027, só a CSLL de 1% (LC 214/2025). Retenção de até R$ 10,00 é dispensada pela lei e não vai na nota. Quando a retenção não vale para a nota (empresa fora do Lucro Presumido, tomador sem CNPJ), true volta 422 nfse.federal_withholding_not_allowed com o motivo.

• Enquanto ela não estiver habilitada, true volta 422 nfse.federal_withholding_unavailable e a nota não é criada. A Ext emite esta nota para você pelo Emissor Nacional: envie o pedido pelo chat.

Respostas

202

Nota aceita e enfileirada, status queued. Uma repetição atendida pela idempotência devolve este mesmo corpo, com o status que a nota original tiver no momento.

400

Payload inválido: campo mal formatado, ausente ou fora do domínio aceito. Corrigir e reenviar resolve.

401

Chave de API ausente, mal formada, revogada ou inexistente. Nenhuma nota é criada.

403

A chave é válida mas não pode fazer isto: falta o escopo exigido, ou a API pública está desligada para a empresa.

409

Conflito com uma requisição anterior — mesma Idempotency-Key ainda em voo, mesma Idempotency-Key com outro corpo, reference já usada, ou nota equivalente criada há menos de um minuto. Consulte antes de reenviar.

413

O corpo da requisição passou do tamanho máximo aceito. Reenviar o mesmo corpo não resolve.

415

Corpo sem Content-Type: application/json — o servidor não o lê. Envie o header e reenvie.

422

A requisição está bem formada mas não pode prosseguir: pendência de cadastro da empresa, ou município que exige o tomador. Reenviar sem mudar nada não resolve. (A Idempotency-Key reaproveitada com outro corpo saiu daqui e virou 409: ela é conflito de estado da chave, não corpo improcessável.)

429

Limite de requisições por chave excedido. Aguarde o que o header Retry-After indicar.

500

Falha nossa. retryable é true: repita com a MESMA Idempotency-Key para não duplicar a nota, e cite o request_id se persistir.

Requisição
# valor em CENTAVOS: R$ 1.250,50 = 125050
# reference: o id da fatura no seu sistema — reencontra a nota e recusa a segunda
# cnae: opcional — sem ele a nota sai na atividade principal da empresa
curl -X POST https://api.extcontabilidade.com.br/v1/nfse \
  -H "Authorization: Bearer $EXT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "amount_in_cents": 125050,
    "description": "Licenciamento de uso da plataforma — plano mensal",
    "customer": { "document": "12345678000190" },
    "reference": "INV-2026-0042",
    "cnae": "6202300"
  }'
Resposta · 202
{
  "object": "nfse",
  "id": "nfse_9f8e7d6c-5b4a-4321-9876-0a1b2c3d4e5f",
  "livemode": true,
  "status": "issued",
  "amount_in_cents": 125050,
  "description": "Desenvolvimento de software sob encomenda — competência 07/2026",
  "competence": "2026-01-31",
  "customer": {
    "document": "12345678000190",
    "name": "Maria Souza",
    "type": "foreign",
    "tax_id": "98-7654321",
    "tax_id_absence_reason": "not_required",
    "address": {}
  },
  "foreign_amount": {
    "currency": "EUR",
    "amount_in_cents": 360000
  },
  "reference": "INV-2026-0042",
  "service_code": "010501",
  "cnae": "6202300",
  "access_key": "31260712345678000190550010000000011234567890",
  "failed_attempts": 0,
  "created_at": "2026-07-15T13:42:07.512Z",
  "issued_at": "2026-07-15T13:43:19.004Z",
  "cancelable_until": "2026-02-08T02:59:59.000Z",
  "cancellation": {
    "status": "pending",
    "requested_at": "2026-08-31T19:00:38.539Z",
    "reason": "issuance_error",
    "error": {
      "code": "nfse.cancel_failed",
      "message": "Não recebemos a confirmação do cancelamento. NÃO gere uma nova nota fiscal — acione o suporte para conferir o estado da nota no SEFIN."
    }
  },
  "failed_at": "2026-07-15T13:43:19.004Z",
  "error": {
    "code": "issue_failed",
    "message": "A emissão da nota falhou. Confira os dados enviados; se estiverem corretos, fale com o suporte ANTES de reemitir."
  },
  "links": {
    "pdf": "https://api.extcontabilidade.com.br/v1/nfse/nfse_9f8e7d6c-5b4a-4321-9876-0a1b2c3d4e5f/pdf?t=eyJhbGciOiJIUzI1NiJ9...",
    "xml": "https://api.extcontabilidade.com.br/v1/nfse/nfse_9f8e7d6c-5b4a-4321-9876-0a1b2c3d4e5f/xml?t=eyJhbGciOiJIUzI1NiJ9..."
  }
}
POST/v1/nfse/{id}/cancel

Cancelar NFS-e

Cancelamento é definitivo, e com ext_sk_live_ a nota deixa de valer como documento fiscal.

Só cancela nota emitida no mês corrente: uma de 30/08 pode ser cancelada até 31/08, e em 01/09 não mais. Leia o instante exato em cancelable_until, não recalcule a regra. Fora da janela, o suporte ainda pode cancelar.

A resposta é 202: acompanhe o objeto cancellation. Ele é pending até o desfecho, succeeded quando o status chega a canceled, e failed quando não recebemos a confirmação — aí nunca reemita, acione o suporte para conferir o estado da nota.

Path

idstringobrigatório

Id da nota, com ou sem o prefixo nfse_. O UUID cru funciona; um prefixo de outro tipo de recurso é 400, não 404.

Corpo · application/json

reasonstring: issuance_error | service_not_provided | other

Motivo do cancelamento, registrado no evento fiscal. Omitido, assume other.

descriptionstring

Justificativa livre, de 15 a 255 caracteres — o limite é da nota fiscal, não nosso, e fora dele o cancelamento é recusado. Validamos aqui para a recusa chegar na hora, e não minutos depois com a nota presa. Omitida, usamos uma padrão.

Respostas

202

Pedido aceito. cancellation.status é pending; consulte a nota até ele virar succeeded ou failed.

400

Payload inválido: campo mal formatado, ausente ou fora do domínio aceito. Corrigir e reenviar resolve.

401

Chave de API ausente, mal formada, revogada ou inexistente. Nenhuma nota é criada.

403

A chave é válida mas não pode fazer isto: falta o escopo exigido, ou a API pública está desligada para a empresa.

404

Não existe nota com este id para a empresa da chave. Mesmo corpo para id inexistente e para nota de outra empresa.

409

A nota não está em estado de ser cancelada: já cancelada (nfse.already_canceled), com pedido em andamento (nfse.cancel_in_progress) ou não cancelável por esta API (nfse.not_cancelable). Nenhum dos três se repete.

413

O corpo da requisição passou do tamanho máximo aceito. Reenviar o mesmo corpo não resolve.

415

Corpo sem Content-Type: application/json — o servidor não o lê. Envie o header e reenvie.

422

A nota é de um mês anterior (nfse.cancel_window_closed). Pela API, o cancelamento só vale dentro do mês da emissão.

429

Limite de requisições por chave excedido. Aguarde o que o header Retry-After indicar.

500

Falha nossa. retryable é true: repita com a MESMA Idempotency-Key para não duplicar a nota, e cite o request_id se persistir.

Requisição
curl -X POST 'https://api.extcontabilidade.com.br/v1/nfse/{id}/cancel' \
  -H 'Authorization: Bearer ext_sk_...' \
  -H 'Content-Type: application/json' \
  -d '{
  "reason": "issuance_error",
  "description": "Valor lançado errado no pedido 4471."
}'
Resposta · 202
{
  "object": "nfse",
  "id": "nfse_9f8e7d6c-5b4a-4321-9876-0a1b2c3d4e5f",
  "livemode": true,
  "status": "issued",
  "amount_in_cents": 125050,
  "description": "Desenvolvimento de software sob encomenda — competência 07/2026",
  "competence": "2026-01-31",
  "customer": {
    "document": "12345678000190",
    "name": "Maria Souza",
    "type": "foreign",
    "tax_id": "98-7654321",
    "tax_id_absence_reason": "not_required",
    "address": {}
  },
  "foreign_amount": {
    "currency": "EUR",
    "amount_in_cents": 360000
  },
  "reference": "INV-2026-0042",
  "service_code": "010501",
  "cnae": "6202300",
  "access_key": "31260712345678000190550010000000011234567890",
  "failed_attempts": 0,
  "created_at": "2026-07-15T13:42:07.512Z",
  "issued_at": "2026-07-15T13:43:19.004Z",
  "cancelable_until": "2026-02-08T02:59:59.000Z",
  "cancellation": {
    "status": "pending",
    "requested_at": "2026-08-31T19:00:38.539Z",
    "reason": "issuance_error",
    "error": {
      "code": "nfse.cancel_failed",
      "message": "Não recebemos a confirmação do cancelamento. NÃO gere uma nova nota fiscal — acione o suporte para conferir o estado da nota no SEFIN."
    }
  },
  "failed_at": "2026-07-15T13:43:19.004Z",
  "error": {
    "code": "issue_failed",
    "message": "A emissão da nota falhou. Confira os dados enviados; se estiverem corretos, fale com o suporte ANTES de reemitir."
  },
  "links": {
    "pdf": "https://api.extcontabilidade.com.br/v1/nfse/nfse_9f8e7d6c-5b4a-4321-9876-0a1b2c3d4e5f/pdf?t=eyJhbGciOiJIUzI1NiJ9...",
    "xml": "https://api.extcontabilidade.com.br/v1/nfse/nfse_9f8e7d6c-5b4a-4321-9876-0a1b2c3d4e5f/xml?t=eyJhbGciOiJIUzI1NiJ9..."
  }
}
GET/v1/nfse/{id}

Consultar NFS-e

Destino do Location do 202. Consulte até o status chegar a issued ou failed, respeitando o Retry-After.

Escopada pelo MODO da chave: a de teste enxerga só notas de teste, e a de produção só notas reais. O que não é do modo responde 404, igual a um id inexistente.

Path

idstringobrigatório

Id da nota, com ou sem o prefixo nfse_. O UUID cru funciona; um prefixo de outro tipo de recurso é 400, não 404.

Respostas

200

Estado atual da nota.

400

Payload inválido: campo mal formatado, ausente ou fora do domínio aceito. Corrigir e reenviar resolve.

401

Chave de API ausente, mal formada, revogada ou inexistente. Nenhuma nota é criada.

403

A chave é válida mas não pode fazer isto: falta o escopo exigido, ou a API pública está desligada para a empresa.

404

Não existe nota com este id para a empresa da chave. Mesmo corpo para id inexistente e para nota de outra empresa.

429

Limite de requisições por chave excedido. Aguarde o que o header Retry-After indicar.

500

Falha nossa. retryable é true: repita com a MESMA Idempotency-Key para não duplicar a nota, e cite o request_id se persistir.

Requisição
curl 'https://api.extcontabilidade.com.br/v1/nfse/{id}' \
  -H 'Authorization: Bearer ext_sk_...'
Resposta · 200
{
  "object": "nfse",
  "id": "nfse_9f8e7d6c-5b4a-4321-9876-0a1b2c3d4e5f",
  "livemode": true,
  "status": "issued",
  "amount_in_cents": 125050,
  "description": "Desenvolvimento de software sob encomenda — competência 07/2026",
  "competence": "2026-01-31",
  "customer": {
    "document": "12345678000190",
    "name": "Maria Souza",
    "type": "foreign",
    "tax_id": "98-7654321",
    "tax_id_absence_reason": "not_required",
    "address": {}
  },
  "foreign_amount": {
    "currency": "EUR",
    "amount_in_cents": 360000
  },
  "reference": "INV-2026-0042",
  "service_code": "010501",
  "cnae": "6202300",
  "access_key": "31260712345678000190550010000000011234567890",
  "failed_attempts": 0,
  "created_at": "2026-07-15T13:42:07.512Z",
  "issued_at": "2026-07-15T13:43:19.004Z",
  "cancelable_until": "2026-02-08T02:59:59.000Z",
  "cancellation": {
    "status": "pending",
    "requested_at": "2026-08-31T19:00:38.539Z",
    "reason": "issuance_error",
    "error": {
      "code": "nfse.cancel_failed",
      "message": "Não recebemos a confirmação do cancelamento. NÃO gere uma nova nota fiscal — acione o suporte para conferir o estado da nota no SEFIN."
    }
  },
  "failed_at": "2026-07-15T13:43:19.004Z",
  "error": {
    "code": "issue_failed",
    "message": "A emissão da nota falhou. Confira os dados enviados; se estiverem corretos, fale com o suporte ANTES de reemitir."
  },
  "links": {
    "pdf": "https://api.extcontabilidade.com.br/v1/nfse/nfse_9f8e7d6c-5b4a-4321-9876-0a1b2c3d4e5f/pdf?t=eyJhbGciOiJIUzI1NiJ9...",
    "xml": "https://api.extcontabilidade.com.br/v1/nfse/nfse_9f8e7d6c-5b4a-4321-9876-0a1b2c3d4e5f/xml?t=eyJhbGciOiJIUzI1NiJ9..."
  }
}
GET/v1/nfse/{id}/pdf

Baixar o PDF da nota

Devolve os BYTES do PDF no corpo, nunca um redirect para URL assinada. Aceita a chave de API ou o token temporário que o links.pdf da consulta já traz na query: ele vale 15 minutos, serve só esta nota, e é o que faz o link abrir para quem não tem a chave. Só existe depois de issued; antes disso, 409 nfse.not_issued. Vale nos dois modos — em livemode: false o PDF vem carimbado sem validade jurídica.

Path

idstringobrigatório

Id da nota, com ou sem o prefixo nfse_. O UUID cru funciona; um prefixo de outro tipo de recurso é 400, não 404.

Respostas

200

PDF da nota. Content-Disposition: attachment, com nome derivado do id.

400

Payload inválido: campo mal formatado, ausente ou fora do domínio aceito. Corrigir e reenviar resolve.

401

Chave de API ausente, mal formada, revogada ou inexistente. Nenhuma nota é criada.

403

A chave é válida mas não pode fazer isto: falta o escopo exigido, ou a API pública está desligada para a empresa.

404

Não existe nota com este id para a empresa da chave.

409

A nota existe, mas ainda não foi autorizada (nfse.not_issued). Espere o Retry-After e consulte o status antes de tentar de novo.

429

Limite de requisições por chave excedido. Aguarde o que o header Retry-After indicar.

500

Falha nossa. retryable é true: repita com a MESMA Idempotency-Key para não duplicar a nota, e cite o request_id se persistir.

Requisição
curl 'https://api.extcontabilidade.com.br/v1/nfse/{id}/pdf' \
  -H 'Authorization: Bearer ext_sk_...'
GET/v1/nfse/{id}/xml

Baixar o XML autorizado

O XML autorizado — é ele o documento fiscal; o PDF é a representação impressa. Mesma forma do PDF: só depois de issued, e vale nos dois modos. Em livemode: false ele traz tpAmb: 2, a marca de que a nota não tem validade fiscal.

Path

idstringobrigatório

Id da nota, com ou sem o prefixo nfse_. O UUID cru funciona; um prefixo de outro tipo de recurso é 400, não 404.

Respostas

200

XML autorizado, sem reprocessamento. Content-Disposition: attachment, com nome derivado do id.

400

Payload inválido: campo mal formatado, ausente ou fora do domínio aceito. Corrigir e reenviar resolve.

401

Chave de API ausente, mal formada, revogada ou inexistente. Nenhuma nota é criada.

403

A chave é válida mas não pode fazer isto: falta o escopo exigido, ou a API pública está desligada para a empresa.

404

Não existe nota com este id para a empresa da chave.

409

A nota existe mas ainda não foi autorizada (nfse.not_issued) — não há XML para baixar.

429

Limite de requisições por chave excedido. Aguarde o que o header Retry-After indicar.

500

Falha nossa. retryable é true: repita com a MESMA Idempotency-Key para não duplicar a nota, e cite o request_id se persistir.

Requisição
curl 'https://api.extcontabilidade.com.br/v1/nfse/{id}/xml' \
  -H 'Authorization: Bearer ext_sk_...'
GET/v1/activities

Listar as atividades em que a empresa pode emitir

Os valores de cnae e service_code que o POST /v1/nfse aceita desta empresa — e só eles: toda linha desta lista é emitível.

Use is_default para saber se o cnae sozinho basta: sendo false, mande o par cnae + service_code para chegar naquela linha. A nota que não manda nenhum dos dois sai na linha que tem is_main e is_default ao mesmo tempo.

Uma atividade do seu cadastro na Receita Federal pode não aparecer aqui. Isso não significa que o seu cadastro esteja errado: a atividade ainda não está liberada para emissão, quase sempre por pendência de cadastro do nosso lado. Fale com o suporte.

Sem filtro por query string e sem paginação — a lista inteira vem numa resposta só.

Respostas

200

Envelope de lista. data vem vazio quando nenhuma atividade da empresa está liberada para emissão — resposta legítima, não erro.

401

Chave de API ausente, mal formada, revogada ou inexistente. Nenhuma nota é criada.

403

A chave é válida mas não pode fazer isto: falta o escopo exigido, ou a API pública está desligada para a empresa.

429

Limite de requisições por chave excedido. Aguarde o que o header Retry-After indicar.

500

Falha nossa. retryable é true: repita com a MESMA Idempotency-Key para não duplicar a nota, e cite o request_id se persistir.

Requisição
curl 'https://api.extcontabilidade.com.br/v1/activities' \
  -H 'Authorization: Bearer ext_sk_...'
Resposta · 200
{
  "object": "list",
  "has_more": false,
  "data": [
    {
      "cnae": null,
      "service_code": null,
      "description": null,
      "is_main": null,
      "is_default": null
    }
  ]
}