TF Fiscal
Documentation

Getting started

Quick start

Register a company, link its certificate, register a webhook, issue an NF-e and query it, the five-step onboarding path of the TF Fiscal Open API.

This guide walks through the five calls every issuing integration makes, in order:

  1. Register the company that will issue documents and obtain its empresaId.
  2. Link the company's A1 digital certificate.
  3. Register a webhook to receive issuance results.
  4. Issue an NF-e.
  5. Query the NF-e for its status and download links.

All requests go to the production base URL https://api.v2.tffiscal.com; the full URL is the base URL plus the path shown for each call. The same five steps apply to CT-e and DC-e: steps 1 to 3 are shared, only the issuing and query endpoints change.

Prerequisites

  • Application credential: an app_secret issued for your application. It is displayed only once; store it securely. If it leaks, request a rotation.
  • API subscription: the platform grants your application access to every endpoint used below. Calling an endpoint you are not subscribed to returns HTTP 403 with code 10009005.
  • Signing: every request carries the token, timestamp and sign headers. The helper below is used by all examples on this page; the full specification is in Authentication. Before the first real call, validate your implementation against Echo test.
bash
HOST="https://api.v2.tffiscal.com"
APP_SECRET="<APP_SECRET>"
# sign = lowercase hex MD5( token + path + body-without-CR-LF + timestamp )
# usage: sign "<path>" "<body>" "<timestamp>" (pass "" as body for GET / DELETE / multipart)
sign() {
printf '%s%s%s%s' "$APP_SECRET" "$1" "$(printf '%s' "$2" | tr -d '\r\n')" "$3" \
| md5sum | awk '{print $1}'
}

Step 1: register the company

POST /openapi/v2/empresas submits the seller's company data. The response carries the empresaId that every later call uses as a path variable; persist it.

bash
API_PATH="/openapi/v2/empresas"
BODY='{"cnpj":"14422279000106","inscricaoEstadual":"999999","razaoSocial":"Empresa teste LTDA","nomeFantasia":"Empresa teste","optanteSimplesNacional":true,"mei":false,"email":"empresa-teste@example.com","telefoneComercial":"6122222222","endereco":{"pais":"Brasil","uf":"MG","cidade":"Belo Horizonte","logradouro":"Rua Teste","numero":"999","bairro":"Bairro Teste","cep":"85100000"},"emissaoNFeProduto":{"ambienteProducao":{"sequencialNFe":1,"serieNFe":"10"}}}'
TS=$(date +%s)
curl -sS -X POST "$HOST$API_PATH" \
-H "Content-Type: application/json" \
-H "token: $APP_SECRET" -H "timestamp: $TS" -H "sign: $(sign "$API_PATH" "$BODY" "$TS")" \
-d "$BODY"
json
{ "empresaId": "1934811222334455" }

Registration places the company in the platform's approval queue. The company can issue only after operations approve it and the certificate is linked; issuing before that returns codigo 10004004. Registering the same CNPJ twice returns HTTP 400 with codigo 10003002. Field-by-field reference: Register company.

POST /openapi/v1/empresas/{empresaId}/certificadoDigital accepts the A1 certificate (.pfx / .p12) either as a multipart/form-data upload or as JSON with the file content in Base64. The JSON shape is used here because its body is signed like every other JSON request. Produce standard Base64 without line breaks so the signed body and the sent body cannot drift apart.

bash
EMPRESA_ID="1934811222334455"
API_PATH="/openapi/v1/empresas/$EMPRESA_ID/certificadoDigital"
BODY=$(printf '{"senha":"certpass123","arquivoBase64":"%s"}' "$(base64 -w0 certificate.pfx)")
TS=$(date +%s)
curl -sS -X POST "$HOST$API_PATH" \
-H "Content-Type: application/json" \
-H "token: $APP_SECRET" -H "timestamp: $TS" -H "sign: $(sign "$API_PATH" "$BODY" "$TS")" \
--data-binary "$BODY"

Success is HTTP 200 with no body, and the upload replaces the company's previous certificate. A wrong password returns HTTP 400 with codigo CER0005; invalid Base64 or a file above 1 MB after decoding returns 10003035; a certificate whose CNPJ does not match the company returns 10003010, and an expired one 10003011. The multipart shape, which signs the empty string as body, is described in Link certificate.

Step 3: register the webhook

POST /openapi/v1/webhooks registers the URL that receives issuance results. The token you choose is sent back verbatim in the token request header of every callback, so your receiver can verify the origin; the platform additionally signs every delivery with the X-Tffiscal-Signature header.

bash
API_PATH="/openapi/v1/webhooks"
BODY='{"uri":"https://example.com/tffiscal/callback","contentType":"application/json","token":"dGt6eXp5ZGRra2tzc3Nra2hoaGFha2tha2FhamFoaGFoNzc3Nz"}'
TS=$(date +%s)
curl -sS -X POST "$HOST$API_PATH" \
-H "Content-Type: application/json" \
-H "token: $APP_SECRET" -H "timestamp: $TS" -H "sign: $(sign "$API_PATH" "$BODY" "$TS")" \
-d "$BODY"
json
{ "webHookId": "550001" }

There is one callback configuration per application; calling the endpoint again overwrites it. Registration subscribes you to the result events, authorized and denied. Your receiver must answer 2xx; anything else is retried with backoff. See Register webhook and Webhooks.

Step 4: issue an NF-e

Once the company is approved, POST /openapi/v2/empresas/{empresaId}/nf-e accepts an issuance request. The id is generated by you and is the key for query, cancellation and idempotency. ambienteEmissao must match the company's current environment; newly registered companies start in Homologacao.

bash
API_PATH="/openapi/v2/empresas/$EMPRESA_ID/nf-e"
BODY='{"id":"NFe-000014553","ambienteEmissao":"Homologacao","pedido":{"presencaConsumidor":"OperacaoPelaInternet","pagamento":{"formas":[{"tipo":"CartaoDeCredito","valor":28.47}]}},"cliente":{"tipoPessoa":"F","nome":"Demo Client","email":"demo.client@mail.com","cpfCnpj":"88533234775","endereco":{"uf":"PR","cidade":"4106902","logradouro":"Rua Presidente Wilson","numero":"911","bairro":"Uberaba","cep":"81570440"}},"itens":[{"cfop":"6403","codigo":"000068","descricao":"Kingston DataTraveler SE9 DTSE9H 16GB USB Drive","ncm":"85235190","ean":"619659000424","quantidade":1,"unidadeMedida":"UN","valorUnitario":28.47,"impostos":{"icms":{"situacaoTributaria":"101"},"pis":{"situacaoTributaria":"49"},"cofins":{"situacaoTributaria":"49"}}}]}'
TS=$(date +%s)
curl -sS -X POST "$HOST$API_PATH" \
-H "Content-Type: application/json" \
-H "token: $APP_SECRET" -H "timestamp: $TS" -H "sign: $(sign "$API_PATH" "$BODY" "$TS")" \
-d "$BODY"

An accepted request returns HTTP 200 with no body and enters the asynchronous issuance flow. The result arrives through the webhook registered in step 3, or through the query in step 5. 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. Full request dictionary: Issue NF-e.

Step 5: query the NF-e

GET /openapi/v2/empresas/{empresaId}/nf-e/{nfeId} returns the current status, the invoice data and, once authorized, the DANFE and XML download links. nfeId is the id you sent in step 4. GET requests sign the empty string as body; the path variables are part of the signed path.

bash
NFE_ID="NFe-000014553"
API_PATH="/openapi/v2/empresas/$EMPRESA_ID/nf-e/$NFE_ID"
TS=$(date +%s)
curl -sS -X GET "$HOST$API_PATH" \
-H "token: $APP_SECRET" -H "timestamp: $TS" -H "sign: $(sign "$API_PATH" "" "$TS")"

The status field moves from AguardandoAutorizacao to Autorizada (or Negada, with a motivoStatus explaining why). Once authorized, linkDanfe and linkDownloadXml can be fetched with a plain GET, see File download. Response reference: Query NF-e.

Verify the failure paths

Each row is a one-line change to the sequence above and confirms your client fails the way you expect:

ChangeExpected result
Wrong token valueHTTP 401, envelope code 10009002 (invalid token)
Change BODY after computing signHTTP 401, envelope code 10009003 (signature mismatch)
Reuse a timestamp older than 300 sHTTP 401, envelope code 10009001 (timestamp)
Call an endpoint your application is not subscribed toHTTP 403, envelope code 10009005 (not subscribed)
Register the same CNPJ twiceHTTP 400, [{"codigo":"10003002", ...}]
Issue with ambienteEmissao set to Producao while the company is in testHTTP 400, [{"codigo":"10004030", ...}]
Query an unknown nfeIdHTTP 404, [{"codigo":"NFe0001", ...}]

Authentication failures use the platform envelope; business failures of the issuing endpoints use an error array. Both shapes are described in General conventions.

Next steps

  • NF-e: cancellation and correction letters (CC-e) on top of the calls above.
  • CT-e and DC-e: the other issuing document types, sharing the same company, certificate and webhook.
  • NF-e verification: verify third-party NF-e documents by XML or access key.
  • Identity lookup: CNPJ and CPF registry lookups.
  • Webhooks: callback payloads, signature verification and retries.
  • Environments: how a company moves from test to production.