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

HTTPcodeQué pasó¿Reintentar?
400BAD_REQUESTLa petición está mal formada.No
401UNAUTHORIZEDFalta la credencial.No
401INVALID_API_KEYLa llave es inválida o fue revocada.No
402INSUFFICIENT_TIMBRESSin saldo de timbres. required dice cuántos hacían falta.No — hay que comprar
403FORBIDDENLa ruta no está disponible para llaves de API.No
403MISSING_SCOPEA la llave le falta un permiso. required dice cuál.No — hay que ampliar la llave
403ORG_MISMATCHEl slug de la URL no es el de la llave.No — es configuración
404NOT_FOUNDNo existe, o es de otra organización.No
405METHOD_NOT_ALLOWEDMétodo HTTP incorrecto para esa ruta.No
409CONFLICTDuplicado, o una llave de idempotencia en un estado que no admite reintento.Depende — ver abajo
412Falta perfil fiscal o CSD válido.No — lo resuelve el usuario en Facty
422VALIDATION_FAILEDDatos inválidos. Trae fieldErrors.No, hasta corregir
429RATE_LIMITEDExcediste el límite. Respeta Retry-After.Sí, después de esperar
5xxINTERNALError 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

ContextoSignificaQué haces
POST /clients o /productsYa existe ese RFC o code.Usa "upsert": true, o haz PATCH.
POST /invoices con llave en vueloHay una llamada al PAC corriendo.Espera y reconcilia. No reintentes.
POST /invoices con llave fallidaEl intento anterior falló.Corrige y reintenta con allowRetryOnFailed.
POST /payments/{id}/stampEl 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íntomaCausa habitual
El RFC del receptor no está en el padrónRFC mal capturado, o el contribuyente no está activo.
Nombre o CP del receptor no coincideLa 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 receptorEse uso no aplica a ese régimen fiscal.
CSD vencido o revocadoEl usuario debe subir el certificado nuevo en Facty.
Fecha fuera de rangoEl 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 requestId de la respuesta,
  • el uuid si se timbró,
  • el code y el fieldErrors si falló.

Con eso, cualquier consulta a soporte se resuelve en minutos en vez de días.