Вебхуки и live WebSocket сообщают о задании сразу, как только оно завершается. Но на следующее утро, после деплоя или когда нужно собрать все локали с ошибкой за последний час, они уже не помогут. Момент ушёл, событие исчезло. А задания — нет: каждое остаётся в платформе как долговечная запись ещё долго после того, как процесс, отправивший его, уже отработал.
GET /jobs/localization позволяет вернуться к этим записям. Эндпоинт отдаёт задания от новых к старым, страницами по курсору, с фильтрацией по движку, на котором они выполнялись, или по статусу, с которым завершились. Это канал для навёрстывания: надёжная история, к которой вы обращаетесь, когда не слушали события в реальном времени.
GET /jobs/localizationВпервые работаете с асинхронной локализацией? Начните с Обзор. Эта страница предполагает, что у вас уже есть задания, которые нужно просмотреть. Как и любой другой эндпоинт, этот использует аутентификацию через ваш X-API-Key.
Фильтры и пагинация#
GET /jobs/localization?engineId=eng_abc123&status=completed&limit=20&cursor=...| Параметр | Тип | Описание |
|---|---|---|
engineId | string (необязательно) | Возвращает только задания, выполненные на этом движке локализации (eng_...). |
status | string (необязательно) | Возвращает только задания в одном из этих состояний: queued, processing, completed, completed_with_warnings или failed. |
limit | number (необязательно) | Размер страницы. По умолчанию — 20, максимум — 100. |
cursor | string (необязательно) | Непрозрачный курсор из поля nextCursor предыдущего ответа. Для первой страницы его можно не передавать. |
Оба фильтра необязательны и работают вместе: engineId=eng_abc123&status=failed вернёт задания с ошибкой для одного движка — и только их. Именно такой запрос вы и зададите во время инцидента — покажите всё, что завершилось ошибкой на этом движке — без необходимости сначала вытаскивать все задания организации и фильтровать их на стороне клиента.
cursor — это позиция в потоке результатов, а не номер страницы. Его не нужно вычислять: вы просто получаете его от API. В каждом ответе приходит nextCursor, и это значение вы передаёте обратно, чтобы получить следующую страницу.
Ответ#
Каждая страница содержит массив items и поле nextCursor. На последней странице nextCursor равно null — это условие выхода из цикла, а не ошибка.
{
"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"
}Каждый элемент — это сводка: её достаточно, чтобы найти задание и понять, чем оно закончилось — какая локаль, какая группа, какой статус, когда оно было создано и когда завершилось. Переведённый результат сюда намеренно не включён. Чтобы получить полные outputData и поэтапные steps для одного из этих заданий, возьмите его id и вызовите Get a single job. Список нужен, чтобы найти; отдельный запрос — чтобы прочитать.
Корректно обрабатывайте неизвестные значения статуса
Сопоставляйте известные вам значения статуса, а для всех остальных оставляйте ветку по умолчанию — не нужно ломать consumer из-за значения, которого он ещё не встречал. Терпимо обрабатывать неизвестные значения — правильная защитная стратегия для любого строкового enum, которым вы не управляете: так ваш код продолжит работать, а не упадёт на входных данных, которые не может классифицировать.
Как пройти по всем результатам#
Условие выхода здесь и есть главное: продолжайте запрашивать страницы, пока nextCursor не вернётся как null. Передавайте nextCursor из одного ответа как cursor в следующий, и цикл завершится сам.
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
}Если увеличить limit до 100, число сетевых запросов для большого бэклога сократится; сам результат не изменится — изменится только количество страниц, которые нужно пройти. Здесь нет offset, который может съехать, и нет номера страницы, который нужно держать в синхронизации: курсор хранит вашу позицию, а null подсказывает, когда вы уже дочитали всё.
Что дальше#
У вас уже есть id задания. Канал навёрстывания довёл вас до этого места; дальше можно либо прочитать результат, либо подключить каналы реального времени, чтобы в следующий раз узнать о нём сразу, как только всё произойдёт.
