Submit content for translation to multiple locales in a single request. The API creates one job per target locale, processes them independently through your localization engine, and delivers results via webhooks or WebSocket.
Create localization jobs#
POST /jobs/localization| Parameter | Type | Description |
|---|---|---|
sourceLocale | string | BCP-47 source locale (e.g., en) |
targetLocales | string[] | Array of BCP-47 target locales (e.g., ["de", "fr", "ja"]) |
data | object | Key-value content to translate. Supports nested objects and arrays. |
hints | object (optional) | Contextual hints per key (array of breadcrumb strings) |
callbackUrl | string (optional) | HTTPS webhook URL for this job group. Overrides the organization default. |
idempotencyKey | string (optional) | Client-generated UUID to prevent duplicate job groups. |
engineId | string (optional) | Localization engine ID. Uses the organization's default engine if omitted. |
Request#
The data field accepts flat key-value pairs or nested structures with objects and arrays at any depth. The localization engine translates all string values, preserves non-string values (numbers, booleans, null) as-is, and maintains the exact structure of the input.
{
"sourceLocale": "en",
"targetLocales": ["de", "fr", "ja"],
"data": {
"lesson_title": "Introduction to Machine Learning",
"lesson_summary": "This lesson covers the fundamentals of ML, including supervised and unsupervised learning."
},
"callbackUrl": "https://your-app.com/webhooks/translations",
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000"
}HTTPS required
The callbackUrl must use HTTPS. HTTP URLs are rejected with a 400 error.
Response (202 Accepted)#
{
"groupId": "ljg_A1b2C3d4E5f6G7h8",
"status": "pending",
"jobs": [
{ "id": "ljb_A1b2C3d4E5f6G7h8", "targetLocale": "de", "status": "queued" },
{ "id": "ljb_B2c3D4e5F6g7H8i9", "targetLocale": "fr", "status": "queued" },
{ "id": "ljb_C3d4E5f6G7h8I9j0", "targetLocale": "ja", "status": "queued" }
],
"createdAt": "2026-03-16T10:30:00.000Z"
}Examples#
const response = await fetch("https://api.lingo.dev/jobs/localization", {
method: "POST",
headers: {
"X-API-Key": process.env.LINGO_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
sourceLocale: "en",
targetLocales: ["de", "fr", "ja"],
data: {
title: "Introduction to Machine Learning",
steps: [
{ heading: "What is ML?", body: "Machine learning is a subset of AI." },
{ heading: "Supervised Learning", body: "Training with labeled data." },
],
},
callbackUrl: "https://your-app.com/webhooks/translations",
}),
});
const { groupId, jobs } = await response.json();
// 202 back in milliseconds. 3 jobs queued.
// Results arrive via webhook as each language completes.
console.log(groupId); // "ljg_A1b2C3d4E5f6G7h8"
console.log(jobs.length); // 3Get a job group#
Retrieve the status of a job group and all its child jobs. Use this to build progress indicators or check overall completion.
GET /jobs/localization/groups/:groupIdResponse#
{
"groupId": "ljg_A1b2C3d4E5f6G7h8",
"status": "processing",
"sourceLocale": "en",
"totalJobs": 3,
"completedJobs": 1,
"failedJobs": 0,
"jobs": [
{ "id": "ljb_A1b2C3d4E5f6G7h8", "targetLocale": "de", "status": "completed", "completedAt": "2026-03-16T10:30:04.000Z" },
{ "id": "ljb_B2c3D4e5F6g7H8i9", "targetLocale": "fr", "status": "processing", "completedAt": null },
{ "id": "ljb_C3d4E5f6G7h8I9j0", "targetLocale": "ja", "status": "queued", "completedAt": null }
],
"createdAt": "2026-03-16T10:30:00.000Z"
}| Group status | Meaning |
|---|---|
pending | Group created, no jobs started yet |
processing | At least one job is in progress |
completed | All jobs completed successfully |
partial | Some jobs completed, some failed |
failed | All jobs failed |
Polling interval
For most jobs, processing takes 2-8 seconds per language. If you're polling instead of using webhooks or WebSocket, a 2-second interval is a reasonable starting point.
Get a single job#
Retrieve full details for one job, including translated output when complete.
GET /jobs/localization/:jobIdResponse#
The outputData field mirrors the structure of the input data with all string values translated. Nested objects, arrays, and non-string values are preserved as-is.
{
"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,
"callbackStatus": "delivered",
"createdAt": "2026-03-16T10:30:00.000Z",
"startedAt": "2026-03-16T10:30:01.000Z",
"completedAt": "2026-03-16T10:30:04.000Z"
}| Field | Description |
|---|---|
status | queued, processing, completed, or failed |
outputData | Translated content matching the input structure. Present when status is completed. |
errorMessage | Error description. Present when status is failed. |
callbackStatus | Webhook delivery state: pending, delivered, or failed. |
List jobs#
GET /jobs/localization?engineId=eng_abc123&status=completed&limit=20&cursor=...Returns a paginated list of jobs, ordered by creation time (newest first). Filter by engineId or status. Use the nextCursor value from the response to fetch subsequent pages.
| Parameter | Type | Description |
|---|---|---|
engineId | string (optional) | Filter by localization engine ID |
status | string (optional) | Filter by job status: queued, processing, completed, or failed |
limit | number (optional) | Max results per page (default 20, max 100) |
cursor | string (optional) | Opaque cursor from a previous response's nextCursor |
Response#
{
"items": [
{
"id": "ljb_C3d4E5f6G7h8I9j0",
"groupId": "ljg_A1b2C3d4E5f6G7h8",
"targetLocale": "ja",
"status": "completed",
"createdAt": "2026-03-16T10:30:00.000Z",
"completedAt": "2026-03-16T10:30:06.000Z"
}
],
"nextCursor": "eyJ0IjoiMjAyNi0wMy0xNlQxMDozMDowMC4wMDBaIiwiaSI6ImxqYl9CMmMzRDRlNUY2ZzdIOGk5In0"
}Best practices#
Use idempotency keys#
If your application might submit the same translation request twice (e.g., due to retries or duplicate events), pass an idempotencyKey. The platform returns the existing job group instead of creating a duplicate. Keys are scoped per localization engine.
A natural idempotency key combines content identity with version: {contentId}-v{contentVersion}. This ensures a new job group is created only when the content actually changes.
Handle partial failures#
Each language is an independent job. If German succeeds but Japanese fails, the German translation is delivered normally via webhook. The failed Japanese job appears with status: "failed" and an errorMessage. The job group status becomes partial.
To retry failed languages, submit a new request with only the failed locales and a fresh idempotency key.
