TF Fiscal
Documentation

NFS-e

Issue service invoices (NFS-e) on behalf of a registered issuer: onboarding flow, municipal channel differences, inbound manifestation, webhook payloads, error model, state machine and an integration checklist.

Capabilities and onboarding flow

StepEndpointNotes
1 Register issuerPOST /openapi/v2/empresasSame as NF-e, with a new optional emissaoNFSe section; the NF-e block may be omitted if you only issue NFS-e, and IE is not required then
2 Bind certificatePOST /openapi/v1/empresas/{empresaId}/certificadoDigitalSame as NF-e
3 Register callbackPOST /openapi/v1/webhooksSame as NF-e; NFS-e results reuse the same callback URL
4 IssuePOST /openapi/v2/empresas/{empresaId}/nfseReturns HTTP 200 with no body on acceptance; filing is asynchronous
5 QueryGET /openapi/v2/empresas/{empresaId}/nfse/{nfseId}Status, events, XML / DANFSe download links, request echo
5-A ReconcileGET …/nfse/emitidas · GET …/nfse/emitidas/{chaveAcesso}Issued list (time-window paging) and lookup by access key
6 CancelPOST /openapi/v2/empresas/{empresaId}/nfse/{nfseId}/cancelarAsynchronous: 200 with an acceptance receipt, outcome via webhook
7 Query received invoicesGET …/nfse/recebidas · GET …/nfse/recebidas/{chaveAcesso}List of received invoices (paged by period) and lookup of one by key; this is where the chaveAcesso used to manifest comes from
8 Manifest on a received invoicePOST …/recebidas/{chaveAcesso}/manifestarRegister a confirmation / rejection of a received NFS-e; the body carries tipo

Authentication is identical to NF-e: headers token / timestamp / sign, see Authentication.

Three structural differences from NF-e

1. The access key is 50 digits and is generated by the issuing authority. An NF-e's 44-digit key is computed by the taxpayer; an NFS-e key only exists once the authority has accepted the document. A failed document therefore has no chaveAcesso and can only be located by identificacaoDps, the 45-character DPS identifier we generate.

2. Service invoices are governed by the municipality. The platform routes by the issuer's municipality; the resulting channel appears as padraoMunicipal in the query response. The national standard (Nacional) and the São Paulo municipal system (Paulistana) are supported today; an unsupported municipality is rejected at acceptance (10018010) rather than accepted and failed asynchronously.

3. Cancellation has a window, and outside it the answer is not "you cannot cancel" but "it goes to municipal review". See Cancel NFS-e.

Prerequisites

  • The issuer must be approved with a usable certificate, otherwise acceptance is rejected (10018004).
  • The issuer must have an NFS-e profile, created through the emissaoNFSe section of Register company or maintained by operations in the console. Simples status, Simples assessment, special regime and DPS series always come from the profile; the issuance request neither sends nor overrides them. Without a profile, acceptance rejects (10018005). Service code, CNAE, NBS and the other service-level fields are not in the profile: they go with every invoice.
  • ambienteEmissao must match the issuer's current environment. After registration an issuer sits in the test environment (Homologacao); switching to production is an operations action and is not exposed through the API.
  • Tax values follow the request: what it carries prevails, and only what is missing is looked up by the platform in the tax reference data, such as the LC 116 list, the federal withholding classification and rates and the IBPT approximate burden. When the data needed for federal withholding, the own PIS/COFINS rates or the ISS place of taxation has not been imported yet, acceptance rejects with 10018065, listing the request fields that can replace it.
  • When the recipient is a legal entity (CNPJ), federal withholding applies: send the amounts in servico.retencoes, where zero declares no withholding, or omit them together with pisCofins.tipoRetencao and the platform determines them from the official withholding classification of the service plus cliente.orgaoPublico and cliente.optanteSimplesNacional; sending pisCofins.tipoRetencao without the amounts is rejected (10018001). Missing information is rejected explicitly (10018023) with every missing item listed at once, rather than treated as zero.

Extra constraints on the São Paulo channel

A São Paulo issuer (padraoMunicipal = Paulistana) needs its municipal registration CCM (inscricaoMunicipal): when it is in the issuer profile it is used as is; when it is missing the platform queries the municipal system by CNPJ at acceptance and stores the result in the profile; acceptance is rejected (10018005) only when nothing is found.

The channel does not support discount fields: fold discounts into the service amount or the deduction. It does not support substitute issuance either (the municipal RPS layout has no such group): cancel the original and issue a new one; sending it is rejected at acceptance (10018001).

It also gets two automatic capabilities that need no new endpoint: São Paulo invoices it receives are pulled directly from the municipal system into the inbound list (origem=PrefeituraSP, merged automatically once the ADN transcription arrives); an outbound invoice cancelled on the municipal portal is synchronized to Cancelada and nfse.canceled is sent as usual.

State machine

text
┌──────────────► Negada (terminal)
│
Pendente ────────┼──────────────► Autorizada ──┬──► Cancelada (terminal)
│ │
└──────────────► Falha ├──► EmProcessoDeCancelamento ──┬──► Cancelada
(failed before numbering, │ └──► Autorizada (review refused)
no document exists) └──► Substituida (replaced)
StatusMeaningTerminal
PendenteAccepted, no outcome yetno
AutorizadaIssued successfullyno (can be cancelled or replaced)
NegadaRejected by the authorityyes
FalhaPlatform-side failure (bad parameters / issuer not eligible); no document was createdyes
CanceladaCancelledyes
EmProcessoDeCancelamentoCancellation review accepted, awaiting the municipalityno
SubstituidaReplaced by another documentyes

Manifestation status of a received invoice

A received invoice's status uses Autorizada / Cancelada / Substituida from the table above; this issuer's manifestation is kept separately in the manifestacao block, and each party manifests at most once per invoice.

text
(not manifested, list filter Pendente) ──┬──► Confirmada (confirmed by this issuer, terminal)
├──► Rejeitada (rejected by this issuer) ──► RejeicaoAnulada (rejection annulled by the municipality, terminal)
└──► ConfirmacaoTacita (tacit confirmation by the municipality, terminal)
StatusMeaningCan still manifest
null (filter value Pendente)Not manifestedyes (while the invoice is Autorizada)
ConfirmadaConfirmed by this issuerno
RejeitadaRejected by this issuerno
ConfirmacaoTacitaTacitly confirmed by the municipality after the deadlineno
RejeicaoAnuladaThe municipality annulled this issuer's rejectionno

Webhook callbacks

Delivered to the URL you register as a bare JSON payload.

Event codeTriggernfseStatus
nfse.authorizedIssued successfullyAutorizada
nfse.rejectedRejected by the authority or failed on our sideNegada
nfse.canceledCancellation completed (including an approved review)Cancelada
nfse.cancel_requestedCancellation review acceptedEmProcessoDeCancelamento
nfse.cancel_deniedCancellation did not happen and the document stays authorized: synchronous refusal by the authority, dispatch exhausted, or review request refused by the municipalityAutorizada
nfse.substitutedThis document was replaced by a substitute; the payload carries an extra nfseChaveSubstitutaSubstituida
nfse.inbound.receivedInbound NFS-e received (this issuer is the recipient / intermediary, explicit subscription required)—
nfse.inbound.canceledInbound NFS-e cancelled (explicit subscription required)—
nfse.inbound.substitutedInbound NFS-e substituted (explicit subscription required)—

Note: Subscription semantics. An app that has never registered any subscription row receives all six outbound events. As soon as any subscription row exists for the app (for example an inbound event), the app switches to allow-list mode and only explicitly enabled events are delivered. When subscribing to inbound events, enable the outbound events you need as well, otherwise outbound notifications stay parked waiting for a target.

json
{
"tipo": "NFS-e",
"empresaId": "1001",
"nfseId": "PED-20260918-001",
"nfseStatus": "Autorizada",
"nfseMotivoStatus": null,
"nfseLinkDanfse": "https://api.v2.tffiscal.com/openapi/files/danfse/…",
"nfseLinkXml": "https://api.v2.tffiscal.com/openapi/files/xml/9001?token=…",
"nfseNumero": "1287",
"nfseSerie": "1",
"nfseChaveAcesso": "3550308…",
"nfseIdentificacaoDps": "DPS3550308…",
"nfseDataEmissao": "2026-09-18T13:00:00Z",
"nfseDataAutorizacao": "2026-09-18T13:00:03Z",
"nfseNumeroProtocolo": null,
"nfseAtualizadoEm": "2026-09-18T13:00:03Z"
}

nfseAtualizadoEm is the moment of this state change. Delivery order is not guaranteed: retries and multiple instances can make an older event arrive after a newer one. Compare it with the timestamp you already hold and drop the older one instead of overwriting state in arrival order.

On a failure callback nfseChaveAcesso may be empty (the authority never generated a key); reconcile by nfseIdentificacaoDps then. A callback for a failure before numbering carries neither, so only nfseId can reconcile it.

Error model

Public endpoints return a bare array:

json
[{"codigo": "10018012", "mensagem": "Codigo de servico fora da lista oficial LC 116: 999999"}]
CodeMeaningWhat to do
10018001Parameter validation refused at acceptance (the message states the reason)Fix as the message says
NFSe0001Document not found: nfseId does not belong to this issuer, or the inbound key is not among this issuer's received invoicesCheck nfseId / chaveAcesso against empresaId
10018004Issuer not eligible (disabled / certificate unusable)Check issuer status and certificate
10018005Issuer has no NFS-e profileCreate it first with Register company carrying the emissaoNFSe section
10018010The municipality's NFS-e standard is not supported yetThat municipality cannot issue yet, contact the platform
10018012Service code is not in the LC 116 listCheck codigoTributacaoNacional
10018013Recipient document is invalidCheck the CPF / CNPJ / NIF
10018014Service export is missing the destination countryAdd servico.paisPrestacao
10018015The replaced document cannot be replacedCheck the status of the replaced document
10018020The ISS rate cannot be determinedSend servico.iss.aliquota or the right servico.codigoTributacaoMunicipal
10018022A Simples issuer must not send the ISS rate directlyRemove aliquota
10018023Federal withholding cannot be determined yet; the message lists every missing itemProvide the listed fields or send the withheld amounts in servico.retencoes
10018030The same request is already being processedRetry later
10018031That order already has an invoice, or the same id carries different contentUse another order number or check the content
10018040A cancellation review is already in progressWait for the verdict
10018041The document has already been replacedNo further operation is allowed
10018042The current status does not allow this eventCheck the document status
10018063The DPS message does not comply with the official XSD (details carry line, column, error key and element name)Fix as detailed: usually text outside Latin-1, an incomplete address, or a value outside the official code table
10018064The NFS-e event message does not comply with the official XSDCheck motivo length (15-255), the codigoMotivo value and the character set
10018065The NFS-e tax reference data this invoice needs has not been imported, rejected at acceptance; the message lists each item with an arrow to the request field that replaces itSend the fields after the arrow in the request, or ask the platform to confirm the data is imported; for iss-local-incidencia, sending servico.iss.municipioIncidencia is enough

Authentication and authorization errors (10009xxx) are in Authentication; the full table is in Error codes.

Integration checklist

  1. Register the issuer (with emissaoNFSe) → bind the certificate → register the webhook.
  2. Issue a minimal invoice in Homologacao (required fields only), confirm nfse.authorized arrives and that the XML and DANFSe download.
  3. Submit the same id again and confirm no second invoice is issued.
  4. Send a service code that does not exist and confirm a synchronous 10018012 refusal rather than an asynchronous Negada.
  5. Cancel an authorized invoice and confirm it reaches Cancelada or EmProcessoDeCancelamento depending on the window.
  6. Query a failed document and confirm it reconciles by identificacaoDps rather than chaveAcesso.
  7. Manifest on a received invoice with tipo=Confirmacao, then check the detail shows manifestacao.status=Confirmada; send tipo=Rejeicao on the same invoice and confirm 10018042 (each party manifests once).