> ## Documentation Index
> Fetch the complete documentation index at: https://docs.newinvoice.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Error reference

> {/* GENERATED by pnpm openapi (scripts/generate-openapi.ts).

Every error is `application/problem+json` (RFC 9457) with agent-first extensions:

```json theme={null}
{
  "type": "https://errors.invoiceapi.mx/cfdi_invalid_rfc",
  "title": "Receptor RFC failed SAT validation",
  "status": 422,
  "detail": "RFC 'XAXX010101ABC' is not registered at the SAT (CFDI40**).",
  "code": "cfdi.receptor_rfc_not_registered",
  "remediation": "Ask the customer for their Constancia de Situación Fiscal and use the RFC exactly as printed.",
  "pac": { "provider": "pac", "codigo": "402", "mensaje": "..." },
  "request_id": "req_..."
}
```

Switch on `code` (stable across releases), show `remediation` to the user or feed it to an agent, and log `request_id`. When the failure came from the PAC, the `pac` object carries the raw PAC/SAT `codigo`/`mensaje`. Every problem body also carries a `doc_url` that deep-links to the matching row below (dots in the code become dashes in the anchor).

## Complete error catalog (226 codes)

Every stable `code` the API can emit, generated by statically scanning the source (a build-time drift guard fails if any code is missing). `HTTP` is the status the code surfaces with. Anchor = `code` with dots replaced by dashes, matching the `doc_url` in every problem body.

| `code` | HTTP | Title | Remediation |
| - | :-: | - | - |
| `auth.key_revoked` | 401 | API key revoked | Mint a replacement key with POST /v1/api\_keys and update your credentials. |
| `auth.unauthorized` | 401 | Authentication required | Send `Authorization: Bearer sk_test_...` (or sk\_live\_...) with a valid API key. |
| `autofactura.driver_unavailable` | job | The Crawlee autofactura driver is not installed | Run `pnpm install` (crawlee+playwright are optionalDependencies) then `pnpm exec playwright install chromium`, or set AUTOFACTURA\_DRIVER=simulated. |
| `autofactura.option_not_found` | job | No matching option in the portal dropdown | The portal may label this dropdown differently than the SAT code. Check the value sent for regimen/usoCfdi against the portal options. |
| `autofactura.portal_unknown` | job | No self-invoice portal could be resolved from the ticket | Ensure the ticket photo shows the merchant self-invoice URL, or pass `portal_url` on the request. |
| `autofactura.vision_empty_response` | job | The vision model returned no text | Retry; if it persists the model may not support image input — check AUTOFACTURA\_VISION\_MODEL. |
| `autofactura.vision_error` | job | The vision model request failed | Check AUTOFACTURA\_VISION\_API\_KEY and AUTOFACTURA\_VISION\_MODEL; retry on 429/5xx. |
| `autofactura.vision_parse_error` | job | The vision model did not return parseable ticket JSON | Retry; if it persists, the prompt/model pairing may need adjustment. |
| `billing.already_subscribed` | 409 | Organization already has a subscription | Use POST /v1/billing/portal\_sessions (or the dashboard) to change or cancel the plan. |
| `billing.free_limit_reached` | 402 | Free plan live-stamp limit reached | Upgrade in the dashboard (Billing) or via POST /v1/billing/checkout\_sessions. Test-mode stamping stays unlimited. |
| `billing.no_billing_account` | 409 | No billing account yet | Subscribe first via POST /v1/billing/checkout\_sessions. |
| `billing.subscription_required` | 402 | Live downloads need a subscription | Subscribe in the dashboard (Billing) or via POST /v1/billing/checkout\_sessions, then retry. Test-mode downloads stay free. |
| `cancel.already_cancelled` | 422 | Previously cancelled | The CFDI was already cancelled. Treat as cancelled — no further action needed. |
| `cancel.date_after_declaration` | 422 | Request date after declaration date | The cancellation request date is after the SAT declaration date for the period. Cancellation is no longer permitted for this CFDI. |
| `cancel.global_invoice_deadline` | 422 | Past the global-invoice deadline | The deadline to cancel this factura global has passed. This CFDI can no longer be cancelled. |
| `cancel.in_process` | 422 | Cancellation in process | The cancellation is in process awaiting the receptor’s response. Keep polling the status; it resolves within 72 business hours. |
| `cancel.motivo_invalid` | 422 | Motivo not valid | The motivo is not valid. Use one of 01-04 per the SAT catalog. |
| `cancel.motivo_missing` | 422 | Motivo not specified or invalid | The cancellation motivo is missing or invalid. Provide a valid motivo 01-04 (01 requires a substitute folio). |
| `cancel.no_acuse` | 422 | CFDI not cancelled — acuse unavailable | The acuse cannot be retrieved because the CFDI is not cancelled (or was not cancelled through this service). Confirm the cancellation completed before requesting the acuse. |
| `cancel.not_applicable` | 422 | Folio not applicable to cancellation | This folio cannot be cancelled (e.g. it is referenced by another live CFDI). Query related CFDIs to find the blocker before retrying. |
| `cancel.not_cancellable` | 422 | Not cancellable | The SAT reports this CFDI as "No Cancelable" (e.g. it is referenced by other live documents). Query related CFDIs to find and resolve the blocker. |
| `cancel.not_primary_sector` | 422 | UUID is not a Sector Primario CFDI | The requested cancellation flow requires a Sector Primario CFDI, which this is not. Use the standard cancellation motivo for this document type. |
| `cancel.outside_annual_declaration` | 422 | Outside the annual declaration window | The cancellation falls outside the allowed annual declaration window and is no longer permitted. |
| `cancel.relation_invalid` | 422 | Relation invalid | The declared relation is invalid. Verify the related CFDI and relationship type. |
| `cancel.request_received` | 422 | Cancellation request received | The SAT received the cancellation request. If the CFDI is "cancelable con aceptación", the receptor has 72 business hours to accept or reject; poll the status until it resolves. |
| `cancel.rfc_mismatch` | 422 | Emisor RFC does not match | The emisor RFC on the request does not match the CFDI. Send the cancellation from the RFC that issued the CFDI, signed with that RFC’s CSD. |
| `cancel.sat_service_unavailable` | 502 | SAT error during query | The SAT returned an error during the status query. Retry with backoff. |
| `cancel.substitution_folio_invalid` | 422 | Substitute folio not valid | The substitute folio is not valid. For motivo 01, supply the UUID of a valid, already-stamped replacement CFDI. |
| `cancel.substitution_folio_not_required` | 422 | Substitute folio not required | This motivo does not require a substitute folio. Remove folioSustitucion. |
| `cancel.user_invalid` | 422 | User not valid for cancellation | The user is not authorized for this cancellation. Verify credentials and that the signing CSD belongs to the emisor RFC. |
| `cancel.uuid_not_found` | 422 | Folio fiscal does not exist | The UUID does not exist at the SAT. Verify the folio fiscal is correct and that the CFDI was actually stamped in this environment (test vs live). |
| `catalog.not_found` | 404 | Unknown catalog | Use one of: the slugs listed by GET /v1/catalogs. GET /v1/catalogs lists them all. |
| `cfdi.csd_expired` | 422 | Invalid invoice | Renew the CSD at the SAT portal; an expired/not-yet-valid certificate cannot stamp. |
| `cfdi.csd_rfc_mismatch` | 422 | Invalid invoice | Use the CSD (certificado de sello digital) issued to the emitter RFC. |
| `cfdi.emisor_not_in_lco` | 422 | Taxpayer not in LCO / obligations negative | The emitter is not in the SAT’s Lista de Contribuyentes Obligados or its fiscal obligations validation is negative. The taxpayer must resolve their status with the SAT (e.g. active CSD, up-to-date obligations) before stamping. |
| `cfdi.fecha_in_future` | 422 | Invalid invoice | Generate Fecha at stamp time; it must not be in the future. |
| `cfdi.fecha_out_of_range` | 422 | Fecha outside the allowed window | The CFDI Fecha is outside the SAT-allowed window (must be within roughly the last 72 hours and not in the future, in Mexico time). Regenerate the CFDI with a fresh timestamp and re-sign it (the sello changes with Fecha). |
| `cfdi.fecha_too_old` | 422 | Invalid invoice | Re-issue the invoice; CFDI must be stamped within 72h of Fecha. |
| `cfdi.invalid_expression` | 422 | Invalid expression | The status query expression is invalid. Check the Total formatting (decimals must match the CFDI exactly) and the RFCs. |
| `cfdi.invalid_fecha` | 422 | Invalid invoice | Pass date as 'YYYY-MM-DDTHH:mm:ss' (SAT forbids a timezone suffix). |
| `cfdi.invalid_input` | 422 | Invalid invoice | Fix every field listed in `errors` and resubmit. See /openapi.json for the schema. |
| `cfdi.malformed_sello` | 422 | Malformed sello | The Sello attribute is not valid base64 or has the wrong length. Recompute the sello: RSA-SHA256 over the cadena original, base64-encoded, using the emitter CSD private key. |
| `cfdi.malformed_xml` | 422 | Malformed CFDI XML | The CFDI XML is not well-formed or violates the SAT schema. Rebuild the XML — check element order, required attributes, and that special characters are escaped. |
| `cfdi.missing_tax_rate` | 422 | Invalid invoice | Provide `rate` (e.g. 0.16) for Tasa/Cuota taxes. |
| `cfdi.missing_uso` | 422 | Invalid invoice | Set `use` at invoice level or `customer.use`. |
| `cfdi.no_tfd` | 422 | Invalid invoice | Only stamped CFDIs (with a TFD) have a TFD cadena. |
| `cfdi.not_a_csd` | 422 | Invalid invoice | Sign CFDIs with the CSD; the FIEL is only for descarga masiva. |
| `cfdi.not_comprobante` | 422 | Invalid invoice | Pass a CFDI 4.0 Comprobante XML. |
| `cfdi.not_found` | 422 | Comprobante not found | The comprobante was not found at the SAT. Verify the UUID and that emisor RFC, receptor RFC and Total exactly match the CFDI. |
| `cfdi.objeto_imp_02_without_taxes` | 422 | Invalid invoice | Provide at least one tax for this item, or set tax\_object to '01' (no objeto de impuesto). |
| `cfdi.objeto_imp_dr_02_without_taxes` | 422 | Invalid invoice | Provide at least one tax on the related document, or set its tax\_object to '01'. |
| `cfdi.parse_error` | 422 | Invalid invoice | Provide a well-formed CFDI XML string. |
| `cfdi.pdf_dependencies_missing` | 422 | Invalid invoice | Install the optional PDF peer dependencies (the CFDI PDF renderer and @xmldom/xmldom) on the server. |
| `cfdi.pdf_requires_stamp` | 422 | Invalid invoice | Stamp the CFDI with the PAC first; the PDF shows the fiscal folio (UUID). |
| `cfdi.retencion_exento` | 422 | Invalid invoice | Use factor Tasa or Cuota for withholdings, or make it a traslado. |
| `cfdi.sello_injection_failed` | 422 | Invalid invoice | Ensure the XML is a well-formed CFDI 4.0 Comprobante. |
| `cfdi.sello_mismatch` | 422 | Sello does not match | The sello does not correspond to the CFDI content or certificate. This means the cadena original was built incorrectly or a different key signed it. Regenerate the cadena original from the final XML and re-sign with the matching CSD private key. |
| `cfdi.status_indeterminate` | 502 | Indeterminate state | The SAT could not determine the state. Retry the status query later. |
| `cfdi.unknown_currency` | 422 | Invalid invoice | Use a valid ISO 4217 currency code present in c\_Moneda. |
| `connect.account_disabled` | 403 | Connected account disabled | Re-enable the account via PATCH /v1/accounts/:id \{"disabled": false}. |
| `connect.account_not_authorized` | 403 | Not a connected account of this platform | Create the connected account with POST /v1/accounts (using your platform key, no Invoice-Account header), then pass its id as Invoice-Account. |
| `connect.nesting_not_allowed` | 403 | Connected accounts cannot own connected accounts | Create connected accounts from the top-level platform organization. |
| `connect.not_supported_here` | 400 | Acting as a connected account is not supported here | Drop the Invoice-Account header. Manage API keys with your platform key directly. |
| `dashboard.email_in_use` | 409 | Email already registered | Sign in at /signin, or use a different email. |
| `dashboard.invalid_credentials` | 401 | Invalid email or password | Check your credentials and try again. |
| `dashboard.pages_unavailable` | 503 | Dashboard pages unavailable | Deploy the web/ assets alongside the API, then retry. |
| `dashboard.session_required` | 401 | Sign in required | Sign in at /signin to obtain a session cookie. |
| `dashboard.too_many_attempts` | 429 | Too many sign-in attempts | Wait 15 minutes before trying again. |
| `dashboard.too_many_signups` | 429 | Too many sign-ups | Wait an hour before trying again, or sign in to an existing account. |
| `download.concurrent_request` | 409 | An identical download was just created | — |
| `download.fiel_required` | 422 | Fiscal profile has no SAT password or FIEL | Save the RFC's SAT password with POST /v1/fiscal\_profiles/:id/sat\_credential and wait for it to verify, or upload a FIEL via PATCH /v1/fiscal\_profiles/:id, then retry. |
| `download.invalid_range` | 422 | Invalid date range | Provide date\_from \<= date\_to. |
| `download.no_request_id` | job | Download has no SAT request id | The solicitud never registered with the PAC — create the download again. |
| `download.overlapping_in_progress` | 409 | An overlapping download is still running | Wait for that download to finish, then send this request again. Invoices it delivers are skipped and not charged twice. |
| `download.poll_timeout` | job | Download gave up waiting for the PAC | The SAT package was not ready within the polling window. Create a new download, and fragment very large date ranges. |
| `download.range_too_large` | 422 | Date range is too wide | Split the range into requests of at most \{MAX\_RANGE\_DAYS} days each. |
| `download.range_too_old` | 422 | Date range starts too early | Request invoices issued on or after \{EARLIEST\_DATE}. |
| `download.request_failed` | 502 | SAT descarga masiva request failed | — |
| `download.unknown_error` | job | Download failed (unclassified) | Read the download `error.message`; if it looks transient, create a new download for the same range. |
| `fiscal_document.concurrent_request` | 409 | Another request for this document just finished | — |
| `fiscal_document.credential_expired` | 422 | The SAT credential has expired | — |
| `fiscal_document.credential_missing` | 422 | Fiscal profile has no SAT credential | — |
| `fiscal_document.credential_pending` | 422 | The SAT credential is still being checked | Wait for the fiscal\_profile.sat\_credential\_verified webhook or poll GET /v1/fiscal\_profiles/:id/sat\_credential, then retry. |
| `fiscal_document.credential_replaced` | 422 | The SAT credential was replaced | Create a new fiscal\_document; it will use the credential saved now. |
| `fiscal_document.credential_unverified` | 422 | The SAT credential is not verified | Confirm the RFC and SAT password with the taxpayer before saving it again: do not retry the same password automatically. \{saveRemediation} |
| `fiscal_document.deadline_exceeded` | job | The request ran out of time | Create a new fiscal\_document. If it keeps happening, contact support. |
| `fiscal_document.expired` | 410 | The PDF is no longer stored | Create a new fiscal\_document to get a current PDF. |
| `fiscal_document.free_quota_exceeded` | 402 | The free constancia for this quarter has been used | The next free constancia for this RFC is available from \{nextStartsOn} (America/Mexico\_City). Until then, use the PDF you already have. |
| `fiscal_document.invalid_artifact` | job | The document service did not return a PDF | Create a new fiscal\_document. If it keeps happening, contact support. |
| `fiscal_document.not_ready` | 409 | The PDF is not available | Wait for the fiscal\_document.ready webhook or poll GET /v1/fiscal\_documents/:id; if it ended in another state, read its `error` and create a new request. |
| `fiscal_document.rfc_in_progress` | 409 | A constancia for this RFC is already in progress | Wait for that request to finish; if it does not deliver a PDF, retry this one. |
| `fiscal_document.simulate_test_only` | 422 | simulate is only available in test mode | Remove `simulate`, or use a sk\_test\_ key. |
| `fiscal_profile.csd_expired` | 422 | CSD is expired or not yet valid | Renew the CSD at the SAT portal (Certificados de Sello Digital) and register the new pair. An expired certificate cannot stamp live CFDIs. |
| `fiscal_profile.csd_is_fiel` | 422 | FIEL supplied where a CSD is required | Register the CSD under `csd` (used to sign CFDIs). If you also have a FIEL for descarga masiva, send it under `fiel`. |
| `fiscal_profile.csd_rfc_mismatch` | 422 | Rotated CSD belongs to a different RFC | Rotate to a CSD issued to the same RFC. To register a different taxpayer, create a new fiscal\_profile. |
| `fiscal_profile.fiel_required` | 422 | A FIEL is required to sign the manifiesto | Send the FIEL under `fiel: { cer_base64, key_base64, password }` in this request, or register it on the profile first. |
| `fiscal_profile.in_use` | 409 | Fiscal profile is in use | A fiscal profile with issued invoices is retained for tax-record integrity. Rotate its CSD instead of deleting, or delete dependent records first. |
| `fiscal_profile.invalid_csd` | 422 | Invalid CSD certificate or password | Verify cer\_base64 is the .cer, key\_base64 is the .key, and password is the private-key passphrase (clave privada). Re-download the CSD from the SAT if unsure. |
| `fiscal_profile.invalid_fiel` | 422 | Invalid FIEL certificate or password | Verify the FIEL (e.firma) files and password; it is only used for descarga masiva. |
| `fiscal_profile.invalid_metadata_json` | 422 | metadata is not valid JSON | Send `metadata` as a JSON string, e.g. metadata=\{"order\_id":"123"}. Keys and values must be strings. |
| `fiscal_profile.manifest_required` | 409 | Carta manifiesto not signed | Sign the carta manifiesto for this RFC first: POST /v1/fiscal\_profiles/\{id}/manifest with the FIEL (e.firma). This is a one-time authorization per profile. Next action → \{ "type": "sign\_manifest", "endpoint": "POST /v1/fiscal\_profiles/\{id}/manifest", "requires": \["fiel"] }. |
| `fiscal_profile.sandbox_expired` | 422 | The test-mode sandbox certificate has expired | Register your own test fiscal profile with POST /v1/fiscal\_profiles (RFC + CSD) and pass its id as fiscal\_profile\_id. |
| `fiscal_profile.sandbox_not_supported` | 422 | Not available on the sandbox fiscal profile | Register your own test fiscal profile with POST /v1/fiscal\_profiles (RFC + CSD) and use its id. The sandbox issuer only stamps test invoices. |
| `fiscal_profile.sandbox_retired` | 422 | The sandbox fiscal profile is no longer available | Pass the id of your test fiscal profile as fiscal\_profile\_id (GET /v1/fiscal\_profiles). |
| `idempotency.key_processing` | 409 | A request with this Idempotency-Key is still being processed | Retry once the in-flight request with this Idempotency-Key completes. |
| `idempotency.key_reuse` | 409 | Idempotency-Key reused with a different request body | Reuse an Idempotency-Key only for byte-identical retries; use a fresh key for a new request. |
| `idempotency.replay_unavailable` | 409 | The original response for this Idempotency-Key cannot be replayed | The original request succeeded but its one-time secret cannot be recovered. Find the resource with a list call, delete or revoke it, and create a new one with a fresh Idempotency-Key. |
| `internal.error` | 500 | Internal server error | — |
| `invoice.already_stamped` | 409 | Invoice already stamped | — |
| `invoice.cancellation_in_progress` | 409 | Cancellation already requested | — |
| `invoice.cancellation_rejected` | 422 | Cancellation rejected | Check the UUID is vigente and the motivo/substitute are correct; retrieve the acuse for details. |
| `invoice.folio_contention` | 409 | Could not assign a folio | Retry the request; concurrent invoice creation on this serie is heavy. |
| `invoice.folio_taken` | 409 | Folio already used | — |
| `invoice.no_xml` | 422 | Invoice has no XML to stamp | Recreate the invoice; the signed XML is missing. |
| `invoice.not_stampable` | 409 | Invoice cannot be stamped | — |
| `invoice.not_stamped` | 409 | Only stamped invoices can be cancelled | — |
| `invoice.pdf_requires_stamp` | 409 | PDF is only available for stamped invoices | — |
| `invoice.profile_missing_csd` | 422 | Fiscal profile has no CSD | Upload the CSD (.cer + .key + password) to the fiscal profile before issuing invoices. |
| `invoice.substitute_not_stamped` | 422 | Substitute invoice is not stamped | Stamp the replacement invoice before referencing it as a substitute. |
| `invoice.substitute_required` | 422 | Substitute invoice required | Pass substitute\_invoice\_id pointing to the stamped CFDI that replaces this one, or use motivo '02'. |
| `invoice.xml_requires_stamp` | 409 | XML is only available for stamped invoices | — |
| `manifest.email_required` | 422 | An email is required for the manifiesto | Provide `email` in the request or set the MANIFEST\_EMAIL config default. |
| `manifiesto.already_signed` | 422 | Contrato firmado previamente | The carta manifiesto was already signed for this RFC — no action needed. The profile is authorized for CFDI certification. |
| `manifiesto.bad_request` | 422 | Petición inválida o parámetros incorrectos | The request was missing or had incorrect parameters (cert, key, keyPass, correo). |
| `manifiesto.bad_sello` | 422 | Sello del emisor mal formado o inválido | The FIEL sello could not be validated. Re-download the FIEL (e.firma) from the SAT and try again. |
| `manifiesto.certificate_error` | 422 | Error en el certificado | There was an error with the certificate. Verify the FIEL files and password, then retry. |
| `manifiesto.certificate_not_sat` | 422 | El certificado del emisor no fue emitido por el SAT | The certificate was not issued by the SAT. Upload the taxpayer’s real FIEL/e.firma certificate. |
| `manifiesto.certificate_not_valid` | 422 | El certificado del emisor no es válido | The FIEL certificate is not valid (expired or revoked). Renew the e.firma at the SAT and retry. |
| `manifiesto.contract_not_valid` | 422 | El contrato no es válido | The signing service reports the signing contract is not valid. Contact support. |
| `manifiesto.invalid_certificate` | 422 | Certificado del emisor mal formado o inválido | The FIEL certificate is malformed or invalid. Ensure cert is the .cer of a current SAT e.firma (not a CSD) and key is its matching .key. |
| `manifiesto.invalid_credentials` | 422 | Usuario, contraseña o contrato del asociado inválido | The manifiesto signing service rejected the request credentials. Verify the FIEL password and that the account is provisioned for manifiesto signing. |
| `manifiesto.invalid_response` | 422 | Manifiesto signing service returned a non-XML response: \{message} | The manifiesto signing service returned a non-XML body (usually an HTML error or login page). This is typically a misconfigured firmado base URL, a manifiesto service not provisioned for the PADE account, or bad platform credentials — verify the PRODIGIA\_FIRMADO\_\* config and that the account is provisioned for manifiesto signing. |
| `manifiesto.malformed_contract` | 422 | Contrato mal formado | The manifiesto contract payload was malformed. Contact support. |
| `manifiesto.malformed_xml` | 422 | XML mal formado | The signing service could not parse the request. Retry; if it persists, contact support. |
| `manifiesto.network_error` | 502 | Manifiesto signing request failed | The manifiesto signing request could not reach the signing service. Retry with backoff. |
| `manifiesto.provider_error` | 502 | Error al procesar la firma del contrato | The signing service hit an internal error signing the manifiesto. Retry with backoff; if it persists, contact support. |
| `manifiesto.signed` | 422 | Firma exitosa | — |
| `manifiesto.xml_generation_error` | 502 | Error al generar el xml | The signing service failed to generate the signed XML. Retry with backoff; if it persists, contact support. |
| `org.bootstrap_disabled` | 403 | Organization bootstrap is disabled | Enable ALLOW\_ORG\_BOOTSTRAP or provision organizations out-of-band. |
| `org.not_found` | 404 | Organization not found | Check the organization id. |
| `pac.auth_failed` | 422 | PAC credentials invalid | The stamping-service credentials were rejected. Do NOT retry — repeated failures can lock the account. This is a server-side configuration issue: contact support so the environment credentials can be verified (test and live credentials are separate). |
| `pac.certificate_invalid` | 422 | Certificate invalid | The certificate is invalid. Verify the emitter CSD is current and matches the emitter RFC, then re-register it. |
| `pac.certificate_revoked` | 422 | Certificate revoked or expired | The emitter CSD is revoked or outside its validity window. The taxpayer must obtain a new Certificado de Sello Digital from the SAT and re-register it on the fiscal profile. Do not retry with the same certificate. |
| `pac.contract_invalid` | 422 | Contract invalid or expired | The stamping account is not found, is expired, or has no available stamping credits. This is a server-side account issue: contact support to renew the account or top up stamp credits. |
| `pac.duplicate` | 422 | Comprobante already exists | This exact comprobante was already stamped (SAT forbids stamping identical CFDIs twice). Treat as a success-replay: use the previously stamped UUID/XML rather than re-stamping. This is the idempotency path — surface the prior stamp to the caller. |
| `pac.fiel_not_assembled` | 422 | FIEL could not be packaged as a PKCS#12 | The FIEL stored on this fiscal profile could not be packaged as a PKCS#12, so nothing was sent to the PAC or the SAT. Usually the key password does not match the .key, or the .key is not a FIEL private key. Upload fiel\_cer, fiel\_key and fiel\_password again, then retry. If they are correct, contact support with the request\_id: the server's PKCS#12 packaging is failing. |
| `pac.internal_error` | 502 | PAC internal error | The service reported a transient internal error. Retry with backoff. |
| `pac.invalid_parameter` | 422 | Invalid or missing parameter | A required parameter was missing or malformed (also returned for a malformed JSON body). Do not retry unchanged — inspect the request payload against the timbrarCfdi contract. |
| `pac.maintenance` | 502 | PAC offline for maintenance | The stamping service is temporarily offline for maintenance. Retry with backoff after a short delay. |
| `pac.no_default_certificate` | 422 | No default certificate for cancellation | No CSD was available to sign the cancellation. Provide the emitter CSD (cerPem/keyPem/keyPassword) on the cancel request, since cancellations are signed. |
| `pac.ok` | 422 | Comprobante found | No action needed — the comprobante was found. |
| `pac.query_incomplete` | 502 | General error, query not completed | The status query did not complete. Retry with backoff. |
| `pac.sandbox_duplicate` | 422 | Duplicate reported for a sandbox invoice | Create a new invoice: POST /v1/invoices assigns a new folio. The duplicate response was discarded. |
| `pac.stamp_outcome_unknown` | 422 | Stamping outcome unknown | Do not create this invoice again: it may already be a real CFDI. Find it with GET /v1/invoices?status=stamp\_failed and call POST /v1/invoices/:id/stamp with a new Idempotency-Key, within 3 months. That re-sends the same serie and folio, so an existing stamp is returned instead of a second CFDI; after 3 months the PAC no longer matches the folio, so check the SAT status first. |
| `pac.unknown` | 422 | Unmapped PAC/SAT code | The stamping service returned a code not in our mapping. Inspect the `pac` message; treat as a definitive failure unless it is clearly transient. |
| `rate_limit.exceeded` | 429 | Too many requests | Back off and retry after the number of seconds in the Retry-After header; batch work or lower your request rate. |
| `request.error` | 500 | Request error | — |
| `request.invalid_date` | 422 | Invalid date filter | Pass \{field} as an ISO-8601 date or datetime, e.g. 2026-07-10 or 2026-07-10T00:00:00Z. |
| `request.invalid_expand` | 422 | Invalid expand parameter | Use only the expand values the endpoint documents (e.g. fiscal\_profile). Remove unsupported values. |
| `request.invalid_pagination` | 422 | Conflicting pagination cursors | Pass only one cursor: starting\_after for the next (older) page, or ending\_before for the previous (newer) page. |
| `request.invalid_version` | 400 | Unknown API version | Send a supported version in the Invoice-Version header, or omit it to use the current version. |
| `request.method_not_allowed` | 405 | Method not allowed | — |
| `request.validation_failed` | 422 | Request validation failed | Fix the fields listed in `errors` and resubmit. See /openapi.json for the schema. |
| `resource.not_found` | 404 | Resource not found | Verify the id and that you are using the matching-mode key (a test id needs a test key, a live id needs a live key). |
| `sat_credential.concurrent_update` | 409 | SAT credential was changed by another request | — |
| `sat_credential.csd_is_not_fiel` | 422 | CSD supplied where a FIEL is required | Send the taxpayer's FIEL (e.firma). SAT does not accept a CSD for this. |
| `sat_credential.fiel_expired` | 422 | FIEL is expired or not yet valid | Renew the e.firma with SAT and send the new certificate and key. |
| `sat_credential.fiel_rfc_mismatch` | 422 | FIEL does not belong to this profile's RFC | Send the FIEL issued to \{rfc}, or use the fiscal profile for \{rfc}. |
| `sat_credential.invalid_fiel` | 422 | Invalid FIEL certificate or password | Check that cer\_base64 is the .cer, key\_base64 is the matching .key, and password is the private-key password of the e.firma. |
| `sat_service.artifact_checksum_mismatch` | 502 | SAT document failed its integrity check | Read the job again and retry the download; the document can change while a job is still updating it. |
| `sat_service.artifact_not_ready` | 409 | SAT document is not ready | Poll the document job and download once it reports an artifact is available. |
| `sat_service.auth_failed` | 503 | SAT document service rejected this deployment | A server-side configuration issue, not a problem with your request. Operators must check SAT\_API\_KEY; do not retry until it is corrected. |
| `sat_service.auth_method_unsupported` | 422 | Credential type cannot request this document | Register a CIEC credential for this document type. |
| `sat_service.capacity_reached` | 429 | SAT document service is at capacity | Retry later with backoff; document jobs run with limited concurrency. |
| `sat_service.captcha_failed` | job | The SAT CAPTCHA could not be solved | Submit the document request again later. |
| `sat_service.conflict` | 409 | SAT document request conflicts with an earlier one | An Idempotency-Key was reused with a different document type, credential, range or deadline. Reuse a key only for identical retries; use a new key for a new request. |
| `sat_service.credential_rejected` | job | SAT rejected the credential | Ask the taxpayer to confirm their RFC and SAT password, then register the credential again. Do not retry the same password automatically. |
| `sat_service.deadline_exceeded` | job | The SAT document job ran out of time | Submit a new request. For invoices, use a shorter date range; a job cannot outlive its credential. |
| `sat_service.invalid_request` | 422 | SAT document service refused the request | Check the RFC, the password length (1 to 256 characters) and any date range, then send a corrected request. |
| `sat_service.invalid_response` | 502 | SAT document service returned an unexpected response | Retry with backoff. If it persists, contact support with the request\_id. |
| `sat_service.job_failed` | job | The SAT document job failed | Submit a new request. If it persists, contact support with the request\_id. |
| `sat_service.needs_review` | job | The SAT request needs manual review | Contact support with the request\_id so the request can be reviewed. |
| `sat_service.not_configured` | 503 | SAT document service is not configured | Live SAT access is not enabled on this deployment. Operators must set SAT\_SERVICE\_URL and SAT\_API\_KEY; until then, use a sk\_test\_ key to exercise this flow against simulated data. |
| `sat_service.not_found` | 404 | SAT credential or job not found | The credential or document job does not exist or is no longer retained. Register the credential again or submit a new job. |
| `sat_service.portal_changed` | job | The SAT portal did not look as expected | Contact support with the request\_id; this needs a fix on our side. |
| `sat_service.sat_unavailable` | job | SAT was unavailable or slow | Submit the document request again later; SAT outages are usually temporary. |
| `sat_service.unavailable` | 503 | SAT document service is unavailable | The request did not complete. Retry with backoff; for a request that creates something, resend the same Idempotency-Key. |
| `sat_service.verification_inconclusive` | job | The credential check was inconclusive | The check could not establish whether the password is valid (for example an outage or a CAPTCHA problem). Register the credential again later. |
| `sat.download_accepted` | 422 | Download request accepted | No action needed — poll the request status until packages are ready. |
| `sat.download_duplicate` | 422 | Duplicate request | An identical request already exists. Reuse the existing request’s id, or vary the date range to create a distinct request. |
| `sat.download_expired` | job | SAT marked the solicitud as expired | Package URLs expire quickly — create a new download for the same range. |
| `sat.download_generation_error` | 503 | Request generation error | The stamping service failed to generate the SAT request. Retry with backoff; if it persists, verify the FIEL, dates and RFC. |
| `sat.download_no_data` | 422 | No data for the filters | The SAT found no CFDIs for these filters — this is a terminal, successful, empty result. Do not retry; the range simply has no documents. |
| `sat.download_package_not_found` | 422 | Package not found | The requested package was not found — its download URL likely expired (URLs live \~5 minutes). Re-poll the request status to re-mint the package URLs and download immediately. |
| `sat.download_packaging_error` | 503 | Packaging error | The SAT failed to package the result. Retry the status poll; if it persists, re-issue the request with a smaller date range. |
| `sat.download_rejected` | job | SAT rejected the solicitud | Verify the FIEL matches the requesting RFC and role (issued/received); a mismatch yields a SAT "tercero no autorizado" rejection. |
| `sat.download_request_limit` | 422 | Lifetime request limit reached | SAT allows at most 2 lifetime requests with identical parameters. Do NOT retry the identical query — fragment or vary the date range (e.g. split the month into weeks) to make a distinct request. |
| `sat.download_too_large` | 422 | Result exceeds SAT size cap | The request exceeds the SAT cap (200,000 XMLs / 1,000,000 metadata records). Fragment the date range into smaller windows and issue separate requests. |
| `sat.download_unauthorized` | 422 | Third party not authorized | SAT "tercero no autorizado": the FIEL does not match the requested RFC. Ensure the FIEL PFX belongs to the RFC being downloaded and that side (issued/received) matches that RFC’s role. |
| `sat.download_uncontrolled_error` | 503 | Uncontrolled error | An uncontrolled error occurred on the descarga masiva service. Retry with backoff; if it persists, contact support. |
| `sync.auth_failed` | 422 | Sync-service credentials rejected | The sync-service credentials were rejected. This is a server-side configuration issue: contact support so the environment credentials can be verified. |
| `sync.download_url_error` | 503 | Error al generar la URL de descarga | The download URL could not be generated. Retry with backoff. |
| `sync.fiel_required` | 422 | FIEL required to enable SAT sync | Attach the taxpayer's FIEL to this fiscal profile (fiel\_cer / fiel\_key / fiel\_password), then enable sync again. |
| `sync.in_process` | 503 | Solicitud en proceso | The request is still being processed. Keep polling with backoff; this is the normal in-flight state, not a failure. |
| `sync.information_expired` | 422 | Información expirada | The prepared result expired before it was collected. Issue a fresh request for the same range. |
| `sync.information_not_found` | 422 | No se encontró la información solicitada | The referenced request no longer exists. Issue a fresh request rather than polling this one. |
| `sync.internal_error` | 503 | Error interno del servicio de sincronización | The sync service failed internally. Retry with backoff. |
| `sync.invalid_fiel` | 422 | Problema con el PFX de la FIEL | The FIEL (e.firma) could not be read. Verify the .cer/.key are a current e.firma (not a CSD) and that the password matches the private key. NOTE: the SAT does not validate the certificate at registration time — a revoked or expired FIEL registers successfully and only fails later when data is requested, so confirm the certificate is active at the SAT's certificate-status portal. |
| `sync.no_documents` | 422 | No se encontraron comprobantes | No CFDIs matched the filters. This is a terminal, successful, EMPTY result — do not retry the same query. |
| `sync.ok` | 422 | Solicitud exitosa | No action needed. |
| `sync.request_created` | 422 | Solicitud creada | No action needed — the request was created; poll for the result. |
| `sync.request_creation_failed` | 503 | Error al crear la solicitud | The request could not be created. Retry with backoff. |
| `sync.request_expired` | 422 | Solicitud vencida | The request expired before it was collected. Issue a new request for the same range. |
| `sync.rfc_already_registered` | 422 | El RFC ya existe | This RFC is already registered for automatic SAT synchronization. Do not register it twice — update the existing profile instead. |
| `sync.rfc_disabled` | 422 | RFC deshabilitado | Automatic synchronization is disabled for this RFC. Re-enable sync on the fiscal profile before querying its documents. |
| `sync.uuid_not_found` | 422 | No se encontró el UUID | The folio fiscal is not in the synced store. It may not have been issued yet, may fall outside the synced date range, or may not belong to this RFC. |
| `sync.zip_error` | 503 | Error al crear el ZIP | The document archive could not be built. Retry with backoff. |
| `ticket_invoice.failed` | job | The merchant portal refused the ticket | The ticket was already invoiced, or the folio/portal is wrong — verify and submit a corrected ticket. |
| `ticket_invoice.fiscal_profile_not_found` | 400 | fiscal\_profile\_id does not reference a known fiscal profile | Create a fiscal profile first (POST /v1/fiscal\_profiles) and pass its id as fiscal\_profile\_id. |
| `ticket_invoice.invalid_metadata_json` | 422 | metadata is not valid JSON | Send metadata as a JSON string, e.g. metadata=\{"trip":"CDMX"}. |
| `ticket_invoice.needs_review` | job | The ticket could not be invoiced automatically | Finish the invoice on the merchant portal manually (captcha/ambiguous form/low OCR confidence), then record the CFDI. |
| `ticket_invoice.photo_missing` | job | The ticket photo is no longer available to process | Submit a new ticket invoice with the photo attached. |
| `ticket_invoice.xml_unavailable` | 409 | XML is only available for a completed ticket invoice | — |
| `validation.empty_xml` | 400 | Decoded XML is empty | Provide base64-encoded CFDI XML in `xml_base64`, or send raw XML with Content-Type application/xml. |
| `webhook.event_type_not_enabled` | 422 | Endpoint does not subscribe to this event type | Subscribe the endpoint to this event type (or a matching wildcard) via PATCH /v1/webhook\_endpoints/:id. |
| `webhook.https_required` | 400 | Webhook URL must use https in live mode | Use an https\:// URL, or a sk\_test\_ key for http\:// endpoints in development. |

## PAC / SAT numeric code mapping

Source of truth: `src/pac/error-map.ts`. Every numeric PAC/SAT code below normalizes to a stable `code`. `Retryable` means a transient failure you can retry with backoff; non-retryable codes require a change before resubmitting.

### Timbrado (stamping)

Returned by `POST /v1/invoices` and `POST /v1/invoices/:id/stamp`. Code `307` (duplicate) is NOT surfaced as an error — it is resolved as a success-replay of the prior stamp.

| PAC/SAT code | Our `code` | Retryable | Remediation |
| - | - | :-: | - |
| `0` | `pac.ok` | no | No action needed — the CFDI was stamped. |
| `1` | `pac.auth_failed` | no | The stamping-service credentials were rejected. Do NOT retry — repeated failures can lock the account. This is a server-side configuration issue: contact support so the environment credentials can be verified (test and live credentials are separate). |
| `2` | `pac.contract_invalid` | no | The stamping account is not found, is expired, or has no available stamping credits. This is a server-side account issue: contact support to renew the account or top up stamp credits. |
| `3` | `pac.invalid_parameter` | no | A required parameter was missing or malformed (also returned for a malformed JSON body). Do not retry unchanged — inspect the request payload against the timbrarCfdi contract. |
| `4` | `pac.stamp_outcome_unknown` | no | Do not create this invoice again: it may already be a real CFDI. Find it with GET /v1/invoices?status=stamp\_failed and call POST /v1/invoices/:id/stamp with a new Idempotency-Key, within 3 months. That re-sends the same serie and folio, so an existing stamp is returned instead of a second CFDI; after 3 months the PAC no longer matches the folio, so check the SAT status first. |
| `5` | `pac.maintenance` | yes | The stamping service is temporarily offline for maintenance. Retry with backoff after a short delay. |
| `20` | `pac.no_default_certificate` | no | The PAC has no default CSD for this user (only relevant when relying on CALCULAR\_SELLO). This API signs locally and embeds the certificate, so this indicates a misconfiguration — ensure the CFDI carries a valid NoCertificado/Certificado and you are not sending CALCULAR\_SELLO. |
| `301` | `cfdi.malformed_xml` | no | The CFDI XML is not well-formed or violates the SAT schema. Rebuild the XML — check element order, required attributes, and that special characters are escaped. |
| `302` | `cfdi.malformed_sello` | no | The Sello attribute is not valid base64 or has the wrong length. Recompute the sello: RSA-SHA256 over the cadena original, base64-encoded, using the emitter CSD private key. |
| `303` | `cfdi.sello_mismatch` | no | The sello does not correspond to the CFDI content or certificate. This means the cadena original was built incorrectly or a different key signed it. Regenerate the cadena original from the final XML and re-sign with the matching CSD private key. |
| `304` | `pac.certificate_revoked` | no | The emitter CSD is revoked or outside its validity window. The taxpayer must obtain a new Certificado de Sello Digital from the SAT and re-register it on the fiscal profile. Do not retry with the same certificate. |
| `305` | `pac.certificate_invalid` | no | The certificate is invalid (wrong type, not a CSD, or does not belong to the emitter RFC). Ensure the fiscal profile holds the emitter’s CSD (not a FIEL) issued for the emitter RFC. |
| `306` | `pac.certificate_invalid` | no | The certificate could not be validated. Re-upload the emitter CSD (.cer as delivered by the SAT) on the fiscal profile. |
| `307` | `pac.duplicate` | no | This exact comprobante was already stamped (SAT forbids stamping identical CFDIs twice). Treat as a success-replay: use the previously stamped UUID/XML rather than re-stamping. This is the idempotency path — surface the prior stamp to the caller. |
| `308` | `pac.certificate_invalid` | no | The certificate is invalid. Re-register a valid emitter CSD on the fiscal profile. |
| `309` | `pac.certificate_invalid` | no | The certificate is invalid. Verify the emitter CSD is current and matches the emitter RFC, then re-register it. |
| `401` | `cfdi.fecha_out_of_range` | no | The CFDI Fecha is outside the SAT-allowed window (must be within roughly the last 72 hours and not in the future, in Mexico time). Regenerate the CFDI with a fresh timestamp and re-sign it (the sello changes with Fecha). |
| `402` | `cfdi.emisor_not_in_lco` | no | The emitter is not in the SAT’s Lista de Contribuyentes Obligados or its fiscal obligations validation is negative. The taxpayer must resolve their status with the SAT (e.g. active CSD, up-to-date obligations) before stamping. |

### Cancellation — invocation/global codes

Global (non per-UUID) codes returned when submitting `POST /v1/invoices/:id/cancel`.

| PAC/SAT code | Our `code` | Retryable | Remediation |
| - | - | :-: | - |
| `0` | `pac.ok` | no | No action needed — poll the cancellation status to confirm the final SAT verdict. |
| `1` | `pac.auth_failed` | no | The stamping-service credentials were rejected. Do NOT retry — repeated failures can lock the account. This is a server-side configuration issue: contact support so the environment credentials can be verified (test and live credentials are separate). |
| `2` | `pac.contract_invalid` | no | The stamping account is not found, is expired, or has no available stamping credits. This is a server-side account issue: contact support to renew the account or top up stamp credits. |
| `3` | `pac.invalid_parameter` | no | A required parameter was missing or malformed (also returned for a malformed JSON body). Do not retry unchanged — inspect the request payload against the timbrarCfdi contract. |
| `4` | `pac.internal_error` | yes | The service reported a transient internal error. Retry with backoff. |
| `5` | `pac.maintenance` | yes | The stamping service is temporarily offline for maintenance. Retry with backoff after a short delay. |
| `7` | `cancel.sat_service_unavailable` | yes | The SAT cancellation web service is temporarily unavailable. Retry with backoff; SAT outages can last minutes to hours. |
| `20` | `pac.no_default_certificate` | no | No CSD was available to sign the cancellation. Provide the emitter CSD (cerPem/keyPem/keyPassword) on the cancel request, since cancellations are signed. |
| `21` | `cancel.no_acuse` | no | The acuse cannot be retrieved because the CFDI is not cancelled (or was not cancelled through this service). Confirm the cancellation completed before requesting the acuse. |

### Cancellation — per-UUID SAT codes

SAT verdicts on the specific CFDI being cancelled (polled asynchronously).

| PAC/SAT code | Our `code` | Retryable | Remediation |
| - | - | :-: | - |
| `94` | `cancel.not_cancellable` | no | The SAT reports this CFDI as "No Cancelable" (e.g. it is referenced by other live documents). Query related CFDIs to find and resolve the blocker. |
| `96` | `cancel.in_process` | no | The cancellation is in process awaiting the receptor’s response. Keep polling the status; it resolves within 72 business hours. |
| `201` | `cancel.request_received` | no | The SAT received the cancellation request. If the CFDI is "cancelable con aceptación", the receptor has 72 business hours to accept or reject; poll the status until it resolves. |
| `202` | `cancel.already_cancelled` | no | The CFDI was already cancelled. Treat as cancelled — no further action needed. |
| `203` | `cancel.rfc_mismatch` | no | The emisor RFC on the request does not match the CFDI. Send the cancellation from the RFC that issued the CFDI, signed with that RFC’s CSD. |
| `204` | `cancel.not_applicable` | no | This folio cannot be cancelled (e.g. it is referenced by another live CFDI). Query related CFDIs to find the blocker before retrying. |
| `205` | `cancel.uuid_not_found` | no | The UUID does not exist at the SAT. Verify the folio fiscal is correct and that the CFDI was actually stamped in this environment (test vs live). |
| `206` | `cancel.not_primary_sector` | no | The requested cancellation flow requires a Sector Primario CFDI, which this is not. Use the standard cancellation motivo for this document type. |
| `207` | `cancel.motivo_missing` | no | The cancellation motivo is missing or invalid. Provide a valid motivo 01-04 (01 requires a substitute folio). |
| `208` | `cancel.substitution_folio_invalid` | no | The substitute folio (folioSustitucion) is invalid. For motivo 01 it must be the UUID of an already-stamped replacement CFDI. |
| `209` | `cancel.substitution_folio_not_required` | no | A substitute folio was supplied but this motivo does not require one. Omit folioSustitucion for motivos 02/03/04. |
| `210` | `cancel.date_after_declaration` | no | The cancellation request date is after the SAT declaration date for the period. Cancellation is no longer permitted for this CFDI. |
| `211` | `cancel.global_invoice_deadline` | no | The deadline to cancel this factura global has passed. This CFDI can no longer be cancelled. |
| `212` | `cancel.relation_invalid` | no | The declared relation is invalid or does not exist. Verify the related/substitute CFDI exists and is correctly related. |
| `300` | `cancel.user_invalid` | no | The user is not authorized for this cancellation. Verify credentials and that the signing CSD belongs to the emisor RFC. |
| `310` | `cancel.motivo_invalid` | no | The motivo is not valid. Use one of 01-04 per the SAT catalog. |
| `311` | `cancel.substitution_folio_invalid` | no | The substitute folio is not valid. For motivo 01, supply the UUID of a valid, already-stamped replacement CFDI. |
| `312` | `cancel.substitution_folio_not_required` | no | This motivo does not require a substitute folio. Remove folioSustitucion. |
| `313` | `cancel.outside_annual_declaration` | no | The cancellation falls outside the allowed annual declaration window and is no longer permitted. |
| `314` | `cancel.relation_invalid` | no | The declared relation is invalid. Verify the related CFDI and relationship type. |

### Status query (`consultarEstatusComprobante`)

Codes from the SAT status query used during cancellation polling and validation enrichment.

| PAC/SAT code | Our `code` | Retryable | Remediation |
| - | - | :-: | - |
| `0` | `pac.ok` | no | No action needed. |
| `1` | `pac.auth_failed` | no | The stamping-service credentials were rejected. Do NOT retry — repeated failures can lock the account. This is a server-side configuration issue: contact support so the environment credentials can be verified (test and live credentials are separate). |
| `2` | `pac.contract_invalid` | no | The stamping account is not found, is expired, or has no available stamping credits. This is a server-side account issue: contact support to renew the account or top up stamp credits. |
| `3` | `pac.invalid_parameter` | no | A required parameter was missing or malformed (also returned for a malformed JSON body). Do not retry unchanged — inspect the request payload against the timbrarCfdi contract. |
| `4` | `pac.internal_error` | yes | The service reported a transient internal error. Retry with backoff. |
| `5` | `pac.maintenance` | yes | The stamping service is temporarily offline for maintenance. Retry with backoff after a short delay. |
| `6` | `pac.query_incomplete` | yes | The status query did not complete. Retry with backoff. |
| `7` | `cancel.sat_service_unavailable` | yes | The SAT returned an error during the status query. Retry with backoff. |
| `90` | `cfdi.not_found` | no | The comprobante was not found at the SAT. Verify the UUID and that emisor RFC, receptor RFC and Total exactly match the CFDI. |
| `91` | `pac.ok` | no | No action needed — the comprobante was found. |
| `92` | `cfdi.invalid_expression` | no | The status query expression is invalid. Check the Total formatting (decimals must match the CFDI exactly) and the RFCs. |
| `93` | `cfdi.status_indeterminate` | yes | The SAT could not determine the state. Retry the status query later. |

### CFDI lifecycle status (`estatusCfdi`)

These are *states*, not errors. The SAT reports one of these when you query a CFDI; we map it to a normalized `cancel_status` bucket surfaced on the invoice `cancellation` object and on `POST /v1/validations`.

| SAT code | Meaning | Normalized `cancel_status` |
| - | - | - |
| `0` | Desconocido | `unknown` |
| `1` | CFDI no existe | `unknown` |
| `2` | Vigente (aceptación indeterminada) | `none` |
| `3` | Vigente con aceptación | `none` |
| `4` | Vigente sin aceptación | `none` |
| `5` | Vigente no cancelable | `none` |
| `6` | Cancelado (medio no especificado) | `cancelled` |
| `7` | Cancelación en proceso (espera respuesta receptor) | `pending` |
| `8` | Cancelado con aceptación | `cancelled` |
| `9` | Cancelado sin aceptación | `cancelled` |
| `10` | Cancelado por plazo vencido | `cancelled` |
| `99` | Indeterminado (SAT no disponible) | `unknown` |

### Descarga masiva (massive download)

Codes from the SAT descarga masiva service (`POST /v1/downloads`). Note the lifetime/size limits — see /guides/massive-download.

| PAC/SAT code | Our `code` | Retryable | Remediation |
| - | - | :-: | - |
| `404` | `sat.download_uncontrolled_error` | yes | An uncontrolled error occurred on the descarga masiva service. Retry with backoff; if it persists, contact support. |
| `5000` | `sat.download_accepted` | no | No action needed — poll the request status until packages are ready. |
| `5001` | `sat.download_unauthorized` | no | SAT "tercero no autorizado": the FIEL does not match the requested RFC. Ensure the FIEL PFX belongs to the RFC being downloaded and that side (issued/received) matches that RFC’s role. |
| `5002` | `sat.download_request_limit` | no | SAT allows at most 2 lifetime requests with identical parameters. Do NOT retry the identical query — fragment or vary the date range (e.g. split the month into weeks) to make a distinct request. |
| `5003` | `sat.download_too_large` | no | The request exceeds the SAT cap (200,000 XMLs / 1,000,000 metadata records). Fragment the date range into smaller windows and issue separate requests. |
| `5004` | `sat.download_no_data` | no | The SAT found no CFDIs for these filters — this is a terminal, successful, empty result. Do not retry; the range simply has no documents. |
| `5005` | `sat.download_duplicate` | no | An identical request already exists. Reuse the existing request’s id, or vary the date range to create a distinct request. |
| `5007` | `sat.download_package_not_found` | no | The requested package was not found — its download URL likely expired (URLs live \~5 minutes). Re-poll the request status to re-mint the package URLs and download immediately. |
| `5010` | `sat.download_packaging_error` | yes | The SAT failed to package the result. Retry the status poll; if it persists, re-issue the request with a smaller date range. |
| `8000` | `sat.download_generation_error` | yes | The stamping service failed to generate the SAT request. Retry with backoff; if it persists, verify the FIEL, dates and RFC. |

### Descarga masiva lifecycle (`estado`)

SAT download-request lifecycle states, mapped to our public `download.status`.

| SAT estado | Meaning | Our `status` |
| - | - | - |
| `1` | Aceptada | `pending` |
| `2` | En proceso | `pending` |
| `3` | Terminada / lista | `pending` |
| `4` | Descargando paquetes | `pending` |
| `5` | Rechazada | `rejected` |
| `6` | Vencida / expirada | `expired` |
| `7` | Paquetes listos | `ready` |

***

*Regenerate this file with `pnpm openapi`.*


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.