Saltar al contenido principal

Fallos de emisión masiva (DLQ)

Cuando una emisión asíncrona (vía POST /credentials/issue con 2+ recipients) deja filas sin emitir — por validación, error transitorio, etc. — esas filas quedan persistidas indefinidamente en una bandeja de fallos (Dead Letter Queue). Los siguientes endpoints permiten consultarlas, exportarlas y reintentarlas.

Contrato de visibilidad
  • Durante el job: GET /jobs/{job_id} devuelve el conteo failed en vivo.
  • Hasta 7 días después del job: GET /jobs/{job_id}/failures resuelve el job_id al batch_uuid vía un mapeo Redis y lista los fallos.
  • Después de 7 días: el mapeo expira y el endpoint anterior responde 410 Gone. Usá GET /bulk-failures?batch_uuid=... (la tabla persiste indefinido).
  • La retención automática mueve fallos pending_review > 90 días a discarded, y borra discarded/retried > 365 días.

GET /jobs/{id}/failures

Lista los fallos asociados al job. Solo accesible mientras el mapeo Redis siga vivo (7 días tras el dispatch).

Request

GET /api/v1/jobs/{job_id}/failures

Headers

HeaderRequeridoDescripción
X-API-KeyTu API key

Response (200)

{
"job_id": "abc123-task-id",
"batch_uuid": "550e8400-e29b-41d4-a716-446655440000",
"total_failures": 2,
"failures": [
{
"id": "9f2a4b7d-1c3e-4a5f-8b6c-2d3e4f5a6b7c",
"batch_uuid": "550e8400-e29b-41d4-a716-446655440000",
"achievement_id": "1f2a3b4c-5d6e-7f8a-9b0c-1d2e3f4a5b6c",
"recipient_email": "[email protected]",
"row_data": {
"email": "[email protected]",
"recipient_name": "Juan Perez",
"credits_earned": null
},
"error_category": "validation_error",
"error_message": "credits_earned es requerido",
"attempts_made": 1,
"first_seen_at": "2026-05-09T15:30:00Z",
"last_attempt_at": "2026-05-09T15:30:00Z",
"status": "pending_review",
"retried_credential_id": null
}
]
}

Errores

StatusDetalleCausa
403Access denied to this jobEl job fue creado con otro API key
410Job too old; failures retention expired. Use GET /api/v1/bulk-failures with batch_uuid filter.Pasaron más de 7 días desde el dispatch

GET /bulk-failures

Auditoría histórica con paginación. Tenant-scoped: siempre devuelve solo fallos del tenant del API key.

Request

GET /api/v1/bulk-failures?page=1&page_size=20&status=pending_review

Query Parameters

ParámetroTipoDefaultDescripción
pageint1Página, mínimo 1
page_sizeint20Items por página, máximo 100
achievement_idUUIDFiltra por credencial
error_categorystringvalidation_error, signing_error, image_error, external_service_error, rds_error, unknown
statusstringsin filtropending_review, retried, discarded, resolved
batch_uuidUUIDFiltra por envío específico (útil cuando expiró el mapeo de /jobs/{id}/failures)

Response (200)

{
"page": 1,
"page_size": 20,
"total": 45,
"items": [
{ "id": "...", "batch_uuid": "...", "...": "..." }
]
}

Los campos de cada item son los mismos que en /jobs/{id}/failures.


POST /bulk-failures/{id}/retry

Reintenta una fila fallida. Genera un nuevo batch_uuid internamente para no chocar con la idempotency del envío original.

Request

POST /api/v1/bulk-failures/{failure_id}/retry
Content-Type: application/json
X-API-Key: uc_live_...

{
"row_data_overrides": {
"credits_earned": 5
}
}

Body

CampoTipoNotas
row_data_overridesobject | nullOpcional. Mergea sobre la fila original solo para esta ejecución; la fila almacenada NO se modifica (audit trail). Los overrides aplicados quedan registrados en last_retry_overrides.

Response (200) — éxito

{
"success": true,
"credential_id": "f1e2d3c4-b5a6-7890-1234-567890abcdef",
"error_category": null,
"error_message": null
}

Response (200) — fallo (status sigue pending_review)

{
"success": false,
"credential_id": null,
"error_category": "validation_error",
"error_message": "credits_earned es requerido"
}

Nota: una respuesta con success=false devuelve HTTP 200 (no 4xx) — el reintento se ejecutó pero no produjo una credencial. El failure se actualiza con el nuevo error y el admin/integrador puede reintentar de nuevo.

Errores

StatusDetalleCausa
404Fallo no encontradoEl ID no existe o pertenece a otro tenant

Estructura de un fallo

CampoTipoDescripción
idUUIDID único del fallo
batch_uuidUUIDLote original al que pertenecía la fila
achievement_idUUIDCredencial que se intentó emitir
recipient_emailstringEmail del destinatario
row_dataobjectFila original (mismo shape que el recipient enviado a /credentials/issue). Inmutable: refleja lo que se envió originalmente.
error_categorystringUna de: validation_error, signing_error, image_error, external_service_error, rds_error, unknown
error_messagestringMensaje del último intento
attempts_madeintCuántas veces se procesó (incluye el intento original + retries)
first_seen_atISO 8601Cuándo se registró por primera vez
last_attempt_atISO 8601Último intento
statusstringpending_review, retried, discarded, resolved
retried_credential_idUUID | nullSi status='retried', ID de la credencial creada en el último retry exitoso

Categorías de error

CategoríaSignificadoAcción típica
validation_errorDatos faltantes o inválidos en la fila (ej: campo requerido vacío)Corregir datos vía row_data_overrides y reintentar
signing_errorFalla armando el JSON-LD OB3 o firmandoProbablemente un bug; reportar
external_service_errorServicio externo (Redis, etc.) o error inesperadoReintentar; si persiste, reportar
rds_errorBase de datos transitoriaReintentar
image_errorFalla generando la imagen (no debería aparecer aquí en la versión actual)Reportar
unknownNo categorizadoInspeccionar error_message