Les webhooks et le WebSocket en direct vous signalent un job au moment même où il est résolu. Mais ni l'un ni l'autre ne vous aident le lendemain matin, après un déploiement, ou quand vous voulez retrouver toutes les langues en échec sur la dernière heure. L'instant est passé ; l'événement a disparu. Les jobs, eux, restent là : chacun constitue un enregistrement durable sur la plateforme, bien après que le processus qui l'a soumis soit passé à autre chose.
GET /jobs/localization vous permet justement de retrouver ces enregistrements. Il renvoie vos jobs du plus récent au plus ancien, page par page via un curseur, avec des filtres pour restreindre les résultats au moteur sur lequel ils ont tourné ou au statut avec lequel ils se sont terminés. C'est le canal de rattrapage : l'enregistrement durable que vous interrogez quand vous n'écoutiez pas en direct.
GET /jobs/localizationVous découvrez la localisation asynchrone ? Commencez par la Vue d'ensemble. Cette page part du principe que vous avez déjà des jobs à parcourir. Comme tous les endpoints, elle s'authentifie avec votre X-API-Key.
Filtres et pagination#
GET /jobs/localization?engineId=eng_abc123&status=completed&limit=20&cursor=...| Paramètre | Type | Description |
|---|---|---|
engineId | string (facultatif) | Renvoie uniquement les jobs exécutés sur ce moteur de localisation (eng_...). |
status | string (facultatif) | Renvoie uniquement les jobs dans cet état : queued, processing, completed, completed_with_warnings ou failed. |
limit | number (facultatif) | Taille de page. 20 par défaut, 100 maximum. |
cursor | string (facultatif) | Curseur opaque issu du nextCursor de la réponse précédente. Omettez-le pour la première page. |
Les deux filtres sont facultatifs et peuvent être combinés : engineId=eng_abc123&status=failed renvoie les jobs en échec pour un moteur donné, et rien d'autre. C'est exactement le type de question que vous vous poserez en cas d'incident — montrez-moi tout ce qui a échoué sur ce moteur — sans rapatrier tous les jobs de l'organisation pour ensuite les filtrer côté client.
Le cursor représente une position dans le flux de résultats, pas un numéro de page. Vous ne le calculez pas : vous le recevez. Chaque réponse vous renvoie un nextCursor, et vous repassez cette valeur pour récupérer la page suivante.
Réponse#
Chaque page contient un tableau items ainsi qu'un nextCursor. nextCursor vaut null sur la dernière page : c'est la condition de sortie de votre boucle, pas une erreur.
{
"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"
}Chaque élément est un résumé — juste ce qu'il faut pour repérer un job et lire son issue : quelle langue, quel groupe, quel statut, à quel moment il a été créé et terminé. La sortie traduite n'y figure volontairement pas. Pour récupérer le outputData complet et les steps par étape pour l'un de ces jobs, prenez son id et appelez Get a single job. La liste sert à trouver ; la récupération sert à lire.
Gérez proprement les valeurs de statut inconnues
Faites correspondre les valeurs de statut que vous connaissez et prévoyez un cas par défaut pour le reste, plutôt que de faire planter le consommateur sur une valeur qu'il n'a jamais vue. Tolérer une valeur non reconnue est le réflexe défensif par défaut pour toute enum de chaînes que vous ne contrôlez pas : votre lecteur continue de fonctionner au lieu d'échouer face à une entrée qu'il ne sait pas classer.
Parcourir tous les résultats#
Toute la logique de sortie tient là : continuez à interroger l'endpoint jusqu'à ce que nextCursor revienne à null. Reprenez le nextCursor d'une réponse comme cursor de la suivante, et la boucle s'arrêtera d'elle-même.
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
}Passer limit à 100 réduit le nombre d'allers-retours quand vous avez un gros retard à rattraper ; cela ne change pas le résultat, seulement le nombre de pages à parcourir pour le lire. Il n'y a pas d'offset qui dérive ni de nombre de pages à garder synchronisé : le curseur conserve votre position, et null vous indique quand vous avez tout lu.
Étapes suivantes#
Vous avez l'id d'un job. Le canal de rattrapage vous a amené jusque-là ; à partir d'ici, vous pouvez consulter le résultat, ou brancher les canaux en direct pour l'entendre la prochaine fois au moment où il se produit.
