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 soloVALIDATION_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:
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
serieyfoliopropios pero noidempotency_key: reenvía la misma request. Si el CFDI ya se timbró recibirás409 DUPLICATE_INVOICE(con eliddel CFDI existente) oDUPLICATE_IN_PROGRESSsi 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 primeroGET /cfdiy verifica si el comprobante ya aparece, o usaGET /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ásCERTIFICATE_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 conGET /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 elsat_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.