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 group | Success shape |
|---|---|
| Company registration, webhook registration | Bare object ({ "empresaId": ... }, { "webHookId": ... }) |
| Certificate association, NF-e / CT-e / DC-e issuance and cancellation | HTTP 200 with no body; the result arrives asynchronously |
| NF-e / CT-e / DC-e query, correction letter and event registration | Bare object: the document, the protocol or the event list |
| XML verification, chave lookup | Bare object: the parsed invoice (XML verification adds a validation block) |
| CNPJ lookup, CPF lookup | Bare 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:
[{ "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:
{ "code": 10015004, "message": "Invoice not found" }
| Field | Type | Description |
|---|---|---|
code | integer | Platform error code |
message | string | Human-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:
{ "success": true, "message": "OK", "data": { } }
{ "success": false, "errorType": 1, "code": 10009003, "message": "Signature error" }
| Field | Type | Presence | Description |
|---|---|---|---|
success | boolean | always | false for every error |
errorType | integer | on error | Error class, see Error typing |
code | integer | on error | Platform error code, see Error codes |
message | string | always | Human-readable explanation, localized |
data | object | null | on success | Endpoint payload; not populated on errors |
Handling both shapes
Your error handler must accept the envelope and the bare error shapes. A robust rule:
- Treat HTTP 400 and above as failure, and additionally
success: falsewhen the body is an envelope. - Parse the body as JSON. If it is an array, read
codigofrom each entry; if it is an object, readcode. - Branch on the code, never on
message/mensagemtext (they are localized).
Error typing (errorType)
Envelope errors carry an errorType field that classifies the failure source:
| errorType | Class | Meaning |
|---|---|---|
| 1 | API error | Authentication, authorization or business rejection by TF Fiscal |
| 2 | SEFAZ rejection | The Brazilian tax authority rejected the operation |
| 3 | System fault | Unexpected platform failure; safe to retry with backoff |
| 4 | Validation failure | Request field validation failed |
HTTP status quick reference
| HTTP | Scenario | Body shape |
|---|---|---|
| 200 | Request accepted; for XML verification this includes a failed validation (check the validation block) | Bare data, or no body |
| 400 | Request invalid or business rule violated | [{codigo, mensagem}] (issuing) or {code, message} (verification / identity) |
| 404 | empresaId / document id not found (issuing family) | [{codigo, mensagem}] |
| 401 | Authentication failed (token / sign / timestamp), or an invalid or expired download link | Platform envelope |
| 403 | Application disabled / not effective / integrator disabled / not subscribed | Platform envelope |
| 422 / 428 / 451 | CPF lookup withheld by law (holder aged 16 to 17 / age not verifiable / holder under 16); do not retry | {code, message}, no personal fields |
| 429 | Rate limit exceeded | Platform envelope |
| 503 | Upstream data source unavailable (identity, 10016020) or file not rendered yet (download, 10009037); retry after Retry-After | {code, message} or platform envelope |
| 5xx | Platform-side fault | Retry with backoff; if persistent, contact the platform with the failing timestamp and path |
Idempotency
- NF-e issuance is idempotent on the request
idyou generate: re-submitting the sameidreuses the original task. If the previous attempt was denied (Negada), resending the sameidwith 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 withcodigo10004032. - CT-e and DC-e issuance are idempotent on
idas well: an identical message reuses the original task, a different message under the sameidis rejected (10017030for CT-e,10019030for 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 headerforceRevalidate: trueon 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-1REJECTEDhas 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
codigo10004002until the queue drains; retry later. - Timestamp window: requests with a
timestampbeyond ±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,dataAutorizacaoof issued documents, webhookoccurred_at,verifiedAt, echoserverTime) are ISO-8601 UTC with theZsuffix, 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 example2026-07-23T11:20:05-03:00. - The
timestamprequest 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 documentidyou 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,cpfandchaveare digit-only strings without formatting characters; the CPF date of birth (nascimento) isDDMMYYYY.
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:
| Positions | Length | Field | Meaning |
|---|---|---|---|
| 1 to 2 | 2 | cUF | IBGE code of the issuing state (e.g. 35 = SP) |
| 3 to 6 | 4 | AAMM | Year and month of issue (YYMM) |
| 7 to 20 | 14 | CNPJ | Issuer CNPJ |
| 21 to 22 | 2 | mod | Fiscal document model (55 = NF-e) |
| 23 to 25 | 3 | serie | Invoice series |
| 26 to 34 | 9 | nNF | Invoice number |
| 35 | 1 | tpEmis | Emission type |
| 36 to 43 | 8 | cNF | Random numeric code |
| 44 | 1 | cDV | Check 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
modvalue.
File download links
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.
