TF Fiscal
Documentation

Getting started

General conventions

The two response shapes (bare responses vs. platform envelope), error shapes, error typing, HTTP status reference, idempotency, rate limits, localization, data types, the NF-e access key structure and file download links.

Conventions shared by the endpoints of the TF Fiscal Open API.

Response shapes

The API has two response shapes, and which one you receive depends on the endpoint and on which layer produced the response.

Bare responses (standard endpoints)

All standard endpoints (companies, NF-e, CT-e, DC-e, verification, identity, webhook registration) respond without an envelope:

Endpoint groupSuccess shape
Company registration, webhook registrationBare object ({ "empresaId": ... }, { "webHookId": ... })
Certificate association, NF-e / CT-e / DC-e issuance and cancellationHTTP 200 with no body; the result arrives asynchronously
NF-e / CT-e / DC-e query, correction letter and event registrationBare object: the document, the protocol or the event list
XML verification, chave lookupBare object: the parsed invoice (XML verification adds a validation block)
CNPJ lookup, CPF lookupBare object: the registry record

Business and request-level errors of the standard endpoints come in two error shapes, by endpoint family.

Issuing family (companies, NF-e, CT-e, DC-e, webhook registration; HTTP 400 / 404): an array of errors, each with codigo and mensagem. Request-body validation failures produce one entry per field:

json
[
{ "codigo": "NFe0001", "mensagem": "A Nota fiscal nao foi encontrada. Por favor, verifique se o id foi informado corretamente" }
]

codigo is a string: the three codes GW001 (invalid city / state), CER0005 (certificate password mismatch) and NFe0001 (invoice not found) keep their alphanumeric values, as do the DCe* codes of the DC-e family; all other entries carry the platform error code as a numeric string.

Verification and identity families (HTTP 400, and 422 / 428 / 451 / 503 for identity): a bare object with code and message:

json
{ "code": 10015004, "message": "Invoice not found" }
FieldTypeDescription
codeintegerPlatform error code
messagestringHuman-readable explanation, localized

Platform envelope

Used by the echo endpoint (POST /openapi/demo/echo) for every response, by the file download endpoint for its failures, and by the platform gateway for authentication-layer errors on every endpoint (HTTP 401 / 403 / 429), which are produced before the request reaches the endpoint:

json
{ "success": true, "message": "OK", "data": { } }
json
{ "success": false, "errorType": 1, "code": 10009003, "message": "Signature error" }
FieldTypePresenceDescription
successbooleanalwaysfalse for every error
errorTypeintegeron errorError class, see Error typing
codeintegeron errorPlatform error code, see Error codes
messagestringalwaysHuman-readable explanation, localized
dataobject | nullon successEndpoint payload; not populated on errors

Handling both shapes

Your error handler must accept the envelope and the bare error shapes. A robust rule:

  1. Treat HTTP 400 and above as failure, and additionally success: false when the body is an envelope.
  2. Parse the body as JSON. If it is an array, read codigo from each entry; if it is an object, read code.
  3. Branch on the code, never on message / mensagem text (they are localized).

Error typing (errorType)

Envelope errors carry an errorType field that classifies the failure source:

errorTypeClassMeaning
1API errorAuthentication, authorization or business rejection by TF Fiscal
2SEFAZ rejectionThe Brazilian tax authority rejected the operation
3System faultUnexpected platform failure; safe to retry with backoff
4Validation failureRequest field validation failed

HTTP status quick reference

HTTPScenarioBody shape
200Request accepted; for XML verification this includes a failed validation (check the validation block)Bare data, or no body
400Request invalid or business rule violated[{codigo, mensagem}] (issuing) or {code, message} (verification / identity)
404empresaId / document id not found (issuing family)[{codigo, mensagem}]
401Authentication failed (token / sign / timestamp), or an invalid or expired download linkPlatform envelope
403Application disabled / not effective / integrator disabled / not subscribedPlatform envelope
422 / 428 / 451CPF lookup withheld by law (holder aged 16 to 17 / age not verifiable / holder under 16); do not retry{code, message}, no personal fields
429Rate limit exceededPlatform envelope
503Upstream data source unavailable (identity, 10016020) or file not rendered yet (download, 10009037); retry after Retry-After{code, message} or platform envelope
5xxPlatform-side faultRetry with backoff; if persistent, contact the platform with the failing timestamp and path

Idempotency

  • NF-e issuance is idempotent on the request id you generate: re-submitting the same id reuses the original task. If the previous attempt was denied (Negada), resending the same id with corrected fields issues again with the new payload, no new id is needed. Changing key fields such as the recipient while the previous attempt is still processing or already authorized is rejected with codigo 10004032.
  • CT-e and DC-e issuance are idempotent on id as well: an identical message reuses the original task, a different message under the same id is rejected (10017030 for CT-e, 10019030 for DC-e), and a terminal failure (Falha) reopens the task with the new message.
  • XML verification is idempotent on the access key (chave): resubmitting while a verification is in progress returns the current progress, and terminal verdicts are reused for 24 hours. To force a re-check, send the header forceRevalidate: true on the XML endpoint (the header is not part of the signature). The chave lookup is a fixed GET and has no re-verification channel; to force a re-check, resubmit through the XML endpoint. A Tier-1 REJECTED has no Tier-2 record; re-verification requires resubmitting the XML.
  • Webhook deliveries are idempotent by event_id: every retry and every fan-out target of one event carries the same value, so deduplicate on it. See Webhooks.

Rate limiting and backoff

  • Per-application quota: exceeding it returns HTTP 429 with code 10009006. Limits are enforced per application and per endpoint group. Back off and retry (start at 1 s, double up to 30 s, add jitter) and smooth your call rate.
  • Per-CNPJ anti-flood: if a company accumulates too many pending issuance tasks, new submissions are rejected with codigo 10004002 until the queue drains; retry later.
  • Timestamp window: requests with a timestamp beyond ±300 s of the server clock are rejected (10009001), which also bounds replay of captured requests.
  • 401 and 403 are configuration errors; retrying them without a fix only consumes quota.
  • File downloads count against the application throttle in their own group and are not billed.

Response localization

message, mensagem and *Description fields follow the request language: the Language header (zh / en / pt / es) takes precedence, then Accept-Language (supports pt-BR and q-values). Without a language header, responses default to Portuguese (pt). The language headers are not part of the signature.

Webhook payloads carry only language-independent enum values; there are no *Description fields there. For programmatic decisions always use codes and enum fields (code, codigo, status, validationStatus, errors[].code, situacao.codigo), never description text.

Timestamps and data types

  • Times produced by the platform (dataCriacao, dataAutorizacao of issued documents, webhook occurred_at, verifiedAt, echo serverTime) are ISO-8601 UTC with the Z suffix, in every environment.
  • Times parsed from a third-party document by the verification API (dataEmissao, dataAutorizacao) are returned verbatim with the document's UTC offset, for example 2026-07-23T11:20:05-03:00.
  • The timestamp request header is Unix time in seconds, not milliseconds.
  • Identifiers are strings even when numeric (empresaId, webHookId, event_id), to avoid JavaScript number precision loss. Treat all ids as opaque strings; the document id you generate is a string as well.
  • Document numbers (numero) and series (serie) are strings. Amounts and quantities are JSON numbers.
  • Path variables such as cnpj, cpf and chave are digit-only strings without formatting characters; the CPF date of birth (nascimento) is DDMMYYYY.

The NF-e access key (chave)

The chave de acesso is the nationally unique, 44-digit identifier of an NF-e. It appears as chaveAcesso in query and verification responses and as the path variable of the chave lookup. Its structure:

PositionsLengthFieldMeaning
1 to 22cUFIBGE code of the issuing state (e.g. 35 = SP)
3 to 64AAMMYear and month of issue (YYMM)
7 to 2014CNPJIssuer CNPJ
21 to 222modFiscal document model (55 = NF-e)
23 to 253serieInvoice series
26 to 349nNFInvoice number
351tpEmisEmission type
36 to 438cNFRandom numeric code
441cDVCheck digit (modulo 11)

Notes:

  • Always store and transmit the chave as a 44-character string (leading zeros are significant).
  • The chave lookup validates length, characters and the check digit locally before any lookup is attempted; a malformed chave returns HTTP 400 with code 10015104.
  • CT-e (model 57) and DC-e (model 99) keys share the same 44-digit layout with their own mod value.

Download links in query responses and callback payloads (linkDanfe, linkDownloadXml, linkDacce and their CT-e / DC-e counterparts) look like https://api.v2.tffiscal.com/openapi/files/{kind}/{ref}?token=.... They need no signature headers: a plain GET that follows the 302 redirect is enough. The token is bound to the path, the integrator and the application, so use the link exactly as returned. Links are valid for 7 days by default and every query issues fresh ones; an expired link returns 401 with 10009036. Details: File download.