Referencia
Errores
Los códigos estables, qué significa cada uno y cuáles se pueden reintentar.
Todo 4xx y 5xx sale con la misma envoltura:
{
"error": "Forbidden: falta el permiso `invoices.create`.",
"code": "MISSING_SCOPE",
"requestId": "0f9c2a1b…",
"required": "invoices.create"
}
Ramifica sobre code, nunca sobre error: el texto está en español y nos
reservamos el derecho de reescribirlo.
Códigos
| HTTP | code | Qué pasó | ¿Reintentar? |
|---|---|---|---|
| 400 | BAD_REQUEST | La petición está mal formada. | No |
| 401 | UNAUTHORIZED | Falta la credencial. | No |
| 401 | INVALID_API_KEY | La llave es inválida o fue revocada. | No |
| 402 | INSUFFICIENT_TIMBRES | Sin saldo de timbres. required dice cuántos hacían falta. | No — hay que comprar |
| 403 | FORBIDDEN | La ruta no está disponible para llaves de API. | No |
| 403 | MISSING_SCOPE | A la llave le falta un permiso. required dice cuál. | No — hay que ampliar la llave |
| 403 | ORG_MISMATCH | El slug de la URL no es el de la llave. | No — es configuración |
| 404 | NOT_FOUND | No existe, o es de otra organización. | No |
| 405 | METHOD_NOT_ALLOWED | Método HTTP incorrecto para esa ruta. | No |
| 409 | CONFLICT | Duplicado, o una llave de idempotencia en un estado que no admite reintento. | Depende — ver abajo |
| 412 | — | Falta perfil fiscal o CSD válido. | No — lo resuelve el usuario en Facty |
| 422 | VALIDATION_FAILED | Datos inválidos. Trae fieldErrors. | No, hasta corregir |
| 429 | RATE_LIMITED | Excediste el límite. Respeta Retry-After. | Sí, después de esperar |
| 5xx | INTERNAL | Error nuestro. | Resultado desconocido — reconcilia |
Los que hay que manejar de forma distinta
402 — sin timbres
Detén la cola. Reintentar no crea saldo. Muéstrale al usuario un enlace a la facturación de Facty. Ver Saldo de timbres.
403 MISSING_SCOPE
{ "code": "MISSING_SCOPE", "required": "payments.create" }
Dile al usuario exactamente qué scope agregar a su llave (o crea una nueva con él). No es un error transitorio.
403 ORG_MISMATCH
La llave pertenece a otra organización que la del slug en la URL. Casi siempre es
una llave de producción pegada en la configuración de pruebas, o al revés — y ese
es exactamente el caso que también tiene que detectar tu chequeo de
stampingMode. Revisa la configuración.
404 en vez de 403
Cuando pides un recurso de otra organización, respondemos 404, no 403: la
existencia de un recurso ajeno también es información privilegiada. Así que un
404 puede significar "no existe" o "no es tuyo" — desde tu lado se manejan
igual.
409 — depende del contexto
| Contexto | Significa | Qué haces |
|---|---|---|
POST /clients o /products | Ya existe ese RFC o code. | Usa "upsert": true, o haz PATCH. |
POST /invoices con llave en vuelo | Hay una llamada al PAC corriendo. | Espera y reconcilia. No reintentes. |
POST /invoices con llave fallida | El intento anterior falló. | Corrige y reintenta con allowRetryOnFailed. |
POST /payments/{id}/stamp | El pago ya tiene REP. | Nada que hacer, ya está timbrado. |
422 — errores de validación
{
"code": "VALIDATION_FAILED",
"fieldErrors": {
"items.0.claveProdServ": "Clave SAT inválida (8 dígitos)",
"usoCfdi": "Uso CFDI inválido"
}
}
Las llaves de fieldErrors son rutas con puntos hasta el campo culpable, listas
para pintarlas junto al input correspondiente. Un 422 nunca se arregla
reintentando lo mismo.
429 — límite de tasa
HTTP/1.1 429
Retry-After: 23
600 lecturas y 60 escrituras por minuto, por llave. Espera lo que diga
Retry-After y vuelve a encolar. El límite de escritura es bajo a propósito:
cada escritura puede gastar un timbre.
5xx y timeouts — resultado desconocido
Un timeout no es un fracaso: puede que la petición sí haya llegado. Nunca reintentes a ciegas un timbrado. Reconcilia por tu llave de idempotencia:
curl -s "$FACTY_HOST/api/authenticated/$FACTY_ORG/invoices?idempotencyKey=mi-erp:prod:factura:4471" \
-H "X-API-Key: $FACTY_KEY" | jq
Ver Idempotencia y reintentos.
Errores que vienen del PAC o del SAT
Algunos rechazos no los genera Facty sino el proveedor de timbrado o el propio
SAT, y llegan como 422 o 502 con el mensaje original. Los más comunes:
| Síntoma | Causa habitual |
|---|---|
| El RFC del receptor no está en el padrón | RFC mal capturado, o el contribuyente no está activo. |
| Nombre o CP del receptor no coincide | La razón social debe ir exactamente como en la Constancia de Situación Fiscal: sin puntos, sin el régimen de capital. |
| UsoCFDI no válido para el régimen del receptor | Ese uso no aplica a ese régimen fiscal. |
| CSD vencido o revocado | El usuario debe subir el certificado nuevo en Facty. |
| Fecha fuera de rango | El CFDI se timbra con la hora del PAC; hay tolerancia de 72 horas hacia atrás. |
Estos los resuelve el usuario corrigiendo datos, no tu código reintentando.
Qué registrar de tu lado
Guarda por cada operación fiscal:
- tu
idempotencyKey, - el
requestIdde la respuesta, - el
uuidsi se timbró, - el
codey elfieldErrorssi falló.
Con eso, cualquier consulta a soporte se resuelve en minutos en vez de días.