|
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

Get a single job

Read one locale's translated output and the per-stage record of how it was produced.

You reach for this once you hold a jobId – returned in the 202 from create, carried in a webhook, or listed under a job group. The group endpoint tells you how many locales are done. This endpoint tells you what a single locale produced, and what happened along the way.

text
GET /jobs/localization/:jobId

New to async localization? Start with the Overview.

That distinction is the whole job of this page. A group response is a scoreboard – counts and per-job status, covered on the job-group page. A single job is the full record of one locale: the translated outputData, the terminal status, any warnings, and a steps[] trail of every stage the pipeline ran. When you're ready to write the German copy into your database, this is the call that hands you the German copy.

Authentication#

Pass your API key in the X-API-Key header. Keys are organization-scoped and reach every engine in the org. See Authentication for details.

Response#

The outputData field mirrors the structure of the input data, with every string value translated and every non-string value (numbers, booleans, null) preserved in place. Same keys, same nesting, same array order – only the strings change.

json
{
  "id": "ljb_A1b2C3d4E5f6G7h8",
  "groupId": "ljg_A1b2C3d4E5f6G7h8",
  "targetLocale": "de",
  "status": "completed",
  "outputData": {
    "id": "course_101",
    "title": "Einführung in maschinelles Lernen",
    "steps": [
      { "heading": "Was ist ML?", "body": "Maschinelles Lernen ist ein Teilbereich der künstlichen Intelligenz." },
      { "heading": "Überwachtes Lernen", "body": "Trainieren eines Modells mit gelabelten Daten." }
    ],
    "metadata": { "author": "Dr. Smith", "difficulty": "beginner" }
  },
  "errorMessage": null,
  "warnings": [],
  "callbackStatus": "delivered",
  "createdAt": "2026-03-16T10:30:00.000Z",
  "startedAt": "2026-03-16T10:30:01.000Z",
  "completedAt": "2026-03-16T10:30:04.000Z",
  "steps": [
    {
      "stepId": "localize",
      "type": "action",
      "status": "completed",
      "errorMessage": null,
      "externalRefType": null,
      "externalRefId": null,
      "externalRefUrl": null,
      "createdAt": "2026-03-16T10:30:01.000Z",
      "startedAt": "2026-03-16T10:30:01.000Z",
      "completedAt": "2026-03-16T10:30:04.000Z"
    }
  ]
}

The metadata block above survived untouched – Dr. Smith and beginner are non-string leaves the engine left alone. The outputData you read back fits the shape you sent, so the same code that built the payload can consume the translation.

FieldDescription
idThis job's own ID (ljb_…). The value you passed in the path.
groupIdThe parent job group (ljg_…) this job belongs to. Pass it to the job-group endpoint to see every sibling locale at once.
targetLocaleThe BCP-47 locale this job translated into – one job exists per target locale. This is the field you branch on to route outputData to the right column or file.
statusqueued, processing, completed, completed_with_warnings, or failed.
outputDataTranslated content matching the input structure. Present when status is completed or completed_with_warnings.
errorMessageError description. Present when status is failed, otherwise null.
warningsNon-critical pipeline stage failures. Each entry is { step, message }. Empty unless status is completed_with_warnings.
callbackStatusWebhook delivery state: pending, delivered, or failed. null if no callback URL is configured.
createdAtWhen the job was accepted (the timestamp on the 202 that created it).
startedAtWhen the engine began translating this locale. Set once the job leaves queued.
completedAtWhen the job reached a terminal state. Set once status is completed, completed_with_warnings, or failed.
stepsPer-stage execution records. Always contains the localize step, plus one entry per enabled optional pipeline stage. Full record shape in Observe pipeline runs.

outputData is null until the job finishes

While status is queued or processing, outputData is empty and errorMessage is null – there is nothing to read yet. Read outputData only after status reaches completed or completed_with_warnings; on failed, read errorMessage instead. Branch on status first, then touch the payload.

Job status values#

A job moves from queued to processing to exactly one terminal state. Branch on status before you read anything else – it tells you which fields are populated.

StatusMeaningWhat to read
queuedAccepted, not yet started.Nothing yet – poll, or wait for the webhook.
processingThe engine is translating this locale.Nothing yet.
completedTranslation finished, every enabled stage succeeded.outputData.
completed_with_warningsTranslation finished and outputData is complete, but a non-critical pipeline stage fell through.outputData, then warnings.
failedThe job did not produce a translation.errorMessage.

completed_with_warnings still ships a translation

completed_with_warnings is not a soft failure. You get full outputData – the core translate step succeeded. What changed is that a non-critical stage (for example pre-edit or back-translation) did not complete, and each failure is logged in warnings as { step, message }. Treat the output as usable; treat warnings as a quality signal worth surfacing to whoever reviews translations. Only failed means there is no translation to read.

Handle unknown status values

The five status values above are the contract today. Pipeline stages evolve, so treat status as an open set: branch on the values you know and route anything unexpected to a default that reads outputData if present and logs otherwise. A switch with no fallback is the line that breaks on the day a new state ships.

The steps array#

steps[] is the per-stage trail behind a single job – one record for every stage the engine ran, in order. Every job carries at least the localize step, because core translation always runs. Each optional pipeline stage you enabled adds one more record. So a job with no extra stages shows a single localize step; a job with pre-edit and back-translation enabled shows three.

This is what makes a job auditable rather than a black box. You don't have to trust that a stage ran – you read its record: which stage (stepId), whether it completed, failed, or was skipped, what it cost (costUsd), and when it started and finished. For human-review stages, externalRef* points at the external record.

json
"steps": [
  {
    "stepId": "preEdit",
    "type": "action",
    "status": "completed",
    "errorMessage": null,
    "costUsd": 0.0012,
    "createdAt": "2026-03-16T10:30:01.000Z",
    "completedAt": "2026-03-16T10:30:02.000Z"
  },
  {
    "stepId": "localize",
    "type": "action",
    "status": "completed",
    "errorMessage": null,
    "costUsd": 0.0184,
    "createdAt": "2026-03-16T10:30:02.000Z",
    "completedAt": "2026-03-16T10:30:05.000Z"
  }
]

A failed entry here does not necessarily fail the job. When a non-critical stage fails, its steps[] record reads failed, the same failure surfaces in the job's top-level warnings, and the job still reaches completed_with_warnings with full outputData. The full record shape – every field, every stepId, the completed/failed/skipped semantics – lives on one canonical page: Observe pipeline runs. This page shows you where to find it on a job; that page specifies it.

Reading a completed job#

A typical consumer branches on status, writes outputData on success, and logs errorMessage on failure. The copy-paste call below returns the payload shown above.

javascript
const jobId = "ljb_A1b2C3d4E5f6G7h8";
const response = await fetch(`https://api.lingo.dev/jobs/localization/${jobId}`, {
  headers: { "X-API-Key": process.env.LINGO_API_KEY },
});

const job = await response.json();

switch (job.status) {
  case "completed":
  case "completed_with_warnings":
    // outputData is populated; warnings may carry non-critical stage failures
    await db.content.update({
      where: { id: job.outputData.id },
      data: { [`content_${job.targetLocale}`]: job.outputData },
    });
    if (job.warnings.length) console.warn(job.targetLocale, job.warnings);
    break;
  case "failed":
    console.error(`${job.targetLocale} failed: ${job.errorMessage}`);
    break;
  default:
    // queued or processing - nothing to read yet; also catches future states
    break;
}

Polling vs push

This endpoint is a point-in-time read. For most jobs the engine takes 2–8 seconds per locale, so if you poll, a 2-second interval is a reasonable start. To avoid polling entirely, register a webhook and fetch the job only when it tells you the locale is done, or watch the whole group over the WebSocket. Either way, a final GET here is the canonical read of outputData.

When this endpoint returns an error – an unknown jobId, a missing key – it follows the standard JSON error model. See Errors and status codes.

Next steps#

Track a job group
Read aggregate counts and handle partial failures across every locale
List jobs
Page through jobs with cursor pagination and filter by status or engine
Live progress (WebSocket)
Read per-locale status as each job finishes, without polling

Was this page helpful?

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