SAT validations
A SAT validation (SAT: Mexico's tax authority, Servicio de Administración Tributaria) checks a CFDI (Mexico's electronic invoice) that has been stamped (stamping: the certification by a SAT-authorized provider that makes a CFDI valid) against the SAT's official services. It checks the XML structure, the validity period of the issuer's certificate, the CFDI status, and two seals: the CFDI's digital seal (the issuer's signature over the CFDI, made with its CSD) and the SAT seal (the seal inside the digital tax stamp that certifies the CFDI). It also checks Lista 69-B (the SAT list of taxpayers presumed to issue invoices for nonexistent transactions, under Article 69-B of the Federal Tax Code) 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). Each check is a validation type that you request independently.
Each type you request uses one validation credit. The charge is all or nothing: if the balance doesn't
cover every type you requested, none of them runs and the API returns 403. Validation credits are separate
from stamps (one credit each, used each time you stamp a CFDI). Their balance is in the
availableValidationBalance field of each person (any individual or legal entity registered in
your tenant), and you transfer them through the stamps endpoint with creditType: 2.
Validation type model
The validation type model (SatValidationType) describes one of the checks Fiscalapi can run on a CFDI. It's
a read-only catalog of seven types.
Properties
- Name
id- Type
- string?
- Description
The identifier of the validation type.
- Type
- enum:
- Values
- "sat.xml.structure""sat.certificate.validity""sat.cfdi.sello"
- Name
description- Type
- string?
- Description
A description of what the validation type checks.
Result model
The result model (SatValidationResult) is what running a validation returns: one element for each type you
requested, always in catalog order.
Properties
- Name
type- Type
- object (SatValidationType)?
The validation type that ran.
- Name
id- Type
- string?
- Description
The identifier of the validation type.
- Name
description- Type
- string?
- Description
A description of what the validation type checks.
- Name
status- Type
- object (SatValidationStatus)?
The status the validation returned.
- Name
id- Type
- string?
- Description
The identifier of the status. Each validation type accepts only a subset of these values: the one that List a type's statuses returns.
A passing status sets
passedtotrue; a failing one sets it tofalse.- Type
- enum:
- Values
- "Valido""Vigente""NoListado"
- Name
description- Type
- string?
- Description
A description of the status.
- Name
details- Type
- string?
- Description
Free-text details of the specific case, such as the certificate number or the cutoff date of the list that was checked. It can be
null. It's informational: don't parse it in your code.
- Name
passed- Type
- boolean?
- Description
The verdict: whether the status returned is a passing status for that validation type.
Save each response: every new request uses validation credits again.
List validation types
This endpoint returns every active validation type, in catalog order. It takes no parameters and uses no credits.
Request
curl --location 'https://test.fiscalapi.com/api/v4/sat-validations' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": [
{
"id": "sat.xml.structure",
"description": "Verifica mediante el servicio oficial de verificación de CFDI del SAT que el XML sea un documento bien formado y cumpla la estructura del Anexo 20 de su versión y la de los complementos declarados en schemaLocation. Detecta atributos obligatorios ausentes, valores fuera de catálogo y patrones inválidos."
},
{
"id": "sat.certificate.validity",
"description": "Comprueba que la fecha de emisión del CFDI esté comprendida entre NotBefore y NotAfter del certificado del emisor contenido en Certificado. Un CFDI firmado fuera de la vigencia del certificado carece de validez aunque el sello verifique."
},
{
"id": "sat.cfdi.sello",
"description": "Verifica mediante el servicio oficial de verificación de CFDI del SAT que el atributo Sello corresponda a la cadena original del comprobante y a la llave pública del certificado contenido en Certificado. Confirma que el contenido no fue alterado después de firmarse y que el firmante posee la llave privada de ese certificado."
},
{
"id": "sat.tfd.sello",
"description": "Verifica mediante el servicio oficial de verificación de CFDI del SAT que el atributo SelloSAT del TimbreFiscalDigital corresponda al certificado del SAT identificado por NoCertificadoSAT. Confirma que el timbre fue emitido por el SAT a través de un PAC y no fue fabricado ni modificado."
},
{
"id": "sat.cfdi.status",
"description": "Consulta ConsultaCFDIService del SAT con UUID, RFC emisor, RFC receptor y total, y reporta el estado actual del comprobante. Es la única validación que refleja cancelaciones posteriores a la emisión."
},
{
"id": "sat.blacklist.69b",
"description": "Busca el RFC del emisor en el listado completo del artículo 69-B CFF y devuelve la situación vigente. Definitivo implica que los CFDI del emisor no producen efectos fiscales y activa el plazo de 30 días hábiles para el receptor."
},
{
"id": "sat.blacklist.69bbis",
"description": "Busca el RFC del emisor en el listado del artículo 69-B Bis CFF (transmisión indebida del derecho a disminuir pérdidas fiscales) y devuelve la situación vigente. Relevante en operaciones con partes relacionadas y reestructuras."
}
],
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Get a validation type by ID
This endpoint gets a validation type by its ID. The ID contains periods: send it as is, without encoding it. It returns 404 if the type doesn't exist or is inactive. It uses no credits.
Request
curl --location 'https://test.fiscalapi.com/api/v4/sat-validations/sat.xml.structure' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": {
"id": "sat.xml.structure",
"description": "Verifica mediante el servicio oficial de verificación de CFDI del SAT que el XML sea un documento bien formado y cumpla la estructura del Anexo 20 de su versión y la de los complementos declarados en schemaLocation. Detecta atributos obligatorios ausentes, valores fuera de catálogo y patrones inválidos."
},
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
List a type's statuses
This endpoint returns the statuses a validation type can have when it runs: the passing ones first and,
within each group, sorted alphabetically by id. It's read-only: it doesn't run the validation or use
credits. The response doesn't include the passed field; that mapping is in the
result model. It returns 404 if the type doesn't exist or is inactive.
Request
curl --location 'https://test.fiscalapi.com/api/v4/sat-validations/sat.xml.structure/statuses' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'X-API-KEY: <api_key>'
Response
{
"data": [
{
"id": "Valido",
"description": "Documento bien formado que cumple el XSD del Anexo 20 de su versión y los XSD de los complementos declarados en schemaLocation, según el verificador del SAT."
},
{
"id": "Invalido",
"description": "El documento no está bien formado o el verificador del SAT reporta que incumple el XSD del Anexo 20 o el de alguno de sus complementos."
},
{
"id": "NoDisponible",
"description": "El verificador del SAT no respondió o rechazó la consulta; la estructura no pudo determinarse y debe reintentarse."
},
{
"id": "Omitido",
"description": "No se evaluó porque el CFDI no contiene el complemento TimbreFiscalDigital; el verificador del SAT solo evalúa comprobantes timbrados."
}
],
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Validate
This endpoint runs the validations you request and returns one result for each type. The results come back in catalog order, not in the order you requested them.
Request body
- Name
xml- Type
- string?
- Description
The complete stamped CFDI, encoded in Base64. Mutually exclusive with
tin. Withxml, you can request any validation type. The Lista 69-B and Lista 69-B Bis checks look up the RFC in thecfdi:Emisornode. Maximum 4,000,000 characters; the request body can be up to 10 MB.
- Name
tin- Type
- string?
- Description
The RFC to look up. Mutually exclusive with
xml. It only accepts thesat.blacklist.69bandsat.blacklist.69bbistypes. The RFC isn't checked against any CFDI: the response reflects only the status of the RFC you sent.
- Name
validationTypes- Type
- array of strings
- required
- Description
The validation types to run. Empty or duplicate elements aren't allowed, and every type must exist and be active.
- Type
- enum:
- Values
- "sat.xml.structure""sat.certificate.validity""sat.cfdi.sello"
In the test environment, the results are fictitious and random. On
https://test.fiscalapi.com, the API doesn't query the SAT or its verifier, and it doesn't even evaluate
the XML you send: it returns a status picked at random from the ones that type accepts. That's why two
identical calls give different results and details is always null.
You also won't see NoDisponible or Omitido, even if you send an invalid or unstamped XML. The
credits are charged all the same.
This environment is only for learning the contract and testing your integration: never use it as a
source of truth for tax purposes or base a business decision on its results. For that, use production. The
details you see in the examples on this page come from production.
Send exactly one of xml or tin, never both or neither. An adverse result isn't an error: if the
validation ran, the response is 200 with passed: false.
Each type you request uses one validation credit, even when the result is NoDisponible or
Omitido. A malformed request returns 400 and uses no credits. An insufficient balance returns 403 and
doesn't use them either, because the charge happens after the request is validated. If the run fails
because of a technical problem (a 500 error), the debit is refunded automatically as a rollback transaction
in the stamps transaction log. However, if you cancel the request or the connection closes, the
charge stands and you get no results: by then, the SAT queries have already been paid for.
An xml that isn't a stamped CFDI isn't a 400 error either: the response is 200, every credit you
requested is charged, and each type reports what it could determine.
The table shows the status of each type with an XML that is malformed or whose root isn't
cfdi:Comprobante, and with a readable CFDI that isn't stamped. In the first case, the details of
sat.xml.structure carries the XML parser's error.
| Type | Malformed XML | Unstamped CFDI |
|---|---|---|
sat.xml.structure | Invalido | Omitido |
sat.certificate.validity | Omitido | Evaluated |
sat.cfdi.sello | Omitido | Omitido |
sat.tfd.sello | Omitido | Omitido |
sat.cfdi.status | Omitido | Omitido |
sat.blacklist.69b | Omitido | Evaluated |
sat.blacklist.69bbis | Omitido | Evaluated |
See errors and the response model.
Request
curl --location 'https://test.fiscalapi.com/api/v4/sat-validations' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <api_key>' \
--data-raw '{
"xml": "<xml-timbrado-en-base64>",
"validationTypes": [
"sat.xml.structure",
"sat.certificate.validity",
"sat.cfdi.sello",
"sat.tfd.sello",
"sat.cfdi.status",
"sat.blacklist.69b",
"sat.blacklist.69bbis"
]
}'
Response
{
"data": [
{
"type": {
"id": "sat.xml.structure",
"description": "Verifica mediante el servicio oficial de verificación de CFDI del SAT que el XML sea un documento bien formado y cumpla la estructura del Anexo 20 de su versión y la de los complementos declarados en schemaLocation. Detecta atributos obligatorios ausentes, valores fuera de catálogo y patrones inválidos."
},
"status": {
"id": "Valido",
"description": "Documento bien formado que cumple el XSD del Anexo 20 de su versión y los XSD de los complementos declarados en schemaLocation, según el verificador del SAT.",
"details": null
},
"passed": true
},
{
"type": {
"id": "sat.certificate.validity",
"description": "Comprueba que la fecha de emisión del CFDI esté comprendida entre NotBefore y NotAfter del certificado del emisor contenido en Certificado. Un CFDI firmado fuera de la vigencia del certificado carece de validez aunque el sello verifique."
},
"status": {
"id": "Vigente",
"description": "La fecha de emisión del CFDI está comprendida entre NotBefore y NotAfter del certificado del emisor.",
"details": "Emisión 2026-08-18T11:56:42 (hora de México); certificado 00001000000720012093 de MAX0611157H8 vigente del 2025-11-04 al 2029-11-04"
},
"passed": true
},
{
"type": {
"id": "sat.cfdi.sello",
"description": "Verifica mediante el servicio oficial de verificación de CFDI del SAT que el atributo Sello corresponda a la cadena original del comprobante y a la llave pública del certificado contenido en Certificado. Confirma que el contenido no fue alterado después de firmarse y que el firmante posee la llave privada de ese certificado."
},
"status": {
"id": "Valido",
"description": "El verificador del SAT confirma que el atributo Sello verifica contra la cadena original del comprobante con la llave pública del certificado del emisor.",
"details": null
},
"passed": true
},
{
"type": {
"id": "sat.tfd.sello",
"description": "Verifica mediante el servicio oficial de verificación de CFDI del SAT que el atributo SelloSAT del TimbreFiscalDigital corresponda al certificado del SAT identificado por NoCertificadoSAT. Confirma que el timbre fue emitido por el SAT a través de un PAC y no fue fabricado ni modificado."
},
"status": {
"id": "Valido",
"description": "El verificador del SAT confirma que el atributo SelloSAT verifica contra la cadena original del TimbreFiscalDigital con el certificado del SAT identificado por NoCertificadoSAT.",
"details": null
},
"passed": true
},
{
"type": {
"id": "sat.cfdi.status",
"description": "Consulta ConsultaCFDIService del SAT con UUID, RFC emisor, RFC receptor y total, y reporta el estado actual del comprobante. Es la única validación que refleja cancelaciones posteriores a la emisión."
},
"status": {
"id": "Vigente",
"description": "ConsultaCFDIService reporta el comprobante como Vigente; se incluyen EsCancelable y EstatusCancelacion devueltos por el SAT.",
"details": "Estado Vigente; CodigoEstatus S - Comprobante obtenido satisfactoriamente.; EsCancelable Cancelable con aceptación; EstatusCancelacion "
},
"passed": true
},
{
"type": {
"id": "sat.blacklist.69b",
"description": "Busca el RFC del emisor en el listado completo del artículo 69-B CFF y devuelve la situación vigente. Definitivo implica que los CFDI del emisor no producen efectos fiscales y activa el plazo de 30 días hábiles para el receptor."
},
"status": {
"id": "NoListado",
"description": "El RFC del emisor no figura en ninguna etapa del listado completo del artículo 69-B al corte indicado.",
"details": "RFC MAX0611157H8; corte del listado 2026-07-31"
},
"passed": true
},
{
"type": {
"id": "sat.blacklist.69bbis",
"description": "Busca el RFC del emisor en el listado del artículo 69-B Bis CFF (transmisión indebida del derecho a disminuir pérdidas fiscales) y devuelve la situación vigente. Relevante en operaciones con partes relacionadas y reestructuras."
},
"status": {
"id": "NoListado",
"description": "El RFC del emisor no figura en ninguna etapa del listado del artículo 69-B Bis al corte indicado.",
"details": "RFC MAX0611157H8; corte del listado 2026-06-05"
},
"passed": true
}
],
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Validate by RFC only
When you only need to know whether an RFC is on the SAT's Lista 69-B and Lista 69-B Bis, send tin
instead of xml. It's the same endpoint and the same model.
In this mode, only sat.blacklist.69b and sat.blacklist.69bbis are accepted: requesting any other
type returns a 400 error. Each type you request uses one credit, as with XML validation, and the same
warning as in Validate applies: in the test environment, the result is fictitious and random,
and you shouldn't use it to decide anything.
Request
curl --location 'https://test.fiscalapi.com/api/v4/sat-validations' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <api_key>' \
--data-raw '{
"tin": "XAXX010101000",
"validationTypes": [
"sat.blacklist.69b",
"sat.blacklist.69bbis"
]
}'
Response
{
"data": [
{
"type": {
"id": "sat.blacklist.69b",
"description": "Busca el RFC del emisor en el listado completo del artículo 69-B CFF y devuelve la situación vigente. Definitivo implica que los CFDI del emisor no producen efectos fiscales y activa el plazo de 30 días hábiles para el receptor."
},
"status": {
"id": "NoListado",
"description": "El RFC del emisor no figura en ninguna etapa del listado completo del artículo 69-B al corte indicado.",
"details": "RFC XAXX010101000; corte del listado 2026-07-31"
},
"passed": true
},
{
"type": {
"id": "sat.blacklist.69bbis",
"description": "Busca el RFC del emisor en el listado del artículo 69-B Bis CFF (transmisión indebida del derecho a disminuir pérdidas fiscales) y devuelve la situación vigente. Relevante en operaciones con partes relacionadas y reestructuras."
},
"status": {
"id": "NoListado",
"description": "El RFC del emisor no figura en ninguna etapa del listado del artículo 69-B Bis al corte indicado.",
"details": "RFC XAXX010101000; corte del listado 2026-06-05"
},
"passed": true
}
],
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}
Transfer validation credits
Transfer validation credits between people through the stamps endpoint with creditType set
to 2; with 1, the default, you transfer stamps. Stamp and validation balances never mix.
Request body
- Name
fromPersonId- Type
- string
- required
- Description
The ID of the source person, who sends the credits. That person must have enough validation credits.
- Name
toPersonId- Type
- string
- required
- Description
The ID of the destination person, who receives the credits.
- Name
amount- Type
- integer | number
- required
- Description
The number of credits to transfer. It must be greater than zero.
- Name
comments- Type
- string?
- Description
Optional comments about the transfer. Maximum 100 characters.
- Name
creditType- Type
- integer | number?
- expandable
- Description
The credit type to transfer.
- Type
- enum:
- Values
- 12
Default:1
Check the resulting balance in availableValidationBalance on the people resource; the stamp
balance is in availableBalance.
Request
curl --location 'https://test.fiscalapi.com/api/v4/stamps' \
--header 'X-TENANT-KEY: <tenant_key>' \
--header 'X-TIME-ZONE: America/Mexico_City' \
--header 'Content-Type: application/json' \
--header 'X-API-KEY: <api_key>' \
--data-raw '{
"fromPersonId": "1",
"toPersonId": "bef56254-0892-4558-95c3-f9c8729e4b0e",
"amount": 10,
"comments": "Créditos para validar CFDI",
"creditType": 2
}'
Response
{
"data": true,
"succeeded": true,
"message": "",
"details": "",
"httpStatusCode": 200
}