Skip to Content
API Reference (English)Verification API

Verification API

The platform performs two-tier verification of third-party NF-e documents (model 55):

TierWhat it doesWhen
Tier 1 (synchronous)Local validation of the XML: syntax, NF-e 4.00 XSD layout, digital signature and certificate ownership, 44-digit access key (chave) structure and consistency with document fields, authorization protocol integrityReturned immediately in the API response
Tier 2 (asynchronous)Real-time authenticity check against SEFAZ (the state tax authority): authorization status, cancellation events, protocol number and digest compared with official recordsRuns after Tier 1 passes; the verdict is delivered via webhook

Two endpoints:

EndpointPurpose
POST /openapi/v3/consultas/nf-e/xmlSubmit the raw NF-e XML; returns the Tier-1 verdict and the parsed invoice, starts Tier 2
GET /openapi/v3/consultas/nf-e/{chave}Pure lookup by access key; returns invoice data and carries no verification verdict

Both require the three signature headers — see Authentication & Signing. Responses are bare (no platform envelope); request-level errors are a bare {code, message} object — see General Conventions.

Verification lifecycle

submit XML ──► Tier 1 ├─ blocking error ──────────────► REJECTED (terminal) └─ pass ──► PENDING_SEFAZ ──► VALIDATING (Tier 2) ├──► VALIDATED (terminal) ├──► REJECTED (terminal, with reason) └──► VALIDATION_ERROR (retryable, with reason)
validationStatusMeaning
VALIDATEDSEFAZ confirms the invoice is authentic and effective (authorized, no cancellation, protocol matches)
REJECTEDVerification failed: a Tier-1 blocking error, or SEFAZ reports the invoice is cancelled / denied / voided / not found / protocol mismatch. reason is provided
VALIDATION_ERRORVerification could not complete (SEFAZ throttling or query failure after retries). Not a judgment on the invoice itself — resubmit later

Onboarding

  1. Application credential — an app_secret, displayed only once.
  2. API subscription — the platform enables your application for both endpoints above.
  3. Webhook endpoint — configure your application-level webhook URL and subscribe to the event invoice.verify.completed, the only push channel for Tier-2 verdicts. When the URL is saved, the platform immediately sends a test delivery with event_type=webhook.verify; your endpoint must return 2xx for the save to succeed (returning 200 without processing is fine for the test event). See Webhooks.

XML verification

Submits the raw NF-e XML. Returns the Tier-1 verdict and the parsed invoice data synchronously; on Tier-1 pass, Tier-2 SEFAZ verification starts automatically (verdict via webhook).

Request

POST /openapi/v3/consultas/nf-e/xml Content-Type: application/xml (or text/xml) token / timestamp / sign see Authentication & Signing forceRevalidate: true (optional header, NOT signed, default false) <request body: raw NF-e XML>

Body requirements:

  • Preferred: the full nfeProc authorized document (including the protNFe protocol node). A bare NFe without the protocol node is also accepted — it produces a warning but is processed normally.
  • Hard limits: max 1 MB, UTF-8 only, DTD forbidden (any <!DOCTYPE is rejected outright).

Signing: the body participates in the signature after stripping every CR and LF, while the request body itself is sent unchanged: sign = md5Hex(app_secret + "/openapi/v3/consultas/nf-e/xml" + xmlWithoutCrLf + timestamp).

Complete worked example:

POST /openapi/v3/consultas/nf-e/xml HTTP/1.1 Host: api.v2.tffiscal.com Content-Type: application/xml token: sk_live_9f8e7d6c5b4a timestamp: 1784906308 sign: 7c1b2a3d4e5f60718293a4b5c6d7e8f9 Language: en <?xml version="1.0" encoding="UTF-8"?><nfeProc xmlns="http://www.portalfiscal.inf.br/nfe" versao="4.00"><NFe>...</NFe><protNFe versao="4.00">...</protNFe></nfeProc>

Equivalent curl:

APP_SECRET="sk_live_9f8e7d6c5b4a" PATH_SIGNED="/openapi/v3/consultas/nf-e/xml" BODY=$(tr -d '\r\n' < invoice.xml) TS=$(date +%s) SIGN=$(printf '%s' "${APP_SECRET}${PATH_SIGNED}${BODY}${TS}" | md5sum | cut -d' ' -f1) curl -X POST "https://api.v2.tffiscal.com${PATH_SIGNED}" \ -H "Content-Type: application/xml" \ -H "token: ${APP_SECRET}" \ -H "timestamp: ${TS}" \ -H "sign: ${SIGN}" \ -H "Language: en" \ --data-binary @invoice.xml

Success response (HTTP 200)

HTTP 200 means the request was accepted — not that the invoice passed. A well-formed XML that fails validation still returns 200; the verdict is in the validation block. Never branch on the HTTP status to decide validity.

{ "tipo": "NF-e", "modelo": "55", "status": "Autorizada", "statusDescription": "Invoice authorized", "ambienteEmissao": "Producao", "numero": "1006122", "serie": "2", "dataEmissao": "2024-11-22T11:20:05-03:00", "chaveAcesso": "35260764962869000108550990001366171195929648", "emitente": { "nome": "EMPRESA EXEMPLO LTDA", "cpfCnpj": "64962869000108", "inscricaoEstadual": "123456789012", "telefone": "1133334444", "endereco": { "uf": "SP", "cidade": "SAO PAULO", "logradouro": "RUA EXEMPLO", "numero": "100", "complemento": null, "bairro": "CENTRO", "cep": "01001000" } }, "destinatario": { "...": "same structure as emitente, plus email" }, "itens": [ { "cfop": "5102", "codigo": "SKU-001", "descricao": "PRODUTO EXEMPLO", "quantidade": 2.0, "valorUnitario": 50.00, "descontos": 0.00, "valorTotal": 100.00 } ], "dataAutorizacao": "2024-11-22T11:20:31-03:00", "protocolo": { "numero": "135240001234567", "digestValue": "oAEE...HwY=" }, "valorTotal": 100.00, "validation": { "fileAccessible": true, "xmlWellFormed": true, "schemaValid": true, "signatureValid": true, "accessKeyValid": true, "protocolPresent": true, "protocolValid": true, "xmlVersion": "4.00", "validationStatus": "PENDING_SEFAZ", "validationStatusDescription": "Tier-1 validation passed, awaiting tax authority verification", "errors": [], "warnings": [] } }

Field reference (top level):

FieldTypePresenceDescription
tipostringalwaysDocument type: NF-e / NFC-e
modelostringalwaysDocument model: 55 / 65
statusstringalwaysSEFAZ fiscal status: Autorizada / Cancelada / Denegada / Inutilizada / NaoEncontrada / Desconhecida
statusDescriptionstringalwaysLocalized description of status
ambienteEmissaostringalwaysIssuing environment: Producao / Homologacao
numerostringalwaysInvoice number nNF, no leading zeros
seriestringalwaysSeries number
dataEmissaostringalwaysIssue time dhEmi — ISO-8601 with UTC offset, verbatim from the document
chaveAcessostringalways44-digit access key
emitentePartyalwaysIssuer — see Party object
destinatarioPartyalwaysRecipient — see Party object
itens[]array of Line itemalwaysInvoice line items — see Line item object
dataAutorizacaostring | nullnullableSEFAZ authorization time dhRecbto (ISO-8601); null when the submitted XML carries no protocol node
protocoloProtocol | nullnullableAuthorization protocol — see Protocol object; null when no protocol node
valorTotalnumberalwaysInvoice net total vNF (after discounts), 2 decimal places
validationValidationalways (XML API only)Tier-1 verdict block — see The validation block

Party object (emitente / destinatario):

FieldTypePresenceDescription
nomestringalwaysLegal name xNome
emailstring | nullnullableE-mail address; populated for the recipient only, when present on the document
cpfCnpjstringalwaysCPF (11 digits) or CNPJ (14 digits), digits only, leading zeros preserved
inscricaoMunicipalstring | nullnullableMunicipal registration IM
inscricaoEstadualstring | nullnullableState tax registration IE; typically null for consumer recipients
telefonestring | nullnullablePhone number fone, digits only
enderecoAddressalwaysSee Address object

Address object (endereco):

FieldTypePresenceDescription
ufstringalwaysState code, 2 letters (e.g. SP)
cidadestringalwaysCity name xMun
logradourostringalwaysStreet xLgr
numerostringalwaysStreet number nro
complementostring | nullnullableAdditional info xCpl (apartment, suite, …)
bairrostringalwaysDistrict xBairro
cepstringalwaysPostal code CEP, 8 digits, no hyphen

Line item object (itens[] element):

FieldTypePresenceDescription
cfopstringalwaysCFOP operation code, 4 digits
codigostringalwaysSeller product code cProd (SKU)
descricaostringalwaysProduct description xProd
quantidadenumberalwaysQuantity qCom, up to 4 decimal places
valorUnitarionumberalwaysUnit price vUnCom, up to 10 decimal places
descontosnumberalwaysLine discount vDesc; 0.00 when absent on the document
valorTotalnumberalwaysGross line amount vProd = quantidade × valorUnitario, before discount

Protocol object (protocolo):

FieldTypePresenceDescription
numerostringalwaysAuthorization protocol number nProt, 15 digits
digestValuestringalwaysAuthorization digest digVal, Base64

Amount semantics: line-level itens[].valorTotal is the gross line amount (vProd, before discount); root-level valorTotal is the net invoice total (vNF, after discount). The difference equals the total discount.

The validation block

FieldTypePresenceDescription
fileAccessiblebooleanalwaysXML content received and readable (always true for direct body submission; compatibility field)
xmlWellFormedbooleanalwaysXML syntax is valid
schemaValidbooleanalwaysConforms to the official NF-e 4.00 XSD layout
signatureValidbooleanalwaysIssuer digital signature valid: digest, signature value, certificate validity period and CNPJ match
accessKeyValidbooleanalways44-digit key structure and check digit valid, and consistent with document fields
protocolPresentbooleanalwaysA protNFe authorization node was present
protocolValidboolean | nullnullableProtocol structurally consistent with the invoice (local check; authenticity is decided by Tier 2). null when no protocol node
xmlVersionstringalwaysLayout version from infNFe@versao (e.g. 4.00)
validationStatusstringalwaysSynchronous response carries PENDING_SEFAZ (passed, Tier 2 queued) or REJECTED (blocking, terminal). Full enum incl. asynchronous states: VALIDATING / VALIDATED / VALIDATION_ERROR
validationStatusDescriptionstringalwaysLocalized description of validationStatus
errors[]array of Issuealways (may be empty)Blocking findings — any entry means REJECTED
warnings[]array of Issuealways (may be empty)Non-blocking findings (e.g. missing protocol node)

Issue object (errors[] / warnings[] element):

FieldTypePresenceDescription
codestringalwaysStable enum code — see Tier-1 validation errors. Branch on this, never on message
fieldstring | nullnullableLocates the offending element (e.g. Signature, chNFe)
messagestringalwaysHuman-readable explanation, localized

Chave lookup

Returns invoice data for a 44-digit access key. Pure query endpoint: the response carries no validation block; verification verdicts are delivered exclusively via webhook.

Request

GET /openapi/v3/consultas/nf-e/{chave} token / timestamp / sign (GET → body is the empty string; the chave is part of the signed path)

Complete worked example. Given:

app_secret = sk_live_9f8e7d6c5b4a chave = 35260764962869000108550990001366171195929648 timestamp = 1784906308

Step 1 — build the signing string:

sign_input = "sk_live_9f8e7d6c5b4a" + "/openapi/v3/consultas/nf-e/35260764962869000108550990001366171195929648" + "" + "1784906308" sign = md5Hex(sign_input) e.g. → 3f2a9c1d8b7e6f5a4c3d2e1f0a9b8c7d

Step 2 — send the request:

GET /openapi/v3/consultas/nf-e/35260764962869000108550990001366171195929648 HTTP/1.1 Host: api.v2.tffiscal.com token: sk_live_9f8e7d6c5b4a timestamp: 1784906308 sign: 3f2a9c1d8b7e6f5a4c3d2e1f0a9b8c7d Language: en

Equivalent curl:

APP_SECRET="sk_live_9f8e7d6c5b4a" CHAVE="35260764962869000108550990001366171195929648" TS=$(date +%s) SIGN=$(printf '%s' "${APP_SECRET}/openapi/v3/consultas/nf-e/${CHAVE}${TS}" | md5sum | cut -d' ' -f1) curl -X GET \ "https://api.v2.tffiscal.com/openapi/v3/consultas/nf-e/${CHAVE}" \ -H "token: ${APP_SECRET}" \ -H "timestamp: ${TS}" \ -H "sign: ${SIGN}" \ -H "Language: en"

Data sources and outcomes

The platform resolves the chave through the following tiers; the first hit is returned:

CaseResult
Previously submitted for verification (platform retains the record)Full invoice data
Invoice issued through this platformFull invoice data parsed from the authorized XML
Unknown chaveFull authorized data fetched from the official data source, per platform policy
No source hitHTTP 400 — { "code": 10015004, "message": "Invoice not found" }
Malformed chave / bad check digitHTTP 400 — { "code": 10015104, ... }; no lookup is attempted

Success response (HTTP 200)

{ "tipo": "NF-e", "modelo": "55", "status": "Autorizada", "statusDescription": "Invoice authorized", "ambienteEmissao": "Producao", "numero": "136617", "serie": "99", "dataEmissao": "2026-07-23T11:20:05-03:00", "chaveAcesso": "35260764962869000108550990001366171195929648", "emitente": { "nome": "EMPRESA EXEMPLO LTDA", "email": null, "cpfCnpj": "64962869000108", "inscricaoMunicipal": null, "inscricaoEstadual": "123456789012", "telefone": "1133334444", "endereco": { "uf": "SP", "cidade": "SAO PAULO", "logradouro": "RUA EXEMPLO", "numero": "100", "complemento": null, "bairro": "CENTRO", "cep": "01001000" } }, "destinatario": { "nome": "COMPRADOR EXEMPLO", "email": "buyer@example.com", "cpfCnpj": "39053344705", "inscricaoMunicipal": null, "inscricaoEstadual": null, "telefone": null, "endereco": { "uf": "RJ", "cidade": "RIO DE JANEIRO", "logradouro": "AV RIO BRANCO", "numero": "156", "complemento": "APT 501", "bairro": "CENTRO", "cep": "20040030" } }, "itens": [ { "cfop": "6102", "codigo": "SKU-001", "descricao": "PRODUTO EXEMPLO", "quantidade": 2.0, "valorUnitario": 50.00, "descontos": 0.00, "valorTotal": 100.00 } ], "dataAutorizacao": "2026-07-23T11:20:31-03:00", "protocolo": { "numero": "135260001234567", "digestValue": "oAEEuC3tGmb2W7ZJxWkNVWuBHwY=" }, "valorTotal": 100.00 }

The top-level fields are the same as for XML verification, minus the validation block; chaveAcesso echoes the path variable. The nested Party / Address / Line item / Protocol objects are defined once under XML verification and are identical here.

Error responses

Unknown chave (no source hit):

{ "code": 10015004, "message": "Invoice not found" }

Malformed chave (wrong length / character / check digit — rejected locally, no lookup attempted):

{ "code": 10015104, "message": "Access key structure or check digit invalid" }

Authentication failures (401 / 403 / 429) use the platform envelope — see Authentication & Signing.

Receiving the final verdict

When Tier-2 SEFAZ verification settles, the platform POSTs the invoice.verify.completed event to your configured webhook URL. This is the only push channel for final verdicts; the chave lookup can serve as a polling fallback. Headers, payload and receiver requirements are documented in Webhooks — Verification verdict event.

Verdict handling:

validationStatusTerminal?Action
VALIDATEDYesSafe to proceed (release goods, settle, etc.)
REJECTEDYesDo not proceed; reason explains the SEFAZ verdict (cancelled / denied / voided / not found / protocol mismatch)
VALIDATION_ERRORNoPlatform-side verification failure, not an invoice judgment; resubmit later with forceRevalidate: true

Idempotency and re-verification

  • XML verification is idempotent on the chave: resubmitting while a verification is in progress returns the current progress; terminal verdicts are reused for 24 hours (no duplicate verification cost).
  • Forced re-verification: send header forceRevalidate: true on the XML endpoint (not part of the signature). The chave lookup has a fixed GET shape with no re-verification channel — to force a re-check, resubmit through the XML endpoint.
  • A Tier-1 blocking REJECTED has no Tier-2 record; re-verification requires resubmitting the XML.

Error reference

Request-level errors — XML endpoint (HTTP 400, bare shape)

The request never enters validation.

codeMeaningAction
10015000Empty request bodySend the XML in the body
10015001Body exceeds 1 MBAn authentic single NF-e never exceeds this; check that you are not wrapping or double-encoding
10015002DTD detected (<!DOCTYPE)Strip DTDs; they are rejected as an XXE safeguard
10015003Encoding is not UTF-8Convert to UTF-8 before submitting

Request-level errors — chave lookup (HTTP 400, bare shape)

codeMeaningAction
10015104Malformed chave (length / characters / check digit)Validate locally first: 44 digits; the last digit is a mod-11 check digit
10015004Invoice not found in any sourceThe chave is unknown to the platform and the official data source; verify the chave with its issuer

Tier-1 validation errors

Returned with HTTP 200 inside validation.errors[]. These are not transport errors: the request succeeded, the document failed. All are blocking (terminal REJECTED) except PROTOCOL_MISSING, which is a warning.

errors[].codeNumericMeaning
XML_MALFORMED10015100XML syntax invalid
XSD_INVALID10015101Does not conform to the NF-e 4.00 XSD layout
SIGNATURE_INVALID10015102Digital signature verification failed (content tampered, or certificate expired at signing time)
SIGNATURE_CERT_MISMATCH10015103Signing certificate CNPJ does not match the issuer
ACCESS_KEY_INVALID10015104chave structure / check digit invalid
ACCESS_KEY_MISMATCH10015105chave segments do not match the document fields
PROTOCOL_MISMATCH10015106Protocol block inconsistent with the document
PROTOCOL_MISSING10015107No protocol node (warning, non-blocking)
XML_VERSION_UNSUPPORTED10015108Layout version is not 4.00

HTTP status quick reference

HTTPScenarioBody shape
200Request accepted — including failed validation (check the validation block)Bare data
400Request itself invalid (empty / oversized / DTD / encoding / malformed chave / not found)Bare {code, message}
401Authentication failed (token / sign / timestamp)Platform envelope
403Application disabled / not effective / integrator disabled / not subscribedPlatform envelope
429Rate limit exceededPlatform envelope
5xxPlatform-side faultRetry with backoff; if persistent, contact the platform with the failing timestamp and path

Integration checklist

  1. Obtain the app_secret; cross-check your signing implementation against the sign-helper output (once for POST with an XML body, once for GET with an empty body).
  2. POST /openapi/v3/consultas/nf-e/xml with a genuine authorized nfeProc → HTTP 200, all five validation checks true, validationStatus=PENDING_SEFAZ.
  3. Receive the invoice.verify.completed webhook: signature verifies, deduplicated by event_id, verdict VALIDATED.
  4. Chave lookup: a previously submitted chave → 200 with invoice data; a nonexistent chave → 400 + 10015004; a chave with a wrong check digit → 400 + 10015104.
  5. Negative cases: submit a tampered XML (SIGNATURE_INVALID); submit an XML without protNFe (warning only, still accepted).
  6. Idempotency: resubmit the same chave → reused verdict; submit with forceRevalidate: true → re-verification, new verdict via webhook.
  7. Failure paths: call with a wrong sign → 401 (code 10009003); call an unsubscribed endpoint → 403 (code 10009005).

Troubleshooting

HTTP 200 but the invoice is fake? Tier 1 only judges whether the XML is internally consistent. Authenticity is decided by Tier 2 against SEFAZ and delivered via webhook — gate your business action (release, settlement) on the webhook VALIDATED, never on the synchronous response alone.

Is VALIDATION_ERROR a problem with the invoice? No. It means the platform’s verification channel failed (e.g. SEFAZ throttling); the invoice itself was not judged. Resubmit later with forceRevalidate: true.

Signature never matches (401, code 10009003)? The most frequent cause on this endpoint is CR/LF not stripped from the XML before concatenation, followed by a GET that concatenated "null" instead of the empty string. The full checklist is in Authentication & Signing.

Last updated on