Trabajos asíncronos
Seis de los ocho servicios descargan documentos de portales oficiales y tardan entre 15 y 90
segundos. En lugar de mantener la conexión abierta, la API crea un job: recibes 202 con
su identificador y el resultado se consulta después o se entrega por webhook.
El modelo en una imagen
- POST al endpoint del servicio con
company_id→202 Acceptedy job enqueued. - El worker toma el job y lo pasa a
running. Puede reintentarse hasta 3 veces si el fallo es del portal. - Termina en
succeeded(conresultyfiles) ofailed(conerror). - Tú haces polling con
GET /v1/jobs/{id}o esperas el webhookjob.succeeded/job.failed.
Estados del job
| Estado | Significado | Qué hacer |
|---|---|---|
| queued | En cola, aún no lo toma un worker. | Esperar; se puede cancelar. |
| running | Ejecutándose contra el portal. | Esperar; ya no se puede cancelar. |
| succeeded | Terminó con resultado. | Leer result y descargar files. |
| failed | Terminó con error no reintentable o agotó intentos. | Leer error.code; corregir y volver a solicitar. |
| canceled | Se canceló antes de ejecutarse. | Solicitar de nuevo si hace falta. |
| expired | El resultado superó la retención de 72 h. | Solicitar de nuevo la consulta. |
La respuesta 202
{
"object": "job",
"id": "4d1f8c0a-6b2e-4c93-8f51-2a7d9e3b6c08",
"service": "sat.constancia",
"status": "queued",
"company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
"created_at": "2026-10-31T16:42:07.318920+00:00",
"started_at": null,
"finished_at": null,
"units": 0,
"result": null,
"files": [],
"error": null,
"warnings": [],
"sandbox": false,
"links": {
"self": "/v1/jobs/4d1f8c0a-6b2e-4c93-8f51-2a7d9e3b6c08"
}
}El job incluye units (unidades que consumirá al terminar), sandbox y
links.self con la ruta para consultarlo. Guarda siempre el id: es un UUID, no un
folio corto.
Polling
Consulta cada 2 segundos con un límite de 2 minutos para el primer intento. Si el job sigue en curso, aumenta el intervalo (2 s, 3 s, 5 s) y continúa hasta el límite de retención. No hagas polling más rápido de 1 petición por segundo por job: consume tu rate limit sin acelerar el resultado.
# 1) crea el job
curl -sS -X POST "https://api.434alchemycode.com/v1/sat/constancias" \
-H "Authorization: Bearer $ALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17"}'
# 2) consulta el estado (repite hasta succeeded/failed)
curl -sS "https://api.434alchemycode.com/v1/jobs/4d1f8c0a-6b2e-4c93-8f51-2a7d9e3b6c08" \
-H "Authorization: Bearer $ALCHEMY_API_KEY"
# 3) lista trabajos recientes si perdiste el id
curl -sS "https://api.434alchemycode.com/v1/jobs?status=succeeded&service=sat.constancia&per_page=10" \
-H "Authorization: Bearer $ALCHEMY_API_KEY"El listado acepta page, per_page (máx. 100), status,
service y company_id. Los jobs se ordenan del más reciente al más antiguo.
wait: esperar sin polling
Si necesitas el resultado en la misma petición, envía "wait": true. La API espera hasta 25 s:
- Termina antes de 25 s →
200 OKconstatus: "succeeded". - No termina →
202 Acceptedcon el job en curso; sigue con polling.
curl -sS -X POST "https://api.434alchemycode.com/v1/sat/opiniones" \
-H "Authorization: Bearer $ALCHEMY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17", "wait": true}'
wait=true en procesos masivos: bloquea un hilo de tu servidor y muchos gateways cortan a los 30 s. Para backend, el patrón correcto es 202 + webhook.Cancelación
Sólo se puede cancelar un job en queued. Si ya está running, la API responde
409 JOB_NOT_READY con el estado actual.
curl -sS -X POST "https://api.434alchemycode.com/v1/jobs/4d1f8c0a-6b2e-4c93-8f51-2a7d9e3b6c08/cancel" \
-H "Authorization: Bearer $ALCHEMY_API_KEY"La cancelación no consume unidades. El job queda en canceled, sin result ni
archivos; cancelar un job que ya terminó responde 409 JOB_NOT_READY.
Expiración y archivos
| Recurso | Vigencia | Qué pasa al expirar |
|---|---|---|
Job y result | 72 horas | 410 JOB_EXPIRED; vuelve a solicitar la consulta. |
URL de files[] | 15 minutos | La descarga responde 404 NOT_FOUND; consulta el job para regenerarla. |
Idempotency-Key | 24 horas | La misma clave con el mismo cuerpo crea un job nuevo. |
| Invitación | 7 días | 410 JOB_EXPIRED al aceptarla; pide una nueva. |
Los archivos se guardan fuera de la base de datos. La URL firmada incluye
file_id, p, exp y sig; no requiere encabezado de
autenticación, pero caduca. No la publiques ni la compartas: cualquiera con la URL puede descargar el archivo
mientras esté vigente.
Webhooks de resultado
Cuando termina un job, si tienes un webhook activo con el evento job.succeeded o
job.failed, AlchemyCode envía un POST firmado con el resumen del job. Es la forma recomendada de
enterarte sin polling.
{
"id": "evt_5b1e9c3a7d2f4b68a0c1",
"type": "job.succeeded",
"created_at": "2026-10-31T16:44:13.104221+00:00",
"data": {
"job_id": "4d1f8c0a-6b2e-4c93-8f51-2a7d9e3b6c08",
"service": "sat.constancia",
"status": "succeeded",
"company_id": "9b2f5c1e-7a3d-4f8b-9c21-6d4e8a0b5f17",
"result": {
"rfc": "OMX2503255F9",
"razon_social": "OMNICONMX, S.A. de C.V.",
"estatus_padron": "Activo"
},
"error": null
}
}La firma y los reintentos se explican en Webhooks. El payload del evento es un resumen: para archivos y datos completos consulta el job por API.
Buenas prácticas
- Persiste el
job_idantes de responder a tu usuario; si tu proceso muere, el job sigue vivo. - Trata
queuedyrunningigual: ambos significan "vuelve después". - Usa
Idempotency-Keyal crear jobs desde reintentos automáticos. - No descargues archivos dentro del bucle de polling: hazlo una vez al recibir
succeeded. - Mide tu latencia extremo a extremo desde el POST hasta
finished_at, no sólo la del POST. - Si un job falla por el portal (
PORTAL_UNAVAILABLE), ya se reintentó solo; espera y solicita otro.