CFDI Status at the SAT
cfdi_statusCheck if a Mexican CFDI invoice is valid and current by querying the SAT's public status service. Get its status, cancelability, and 69-B validation without credentials.
Instructions
Ask the SAT whether an invoice actually exists and is still live. This queries the public ConsultaCFDIService SOAP endpoint — the same service the QR code printed on every Mexican invoice points at — so it needs no credentials, no CSD and no PAC contract.
Pass either the four values the SAT keys on (issuer RFC, receiver RFC, total, UUID) or the whole xml, in which case they are derived from it with the same reader parse_cfdi uses. Deriving them from the XML is the more reliable route: the total must be formatted exactly the way the printed-representation spec demands (six decimals, trailing zeros trimmed), and a total written in another format can come back as a spurious 'No Encontrado'.
What comes back, each with its meaning spelled out:
Estado — Vigente / Cancelado / No Encontrado.
EsCancelable — whether the issuer can cancel it unilaterally, needs the receiver's approval, or cannot cancel it at all.
EstatusCancelacion — whether a cancellation is in progress, was accepted, was rejected, or lapsed.
ValidacionEFOS — whether the issuer, and any third-party RFC the invoice was issued on behalf of (a cuenta de terceros), appears on the SAT's definitive 69-B list of companies that invoice simulated operations. Read with the code table the SAT documents (service documentation v1.4, section 3): 100, 101 and 104 put the issuer on the list; 102 and 103 mean the issuer is NOT on it but a third-party RFC is; 200 and 201 mean the issuer is not on it (201: nor any third party).
efos_statespeaks of the issuer only andefos_third_party_stateof the third parties; an empty field or an undocumented code isunknown, with the raw code kept invalidacion_efos.CodigoEstatus — the service's own result code.
Fail-soft by design. The SAT publishes no SLA and no status page, and the endpoint does go down; its documentation states capacity for up to 2 million queries per hour and asks callers not to raise their query volume, so query once per invoice and cache the answer. On timeout, refusal or a malformed answer this returns available: false with the reason instead of raising — a failed lookup is a statement about the SAT, never about the invoice. Never report a document as invalid on the strength of an unreachable service. One retry, 10-second timeout.
Args (either shape):
xml (string), OR rfc_emisor + rfc_receptor + total + uuid (all strings).
response_format ('markdown' | 'json'): output format (default 'markdown').
Returns: { available, unavailable_reason, endpoint, expression, attempts, elapsed_ms, source, status{codigo_estatus, query_outcome, estado, document_state, document_meaning, es_cancelable, cancellable_state, cancellable_meaning, estatus_cancelacion, cancellation_state, cancellation_meaning, validacion_efos, efos_state, efos_third_party_state, efos_meaning, raw} | null, findings[] }.
Example: "Is this invoice still valid?" -> cfdi_status(xml="<cfdi:Comprobante …>").
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| xml | No | The complete CFDI XML. When given, the four query fields are derived from it and any values passed alongside are ignored. | |
| uuid | No | The fiscal folio (UUID) from the Timbre Fiscal Digital. Required unless `xml` is given. | |
| total | No | Invoice total exactly as written in the XML, e.g. '1160.00'. Required unless `xml` is given. | |
| rfc_emisor | No | Issuer's RFC. Required unless `xml` is given. | |
| rfc_receptor | No | Receiver's RFC. Required unless `xml` is given. | |
| response_format | No | Output format: 'markdown' for a human-readable summary (default) or 'json' for the full structured payload. | markdown |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| source | Yes | ||
| status | Yes | ||
| attempts | Yes | ||
| endpoint | Yes | ||
| findings | Yes | ||
| available | Yes | ||
| elapsed_ms | Yes | ||
| expression | Yes | ||
| unavailable_reason | Yes |