GET /jobs/{id}
Consulta el estado de un job de emisión masiva asíncrona.
Request
GET /api/v1/jobs/{job_id}
Headers
| Header | Requerido | Descripción |
|---|---|---|
X-API-Key | Si | Tu API key |
Path Parameters
| Parámetro | Tipo | Descripción |
|---|---|---|
job_id | string | ID del job retornado por POST /credentials/issue |
Response: En progreso
{
"job_id": "abc123-task-id",
"status": "PROGRESS",
"progress": {
"current": 25,
"total": 50,
"status": "Procesando fila 25 de 50"
},
"batch_uuid": "550e8400-e29b-41d4-a716-446655440000"
}
Si el job termina con failed > 0, podés consultar los fallos persistidos por fila usando el batch_uuid que aparece en este response. Mientras el mapeo Redis no expire (7 días), también podés usar GET /jobs/{id}/failures. Después, usá GET /bulk-failures?batch_uuid=....
progress.currentInternamente, los lotes asíncronos pueden correr en una arquitectura por chunks (100 filas por chunk). Cuando esa ruta está activa, progress.current avanza en saltos de 100 (cuando un chunk termina) en vez de fila por fila, y progress.status se ve como "Procesando 200 de 1000..." en lugar de "Procesando fila 25 de 50". El shape del response es idéntico; solo cambia la granularidad del avance. No requiere cambios en el cliente.
Response: Completado
{
"job_id": "abc123-task-id",
"status": "SUCCESS",
"result": {
"total": 50,
"successful": 48,
"failed": 2,
"errors": [
{"row": 12, "email": "invalido@test", "error": "Invalid email: invalido@test"},
{"row": 37, "email": "[email protected]", "error": "Calificación inválida"}
],
"emails_sent": 48,
"emails_failed": 0,
"internal_count": 30,
"external_count": 18
}
}
Response: Fallido
{
"job_id": "abc123-task-id",
"status": "FAILURE",
"error": "Achievement not found"
}
Estados del job
| Status | Significado |
|---|---|
PENDING | En cola, esperando ser procesado |
STARTED | El worker comenzó a procesar |
PROGRESS | En progreso, con info de avance |
SUCCESS | Completado exitosamente |
FAILURE | Fallo (ver campo error) |
Campos del resultado (SUCCESS)
| Campo | Descripción |
|---|---|
total | Total de recipientes procesados |
successful | Credenciales emitidas exitosamente |
failed | Recipientes con errores |
errors | Detalle de errores por fila |
emails_sent | Notificaciones enviadas |
emails_failed | Notificaciones fallidas |
internal_count | Recipientes internos (miembros de la org) |
external_count | Recipientes externos |
Patron de polling recomendado
import requests
import time
def poll_job(job_id, api_key, interval=2, max_attempts=300):
"""Consulta el estado de un job cada `interval` segundos."""
headers = {"X-API-Key": api_key}
base_url = "https://app.unicreda.com/api/v1"
for attempt in range(max_attempts):
response = requests.get(f"{base_url}/jobs/{job_id}", headers=headers)
data = response.json()
if data["status"] == "SUCCESS":
return data["result"]
elif data["status"] == "FAILURE":
raise Exception(f"Job failed: {data['error']}")
elif data["status"] == "PROGRESS":
progress = data.get("progress", {})
current = progress.get("current", 0)
total = progress.get("total", 0)
print(f" Progress: {current}/{total}")
time.sleep(interval)
raise TimeoutError("Job did not complete in time")
Errores
| Status | Detalle | Causa |
|---|---|---|
403 | Access denied to this job | El job fue creado con otro API key |
Ejemplo
curl -X GET "https://app.unicreda.com/api/v1/jobs/abc123-task-id" \
-H "X-API-Key: uc_live_tu_key_aqui"