Skip to main content

Formato de error

Todos los errores de la API usan el mismo formato JSON:
Los campos opcionales se omiten cuando no aplican: nunca llegan como null.

Rechazos del SAT

Ipsofactura orquesta el timbrado contra una red de PACs con failover automático; ese detalle es interno y no cambia el formato del error. Cuando el SAT rechaza un CFDI, recibes un solo VALIDATION_ERROR con el código de validación del SAT (tipo CFDIxxxxx) en el campo sat_code:

code vs. sat_code

Son dos códigos con dueños distintos, y por eso conviven:
No parsees message. Cuando el rechazo viene del SAT, ese texto lo redacta el PAC que atendió el timbrado: varía entre proveedores y puede cambiar sin previo aviso. Antes era la única forma de saber qué regla se incumplió; hoy esa información es sat_code. Usa message para mostrarlo o registrarlo, nunca para decidir.
Dos reglas más sobre sat_code:
  • Se omite en nuestros propios errores — validación local, autenticación, rate limit, servicio no disponible. Que el campo esté presente ya te dice que fue el SAT quien rechazó el comprobante, no nosotros.
  • Nunca se inventa. Si la respuesta del PAC no trae ningún código, el campo se omite en lugar de adivinarlo. Trátalo siempre como opcional:

Catálogo de códigos

El status HTTP indica la familia del problema; code el motivo exacto.

400 — Error de validación

Tu request tiene un problema. Corrígelo antes de reintentar.

401 / 403 — Autenticación y permisos

Un recurso de otra cuenta responde 404, no 403. Pedir un CFDI o una empresa que existe pero es de alguien más devuelve exactamente la misma respuesta que pedir un id que nunca se emitió: 404 CFDI_NOT_FOUND o 404 EMPRESA_NOT_FOUND, mismo mensaje. Así la API no confirma la existencia de nada que no va a entregar. El único 403 que puedes recibir en una ruta autenticada es ACCOUNT_SUSPENDED.

402 — Límite del plan

404 — No encontrado

409 — Conflicto (duplicados)

413 — Payload demasiado grande

422 — Request no procesable

429 — Rate limit

5xx — Errores del servidor


Rate limits

Los límites se aplican por cuenta, en ventanas fijas de un minuto: Toda respuesta de una ruta con límite incluye los headers X-RateLimit-Limit, X-RateLimit-Remaining y X-RateLimit-Reset (epoch del inicio de la siguiente ventana). Al exceder el límite recibes 429 con code: RATE_LIMIT_EXCEEDED y el header Retry-After en segundos. La tabla completa y el manejo recomendado están en Rate limits.

Reintentos

¿Cuándo reintentar?

Backoff exponencial recomendado

Python

Problemas frecuentes

”Mi CFDI fue timbrado pero no recibí la respuesta”

Puede pasar si la conexión se interrumpió después de que el PAC timbró pero antes de que la respuesta llegara a tu sistema.
  • Si enviaste idempotency_key: reenvía la misma request con la misma llave. Recibes la respuesta original tal cual (Idempotency-Replayed: true) sin timbrar ni cobrar dos veces. Ver Recuperación tras timeout.
  • Si asignas serie y folio propios pero no idempotency_key: reenvía la misma request. Si el CFDI ya se timbró recibirás 409 DUPLICATE_INVOICE (con el id del CFDI existente) o DUPLICATE_IN_PROGRESS si sigue en curso.
  • Si dejas que Ipsofactura asigne el folio y no enviaste idempotency_key: no reenvíes a ciegas — cada envío se consideraría un CFDI nuevo. Consulta primero GET /cfdi y verifica si el comprobante ya aparece, o usa GET /cfdi?serie=&folio=&rfc_emisor= si conoces esos datos.
  • Si recibiste 503 STAMPING_OUTCOME_UNKNOWN: no es un timeout de red común. Ver la sección dedicada en Recuperación tras timeout.

”Mi CSD vence pronto, ¿qué pasa si no lo renuevo?”

Los CFDIs timbrados con el CSD anterior siguen siendo válidos — el SAT no los invalida. Pero a partir del vencimiento recibirás CERTIFICATE_EXPIRED al timbrar, hasta subir el CSD renovado. Monitorea el campo fecha_fin de tus certificados en GET /empresas?include_certificados=true, o el de una sola empresa con GET /empresas/{id}?include_certificados=true. También puedes suscribirte al webhook csd.por_vencer, que avisa a los 30, 15 y 7 días.

”El SAT rechazó mi cancelación”

La cancelación puede requerir aceptación del receptor. Consulta el resultado con GET /cfdi/{id}/estatus: cuando hay actividad de cancelación, el campo estatus_cancelacion trae el detalle del SAT ("En proceso", "Solicitud rechazada", "Plazo vencido", "Cancelado con aceptacion", …). Si el receptor rechazó la cancelación:
  • Negocia con el receptor para que la acepte desde su portal del SAT.
  • Si el CFDI tiene un error de importe, emite una nota de crédito (egreso) por la diferencia.

Errores en el sandbox

El sandbox valida el XSD, las reglas propias de Ipsofactura y un subconjunto curado de las reglas CFDI 4.0 del SAT, y devuelve los errores en el mismo formato que producción, incluido el sat_code con el código tipo CFDIxxxxx. Úsalo para probar el manejo de errores de tu integración: el contrato que verifiques aquí es el mismo que recibirás en producción. Lo que no valida —y producción sí rechaza— está en Qué valida el sandbox y qué no. Para forzar escenarios de error de forma determinista, envía uno de los RFC de receptor mágicos: La tabla completa —con los escenarios de cancelación (SBX010101PRO, SBX010101RCH) y de timbrado tardío (SBX010101LAT)— vive en RFCs mágicos.
Las validaciones del sandbox son representativas, no exhaustivas: un CFDI aceptado en sandbox puede ser rechazado en producción por una regla del SAT que el sandbox no aplica.

Idempotencia

POST /cfdi/timbrar, /cfdi/complemento-pago y /cfdi/cancelar aceptan un idempotency_key en el body. Un reintento con la misma llave devuelve la respuesta del primer intento tal cual, byte a byte, en lugar de volver a ejecutarse — la forma recomendada de manejar timeouts. Ver la guía completa en Recuperación tras timeout. Independientemente de idempotency_key, el timbrado también deduplica por serie + folio + RFC emisor + tipo: dos CFDI con la misma combinación no pueden coexistir, y el segundo intento regresa 409 DUPLICATE_INVOICE en lugar de timbrar y cobrar de nuevo.
Sin idempotency_key ni serie/folio propios, Ipsofactura asigna el consecutivo siguiente en cada request y dos envíos producen dos CFDIs distintos. Para reintentos seguros, asigna tu propia idempotency_key (recomendado) o al menos serie y folio propios.