Los webhooks y el WebSocket en vivo te avisan de un trabajo en cuanto se resuelve. Pero ninguno te sirve a la mañana siguiente, después de un deploy, o cuando quieres ver todos los idiomas que fallaron en la última hora. Ese momento ya pasó; el evento ya no está. Los trabajos, no: cada uno queda como un registro persistente en la plataforma, mucho después de que el proceso que lo envió haya seguido adelante.
GET /jobs/localization es la forma de volver a esos registros. Devuelve tus trabajos del más reciente al más antiguo, en páginas que recorres con un cursor, filtrados por el motor en el que se ejecutaron o por el estado en el que terminaron. Este es el canal para ponerte al día: el registro persistente que consultas cuando no estabas escuchando en vivo.
GET /jobs/localization¿Recién empiezas con la localización asíncrona? Empieza por la Descripción general. Esta página asume que ya tienes trabajos para revisar. Como cualquier endpoint, se autentica con tu X-API-Key.
Filtros y paginación#
GET /jobs/localization?engineId=eng_abc123&status=completed&limit=20&cursor=...| Parámetro | Tipo | Descripción |
|---|---|---|
engineId | string (opcional) | Devuelve solo los trabajos que se ejecutaron en este motor de localización (eng_...). |
status | string (opcional) | Devuelve solo los trabajos con este estado: queued, processing, completed, completed_with_warnings o failed. |
limit | number (opcional) | Tamaño de página. Valor predeterminado: 20. Máximo: 100. |
cursor | string (opcional) | Cursor opaco tomado del nextCursor de la respuesta anterior. Omítelo en la primera página. |
Ambos filtros son opcionales y se pueden combinar: engineId=eng_abc123&status=failed devuelve los trabajos fallidos de un motor y nada más. Esa combinación responde una pregunta que de verdad harás en medio de un incidente: muéstrame todo lo que falló en este motor , sin traer todos los trabajos de la organización para filtrarlos del lado del cliente.
El cursor es una posición dentro del flujo de resultados, no un número de página. No lo calculas; lo recibes. Cada respuesta te entrega un nextCursor, y tú reenvías ese valor para pedir la página siguiente.
Respuesta#
Cada página incluye un arreglo items y un nextCursor. nextCursor es null en la última página: esa es la condición de salida de tu bucle, no un error.
{
"items": [
{
"id": "ljb_C3d4E5f6G7h8I9j0",
"groupId": "ljg_A1b2C3d4E5f6G7h8",
"targetLocale": "ja",
"status": "completed",
"warnings": [],
"createdAt": "2026-03-16T10:30:00.000Z",
"completedAt": "2026-03-16T10:30:06.000Z"
}
],
"nextCursor": "eyJ0IjoiMjAyNi0wMy0xNlQxMDozMDowMC4wMDBaIiwiaSI6ImxqYl9CMmMzRDRlNUY2ZzdIOGk5In0"
}Cada elemento es un resumen: suficiente para ubicar un trabajo y leer su resultado: qué idioma, qué grupo, qué estado, cuándo se creó y cuándo terminó. A propósito, no incluye la salida traducida. Para obtener el outputData completo y los steps por etapa de uno de estos trabajos, toma su id y llama a Obtener un solo trabajo. Lista para encontrar; consulta para leer.
Maneja con elegancia los valores de estado desconocidos
Haz match con los valores de estado que conoces y deja que el resto caiga en una rama por defecto, en lugar de hacer fallar al consumidor ante un valor que no había visto. Tolerar un valor no reconocido es la postura defensiva por defecto para cualquier enum de strings que no controlas: mantiene tu lector en funcionamiento en vez de lanzar un error con una entrada que no puede clasificar.
Recorre todos los resultados#
La condición de salida es la clave: sigue solicitando hasta que nextCursor vuelva como null. Pasa el nextCursor de una respuesta como el cursor de la siguiente, y el bucle termina por sí solo.
async function listFailedJobs(engineId) {
const failed = [];
let cursor = undefined; // first page: no cursor
do {
const url = new URL("https://api.lingo.dev/jobs/localization");
url.searchParams.set("engineId", engineId);
url.searchParams.set("status", "failed");
url.searchParams.set("limit", "100"); // fewer round-trips
if (cursor) url.searchParams.set("cursor", cursor);
const response = await fetch(url, {
headers: { "X-API-Key": process.env.LINGO_API_KEY },
});
const { items, nextCursor } = await response.json();
failed.push(...items);
cursor = nextCursor; // null on the last page -> loop ends
} while (cursor);
return failed; // every failed job for this engine
}Subir limit a 100 reduce la cantidad de viajes de ida y vuelta cuando hay un backlog grande; no cambia el resultado, solo cuántas páginas recorres para leerlo. No hay offset que se desplace ni conteo de páginas que mantener sincronizado: el cursor conserva tu posición, y null te dice cuándo ya lo leíste todo.
Siguientes pasos#
Ya tienes el id de un trabajo. El canal para ponerte al día te trajo hasta aquí; desde acá puedes leer el resultado o conectar los canales en vivo para que la próxima vez te enteres en el momento en que ocurre.
