Developers / Documentos Fiscais / NFS-e
Documentos Fiscais · NFS-e

Emissão Conjunta POST

Gera um link web de uso único onde o tomador (ou qualquer pessoa sem acesso à sua API) revisa, completa e confirma os dados de uma NFS-e antes da emissão de verdade -- sem precisar de API key, login ou acesso ao seu sistema.

Quando utilizar

Quando quem tem os dados finais do serviço não é o seu backend -- ex.: o tomador precisa confirmar/completar a descrição do serviço, escolher o código NBS/CNAE certo, ou aprovar o valor antes da nota sair. Você cria a sessão pela API (com o que já sabe: prestador, tomador, serviço, valor, alíquota) e manda o link -- a outra pessoa faz o resto no navegador.

💡
Já sabe o código NBS? Envie em codigo_nbs na criação da sessão -- a tela de revisão já abre com ele preenchido, e quem revisar não precisa escolher manualmente. Sem esse campo, a busca de NBS fica em branco até a pessoa preencher na hora. É obrigatório pra confirmar a sessão, mesmo que não seja exigido nesta chamada de criação.
💡
Prestador não é Simples Nacional nem MEI? O Ambiente Nacional de NFS-e exige a classificação tributária de IBS/CBS (reforma_tributaria.cst/ classificacao_tributaria) nesse caso. Se você não souber a classificação específica, não precisa mandar nada aqui -- a API preenche automaticamente com a classificação genérica (tributação integral) na hora de confirmar.
💡
Não é uma API de emissão direta. POST /v1/joint-emissions/sessions só cria a sessão (o rascunho + o link) -- a emissão de fato só acontece quando alguém abre o link e confirma na tela, ou usa POST .../confirm dentro da própria sessão (endpoints de token, não fazem parte desta API Bearer). Se você quer emitir direto, sem essa etapa de revisão humana, use POST /v1/nfse/emissoes.

Pré-requisitos

  • Contrato com o produto Emissão Conjunta ativo (separado do produto NFS-e padrão).
  • Prestador cadastrado, ativo, com certificado digital -- mesmos requisitos de emissão direta.
  • Tomador pré-carregado via POST /v1/contribuintes/{id}/tomadores, se quiser que a tela já abra com os dados dele preenchidos. Sem isso, a sessão ainda é criada, mas quem abrir o link precisa digitar os dados do tomador manualmente.

Fluxo da operação

POST /v1/joint-emissions/sessions (Bearer, sua API key)
Você recebe emissionUrl
Manda o link pro tomador (e-mail, WhatsApp, etc.)
Tomador revisa/confirma no navegador -- sem API key
NFS-e emitida de verdade

Pra saber o resultado, duas opções: consultar GET /v1/joint-emissions/sessions/{session_id} (Bearer) depois, ou informar callback_url na criação -- a CentralFiscal faz um POST pra essa URL quando o tomador confirma (status vira authorized ou rejected). É best-effort: uma tentativa só, sem retry se sua URL estiver fora do ar, e sem assinatura/HMAC no corpo -- não é (ainda) um sistema de webhooks genérico, é específico desta sessão. Pra confiabilidade, trate o callback como um aviso e confirme sempre via GET.

O link (emissionUrl) é de uso único -- vale só pra essa sessão/documento. Se o mesmo prestador for usar a Emissão Conjunta com frequência, a resposta também traz um workspaceUrl fixo (uma espécie de "CRM de emissão" -- lista de sessões + criar documento novo, sem precisar gerar link a cada vez). O workspaceUrl só aparece no corpo da resposta na primeira vez que é criado pra aquele prestador -- mesma regra de qualquer segredo neste produto (ex.: API key): guarde-o, ele não volta a aparecer em chamadas seguintes.

Endpoint

POST https://areacliente.centralfiscal.com.br/v1/joint-emissions/sessions

Headers

HeaderValor
AuthorizationBearer <sua_api_key>obrigatório
Content-Typeapplication/jsonobrigatório

Isso vale só pra esta chamada (criar a sessão). O link resultante (emissionUrl/workspaceUrl) que a outra pessoa abre no navegador não usa API key -- a segurança dali é o próprio token opaco na URL, de uso único e com expiração.

Body

Clique num campo com pra expandir.

prestador_documentorequired
string
CNPJ do prestador, autorizado no contrato com o produto Emissão Conjunta.
tomador_documentorequired
string
CPF/CNPJ do tomador. Se já carregado via POST .../tomadores, a tela abre com os dados dele preenchidos -- senão, quem abrir o link precisa digitar.
codigo_servicorequired
string
Código de tributação nacional (fiscal.nfse_servico_nacional).
descricao_servicorequired
string
valor_servicorequired
number
codigo_nbs
string
Código NBS (Nomenclatura Brasileira de Serviços). Não é exigido pra criar a sessão, mas é obrigatório pra confirmar -- se não vier aqui, quem abrir o link precisa escolher manualmente antes de conseguir confirmar (ver callout acima).
aliquota_iss
number
Percentual, ex.: 2 para 2%. Dispensado se o prestador for Simples Nacional/MEI e o ISS não for retido pelo tomador -- nesse caso o ISS é recolhido dentro do DAS, e o campo relevante é percentual_total_tributos_simples abaixo, não este. Obrigatório em todos os demais casos (regime normal, ou Simples/MEI com retenção pelo tomador).
percentual_total_tributos_simples
number
Percentual efetivo do Simples Nacional -- o mesmo número já usado na apuração do PGDAS-D. Obrigatório só para prestador Simples Nacional que não seja MEI (o Ambiente Nacional de NFS-e exige esse dado explícito, pTotTribSN, e não temos como calculá-lo). Sem relação com aliquota_iss acima. Não envie 0 como placeholder -- é tratado como valor real, e SEFIN autoriza a nota com um percentual de tributos que não existe.
object
IBS/CBS (EC 132/2023, LC 214/2025). Só é obrigatório (pro Ambiente Nacional aceitar) quando o prestador NÃO é Simples Nacional nem MEI -- nesse caso, se você não mandar aqui, a API preenche automaticamente com a classificação genérica (cst=000/classificacao_tributaria=000001, tributação integral) na hora de confirmar. Mande explicitamente só se souber que o serviço tem uma classificação diferente da genérica.
cst
string
Código de Situação Tributária do IBS/CBS, 3 dígitos.
classificacao_tributaria
string
Classificação tributária do IBS/CBS associada ao CST, 6 dígitos.
municipio_incidencia_ibge
integer
Opcional -- default é o município do prestador.
exigibilidade_iss
string
Default: "1"
iss_retido
boolean
Indica se o ISS foi retido pelo tomador -- pré-preenche a tela de revisão.
rps_numero
string
Número do RPS, se o prestador controlar a própria numeração.
rps_serie
string
Série do RPS. Opcional -- se omitido junto com rps_numero, usa a numeração nacional padrão.
data_emissao
string <date>
Data de emissão do RPS/NFS-e (AAAA-MM-DD) -- pré-preenche a tela de revisão.
competencia
string <date>
Data de competência do serviço prestado (AAAA-MM-DD).
servico
object
Alternativa a informar codigo_servico/descricao_servico soltos na raiz -- mesmo formato do grupo servico da emissão direta de NFS-e. Único objeto aninhado de fato lido; os demais (prestador/tomador completos) não são.
external_reference
string
Referência livre do seu sistema pra essa sessão.
callback_url
string
Recebe um POST best-effort (1 tentativa, sem retry, sem assinatura) quando o tomador confirma. Não confunda com um sistema de webhooks genérico -- é específico desta sessão.
⚠️
Serviço e valor vêm nesta própria chamada. Não existe uma segunda etapa pra completá-los -- são esses campos que pré-preenchem a tela de revisão. Todos ficam no nível raiz do corpo (não dentro de um objeto payload_inicial ou similar).
⚠️
Campo desconhecido é rejeitado. Qualquer chave no nível raiz do corpo que não estiver nesta lista devolve 400, com a lista do que foi rejeitado e do que é aceito. Não mande variações de nome "pra garantir" (ex.: nbs, nbsCode e codigo_nbs juntos) -- use exatamente um dos nomes documentados aqui; objetos aninhados como prestador/tomador completos também não são lidos, só os campos de nível raiz acima.

Gap conhecido

Compra governamental (grupo gTribCompraGov do XSD de IBS/CBS) ainda não é suportado pela Emissão Conjunta nem pela emissão direta -- se o tomador for órgão público com esse regime específico, a CentralFiscal ainda não tem como marcar isso no documento. Sem previsão nesta versão da API.

Exemplos por regime tributário

Os campos obrigatórios mudam de verdade conforme o regime do prestador -- aliquota_iss, percentual_total_tributos_simples e reforma_tributaria nunca são os três juntos ao mesmo tempo. Quatro exemplos completos: um "máximo" com todos os campos, e mais três -- um por regime -- veja o código ao lado.

Máximo (todos os campos)

Mandar todo campo documentado de uma vez é seguro, mesmo os que não valem pro regime do seu prestador. Quem decide o que de fato entra no XML é o regime tributário já cadastrado no contribuinte (optante_simples_nacional/MEI, resolvido internamente), nunca o que você manda: aliquota_iss é aceito e usado quando enviado mesmo se dispensado pro seu regime; percentual_total_tributos_simples só é lido de verdade pra prestador ME/EPP -- pra MEI e regime normal, o motor nem olha esse campo (usa indTotTrib/vTotTrib em vez disso); reforma_tributaria é usado sempre que presente, e auto-preenchido pela API quando ausente e o regime exigir (não-Simples/MEI). Se você não quer decidir campo a campo por integração, pode simplesmente mandar tudo que souber -- não há risco de rejeição por excesso de informação.

MEI

ISS recolhido dentro do DAS (não destacado) -- aliquota_iss dispensado. percentual_total_tributos_simples não se aplica a MEI (só a ME/EPP). IBS/CBS (reforma_tributaria) também dispensado -- MEI recolhe por fora, sem destacar na DPS.

Simples Nacional (ME/EPP, não-MEI)

Mesma dispensa de aliquota_iss do MEI (ISS recolhido no DAS), mas aqui percentual_total_tributos_simples é obrigatório -- o Ambiente Nacional exige esse percentual explícito (pTotTribSN) pra ME/EPP, e não tem como calculá-lo. reforma_tributaria continua dispensado (mesma razão do MEI).

Regime normal

Aqui aliquota_iss é obrigatório (não há DAS que recolha por fora). percentual_total_tributos_simples não se aplica (só é exigido pra Simples/ME-EPP). reforma_tributaria não foi enviado neste exemplo de propósito -- o Ambiente Nacional exige a classificação de IBS/CBS pra regime normal, e como não foi informada aqui, a API preenche sozinha a classificação genérica (tributação integral) na hora de confirmar. Mande o campo explicitamente se souber a classificação real do serviço.

Resposta de sucesso

200 OK com o link pronto pra enviar. status começa como "pending_confirmation" -- ainda não foi emitido, só a sessão foi criada.

Próximos passos

  • Enviar emissionUrl pro tomador (e-mail, link direto, o que fizer sentido pro seu fluxo).
  • Consultar GET /v1/joint-emissions/sessions/{session_id} pra saber se já foi confirmado/emitido -- ou usar callback_url pra ser avisado.
  • Listar sessões de um prestador: GET /v1/joint-emissions/sessions?prestador_documento=....
  • Pré-carregar tomadores (recomendado): POST /v1/contribuintes/{id}/tomadores.