SAT validations
Stamping (the certification by a SAT-authorized provider that makes a CFDI valid) doesn't, on its own, make a CFDI (Mexico's electronic invoice) deductible. It may have been canceled after delivery, signed with a certificate that had already expired, or altered after it was signed. Its issuer may also appear on Lista 69-B (the SAT list of taxpayers presumed to issue invoices for nonexistent transactions, under Article 69-B of the Federal Tax Code). None of that shows on the PDF that reaches accounts payable. A SAT validation (SAT: Mexico's tax authority, Servicio de Administración Tributaria) checks the CFDI against the official sources at any time after it's issued. Fiscalapi runs seven independent criteria in a single call and returns a verdict for each one.
This page explains how SAT validations work. The full reference for the endpoints, the models, and the examples in each language is in SAT validations.
Use cases
| Use case | What it solves |
|---|---|
| Accounts payable | Validate each supplier CFDI when you receive it, and stop payment on the CFDIs that have no tax effect before you record a liability. |
| After-the-fact reconciliation | Detect the cancellations that happened after the CFDI was delivered and recorded. |
| Supplier compliance | Check your supplier list against Lista 69-B and Lista 69-B Bis (the SAT list of taxpayers involved in a presumed improper transfer of the right to offset tax losses, under Article 69-B Bis of the Federal Tax Code) on a recurring basis, with the cutoff date of each query. |
| Document integrity | Confirm that the stored XML is the same one that was signed and that its structure complies with Anexo 20 (the SAT technical standard that defines the CFDI: its XML structure, catalogs, and validation rules) before it enters your ERP. |
| Audit | Keep the verdict of each criterion as support for the deduction or the IVA credit (acreditamiento, offsetting the VAT you paid against the VAT you charge). |
Validating isn't stamping
Stamping certifies a CFDI when it's issued. Validation checks, at a later point, that the CFDI is still intact, active, and effective for tax purposes. They're two separate operations with separate balances that never mix: stamping uses stamps (one credit each, used each time you stamp a CFDI) and validation uses validation credits. See stamps.
Legal framework
| Regulation | What it governs |
|---|---|
| CFF (Código Fiscal de la Federación, Mexico's Federal Tax Code), Articles 29 and 29-A | Requirements for issuing CFDIs. |
| Anexo 20 of the Resolución Miscelánea Fiscal (the SAT's annual tax rules) | Structure of the CFDI XML and its complements (complemento, XML extensions the SAT defines for specific operations). |
| CFF, Article 69-B | Presumed nonexistent transactions (EFOS, Empresas que Facturan Operaciones Simuladas, taxpayers that issue invoices for simulated transactions). Once the final list is published, the issuer's CFDIs have no tax effect and never had any, and the recipient has 30 business days from publication in the DOF (Diario Oficial de la Federación, Mexico's official gazette) to prove that the transaction took place or to correct its tax situation. |
| CFF, Article 69-B Bis | Improper transfer of the right to offset tax losses. |
How it works
Send the CFDI and the list of criteria you want to run. Fiscalapi queries the official source of each criterion, charges one credit per requested criterion, and returns one result for each:
POST /api/v4/sat-validations
├── xml (CFDI timbrado en Base64) → admite los siete criterios
└── tin (RFC) → solo sat.blacklist.69b y sat.blacklist.69bbis
└── validationTypes[] → resultado por criterio (type, status, passed)
Results come back in catalog order, not in the order you requested them. There's no pending status and nothing to poll: the response to the call is the final result.
Request parameters
The request body carries two things: what to check and which criteria to run on it.
| Field | Type | Required | What it is |
|---|---|---|---|
xml | string | One of the two | The full stamped CFDI, Base64-encoded |
tin | string | One of the two | The RFC (Mexico's taxpayer ID, Registro Federal de Contribuyentes) to look up |
validationTypes | array of strings | Yes | The IDs of the criteria to run, with no empty or duplicate values |
What to check: xml or tin
xml and tin are mutually exclusive: the request must include exactly one of them. Sending both, or
neither, returns a 400 error. What you send determines which criteria you can request.
With xml, you check a specific CFDI and can request all seven criteria. For Lista 69-B and Lista 69-B
Bis, Fiscalapi looks up the RFC in the cfdi:Emisor node of the CFDI itself.
With tin, you only look up the status of that RFC on the lists, so only sat.blacklist.69b and
sat.blacklist.69bbis are accepted. Requesting any other criterion returns a 400 error. The RFC isn't
matched against any CFDI: the response reflects only what the lists say about that RFC. Use it to check a
supplier when you don't have an invoice from them to validate yet.
Limits
xml accepts up to 4,000,000 characters, and the request body up to 10 MB. The tin must have a valid RFC
format.
The seven validation criteria
| # | Criterion | ID | Requested with |
|---|---|---|---|
| 1 | XML structure | sat.xml.structure | xml |
| 2 | Certificate validity | sat.certificate.validity | xml |
| 3 | Issuer's digital seal (the issuer's signature over the CFDI, made with its CSD) | sat.cfdi.sello | xml |
| 4 | SAT seal (the seal inside the digital tax stamp that certifies the CFDI) | sat.tfd.sello | xml |
| 5 | CFDI status | sat.cfdi.status | xml |
| 6 | Lista 69-B | sat.blacklist.69b | xml or tin |
| 7 | Lista 69-B Bis | sat.blacklist.69bbis | xml or tin |
XML structure
Uses the SAT's official CFDI verification service to check that the document is well formed and complies
with Anexo 20 for its version and with the schemas of the complements declared in schemaLocation. It
detects missing required attributes, values outside the SAT catalogs, and invalid patterns.
Certificate validity
Checks that the issue date of the CFDI falls between the NotBefore and NotAfter dates of the issuer's
certificate. A CFDI signed outside the validity period of its certificate isn't valid, even if its digital
seal is.
Issuer's digital seal
Checks that the Sello attribute matches the original string (cadena original, the sequence of CFDI data
that the digital seal signs) of the CFDI and the public key of the declared certificate. It confirms that the
content wasn't altered after it was signed and that the signer holds the private key of that certificate.
SAT seal
Checks that the SelloSAT attribute of the digital tax stamp (Timbre Fiscal Digital, the SAT seal the PAC
adds to a CFDI) matches the SAT certificate identified by NoCertificadoSAT. It confirms that the SAT issued
the stamp through a PAC (Proveedor Autorizado de Certificación, a SAT-authorized provider that stamps CFDIs)
and that the stamp wasn't forged or modified.
CFDI status
Queries the SAT's CFDI lookup service and reports the current status of the CFDI. It's the only criterion that reflects cancellations made after issue: a CFDI with all its seals correct can still be canceled.
Lista 69-B
Looks up the issuer's RFC in the full Lista 69-B and returns its current status as of the queried cutoff.
It's the criterion with the most nuance, because it covers every stage of the procedure: Presunto is a
risk alert in which the CFDIs still have tax effect, while Definitivo means they have none and starts the
30-business-day period for the recipient.
Lista 69-B Bis
Looks up the issuer's RFC in Lista 69-B Bis, which covers the improper transfer of the right to offset tax losses. It's relevant in related-party transactions and corporate restructurings.
Statuses of each criterion
Each criterion accepts only some statuses. A passing status returns passed: true.
| Status | Verdict | Criteria that return it |
|---|---|---|
Valido | Passing | XML structure, issuer's digital seal, and SAT seal |
Vigente | Passing | Certificate validity and CFDI status |
NoListado | Passing | Lista 69-B and Lista 69-B Bis |
Desvirtuado | Passing | Lista 69-B |
SentenciaFavorable | Passing | Lista 69-B and Lista 69-B Bis |
Invalido | Failing | XML structure, issuer's digital seal, and SAT seal |
Expirado | Failing | Certificate validity |
NoVigenteAun | Failing | Certificate validity |
Cancelado | Failing | CFDI status |
NoEncontrado | Failing | CFDI status |
Presunto | Failing | Lista 69-B |
Definitivo | Failing | Lista 69-B and Lista 69-B Bis |
NoDisponible | Failing | All except certificate validity |
Omitido | Failing | All |
How to read a result
Each item in the response has three parts:
| Field | What it is |
|---|---|
passed | The binary verdict of the criterion: true if the status returned is a passing status for that criterion. |
status.id | The specific status that produced that verdict. It's the value worth logging, because it tells apart cases that passed groups together. |
status.details | Free-form informational text with the facts of the case: the RFC and the cutoff date of the list, the certificate number, the SAT's response. It can be null, and your code must not parse it. |
For example, Presunto and Definitivo both return passed: false, but they don't mean the same thing. An
adverse verdict isn't an error: if the validation ran, the response is 200 even if the criterion doesn't pass.
Two statuses apply across criteria. They don't describe a tax fact, but how the criterion ran:
| Status | What it means | What to do |
|---|---|---|
NoDisponible | The official source didn't respond, or there's no imported cutoff of the list. The criterion couldn't be determined. | Retry later |
Omitido | The input didn't allow that criterion to be evaluated, for example a CFDI without a digital tax stamp. | Check the CFDI you sent |
An XML that isn't a stamped CFDI doesn't return a 400 error either. A malformed XML returns Invalido for
the structure and Omitido for the rest. A readable but unstamped CFDI returns Omitido for the structure,
both seals, and the status, while the certificate validity and the lists are still evaluated. In both cases,
all the requested credits are charged.
Validation credits
Validations are charged in validation credits, a balance separate from the stamp balance.
- Fiscalapi charges one credit per requested criterion, not one per call: requesting all seven costs seven.
- You're charged even if the verdict is
passed: false, and even if the status isNoDisponibleorOmitido. - The charge is all or nothing and happens before execution: if the balance doesn't cover every requested criterion, none of them runs, nothing is used, and the API returns 403.
- The catalog endpoints (list the criteria, get one by its ID, and list its statuses) are read-only and don't use credits.
| Scenario | HTTP | Uses credits? |
|---|---|---|
Full run, even if a criterion returns passed: false | 200 | Yes, one per criterion |
NoDisponible or Omitido status | 200 | Yes |
Malformed request: xml and tin both missing or both present, invalid Base64, unknown criterion | 400 | No |
| Insufficient balance | 403 | No |
| Nonexistent or inactive criterion on the catalog endpoints | 404 | No |
| Technical failure during execution | 500 | Refunded automatically |
| You cancel the request or the connection closes | No response | Yes, the charge stands |
Balance and top-up
The available balance of each person (any individual or legal entity registered in your tenant) is in
availableValidationBalance of the people resource; availableBalance is the stamp balance. In
the dashboard, they appear as Validaciones disponibles (Available validations)
and Timbres disponibles (Available stamps).
Buy the credits in Tienda en línea (Store) in the dashboard. Then distribute
them among the people in your tenant (the space in Fiscalapi that holds your issuers, recipients, credits, and
data) from Catalogos > Personas (Catalogs > People), or through the API with the
stamps endpoint and creditType: 2. The value 1, which is the default, transfers stamps.
Auditing usage
Each validation leaves a transaction in the same transaction log as stamps, which you query with
List transactions.
It's a consumption transaction, it has creditType: 2, and its comments list the IDs of the charged
criteria, for example SAT: sat.xml.structure,sat.blacklist.69b. This way you reconcile credit spending
criterion by criterion without instrumenting anything in your system.
Error responses also include a traceIdentifier, which is the same as the referenceId of the transaction:
it's the value to cite when you report a problem. See response model and
errors.
Test environment
In the test environment, the results are fictitious and random. At https://test.fiscalapi.com, the
API doesn't query the SAT or evaluate the XML you send: it returns a status picked at random from the ones
that criterion accepts. That's why two identical calls return different results, and details is always null.
You won't see NoDisponible or Omitido either. Credits are charged the same way. Use this environment
only to learn the contract and test your integration: never use it as a source of tax truth or base a
business decision on its results.
See environments for the credentials and the URL of each one.
Available in the SDKs
The five official SDKs expose SAT validations with the same surface: three catalog operations and one execution operation.
| SDK | Accessor |
|---|---|
| .NET | fiscalApi.SatValidations |
| JavaScript / TypeScript | fiscalApi.satValidations |
| Python | fiscalapi.sat_validations |
| Java | fiscalApi.getSatValidationService() |
| PHP | $fiscalApi->getSatValidationService() |
| Operation | .NET | Python | JavaScript / TypeScript, Java, and PHP |
|---|---|---|---|
| List validation types | GetTypesAsync | get_types | getTypes |
| Get a type by its ID | GetTypeByIdAsync | get_type_by_id | getTypeById |
| List the statuses of a type | GetStatusesAsync | get_statuses | getStatuses |
| Validate | ValidateAsync | validate | validate |
All five expose the SatValidationTypeIds and SatValidationStatusIds constants; use them instead of
literals. The examples of each operation in each language are in SAT validations, and the
installation of each package is in SDKs.
Frequently asked questions
What's the difference between validating a CFDI and stamping it? Stamping certifies a CFDI when it's issued. Validation checks, at any later time, that the XML is still intact, active, and effective for tax purposes: seals, certificate, status at the SAT, and the issuer's standing on the Article 69-B lists.
Is a CFDI from an issuer on the final list still valid? It has no tax effect. Once the final Lista 69-B is published, the issuer's CFDIs are deemed, for everyone, to have no tax effect at all, and the recipient has 30 business days to prove that the transaction took place or to correct its tax situation.
How often are Lista 69-B and Lista 69-B Bis updated?
Fiscalapi adds each official SAT publication. Each response from the list criteria includes in details the
cutoff date of the list it queried, so your system knows what information it based its decision on.
Can I run only some criteria?
Yes. validationTypes sets exactly which ones to run, and only those are charged. To check Lista 69-B and
Lista 69-B Bis, send the issuer's RFC alone, without XML.