|
Documentation
Book a DemoPlatform
PlatformMCPCLIAPI
Workflows
GuidesChangelog

Welcome

  • Overview
  • Authentication
  • Errors & status codes
  • Webhook signatures

Localization

  • Overview
  • Create jobs
  • Lock non-translatable keys
  • Track a job group
  • Get a single job
  • List jobs
  • Webhook delivery
  • Live progress (WebSocket)

Pipeline

  • Overview
  • Pre-localization AI edit
  • Human review
  • AI review (post-edit)
  • Rephrase for natural copy
  • Back-translation check
  • Configure the pipeline
  • Observe pipeline runs

Provisioning

  • Overview
  • Create a provisioning job
  • Source types
  • What the AI extracts
  • Webhook delivery
  • Live progress (WebSocket)

Synchronous

  • Localize
  • Recognize

Engine management

  • Engine Suggestions

List jobs

Webhooks and the live WebSocket tell you about a job the moment it resolves. But neither helps the next morning, after a deploy, or when you want every locale that failed in the last hour. The moment passed; the event is gone. The jobs are not – each one is a durable record on the platform, long after the process that submitted it has moved on.

GET /jobs/localization is how you reach back for those records. It returns your jobs newest first, in pages you walk with a cursor, narrowed by the engine they ran on or the status they ended in. This is the catch-up channel: the durable record you query when you weren't listening live.

text
GET /jobs/localization

New to async localization? Start with the Overview. This page assumes you already have jobs to look through. Like every endpoint, it authenticates with your X-API-Key.

Filters and pagination#

text
GET /jobs/localization?engineId=eng_abc123&status=completed&limit=20&cursor=...
ParameterTypeDescription
engineIdstring (optional)Return only jobs that ran on this localization engine (eng_...).
statusstring (optional)Return only jobs in this state: queued, processing, completed, completed_with_warnings, or failed.
limitnumber (optional)Page size. Default 20, maximum 100.
cursorstring (optional)Opaque cursor from the previous response's nextCursor. Omit it for the first page.

Both filters are optional and combine: engineId=eng_abc123&status=failed returns the failed jobs for one engine and nothing else. That combination answers a question you will actually ask in an incident – show me everything that failed on this engine – without pulling back every job in the organization to filter client-side.

The cursor is a position in the result stream, not a page number. You don't compute it; you receive it. Each response hands you a nextCursor, and you pass that value back to fetch the page after it.

Response#

Each page is an items array plus a nextCursor. nextCursor is null on the last page – that is your loop's exit condition, not an error.

json
{
  "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"
}

Each item is a summary – enough to locate a job and read its outcome: which locale, which group, what status, when it was created and finished. It deliberately does not carry the translated output. To pull the full outputData and per-stage steps for one of these jobs, take its id and call Get a single job. List to find; fetch to read.

Handle unknown status values gracefully

Match on the status values you know and fall through to a default branch for the rest, rather than crashing the consumer on a value it hasn't seen. Tolerating an unrecognized value is the defensive default for any string enum you don't own – it keeps your reader running instead of throwing on input it can't classify.

Page through every result#

The exit condition is the whole point: keep requesting until nextCursor comes back null. Pass the nextCursor from one response as the cursor of the next, and the loop terminates on its own.

javascript
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
}

Raising limit to 100 cuts the number of round-trips for a large backlog; it does not change the result, only how many pages you walk to read it. There is no offset to drift and no page count to keep in sync – the cursor carries your place, and null tells you when you've read everything.

Next steps#

You have the id of a job. The catch-up channel got you here; from here you read the result, or wire up the live channels so next time you hear it as it happens.

Get a single job
Take an id from the list and pull its full outputData and per-stage steps.
Track a job group
Know the group? Fetch it directly with rolled-up per-locale counts.
Webhook delivery
Get each result POSTed to you the moment a locale completes.
Live progress (WebSocket)
Stream status as it happens, no polling - the as-it-happens channel.

Was this page helpful?

Max PrilutskiyMax Prilutskiy·Updated 3 days ago·4 min read