Feedback about your translations rarely arrives as a dashboard click. It is a line in your own support tool, a note from a reviewer, a row in your own QA queue – "stop translating the product name", "use the formal register in German". The Engine Suggestions API turns that free text into engine changes from code: send the feedback as text, the platform reasons over it, and concrete, structured edits to your engine's glossary, instructions, or brand voice come back for you to apply.
This is the programmatic counterpart to the dashboard feature. There, suggestions are generated automatically when your AI Reviewers score a translation low; here, you supply the signal as text. Either way the output is the same – pending suggestions you review and apply.
The flow is two halves. Generation is asynchronous – you hand over feedback and the platform reasons over it in the background, landing pending suggestions on the engine. Review is synchronous – you list the pending suggestions, read what each proposes, and apply or dismiss each one. This page covers both. For the dashboard experience – automatic generation from low review scores, the Suggestions tab, notifications – see Engine Suggestions.
A configuration endpoint, not a translation one
These endpoints read and change an engine's configuration – its glossary, instructions, and brand voice. They are scoped to a single engine by its :id, and authenticate with the same organization-scoped X-API-Key as the rest of the API. They never translate content or alter past translations; an applied suggestion takes effect on the engine's next translation.
Authentication
Pass your API key in the X-API-Key header. Keys are organization-scoped and reach every engine in the organization. See Authentication for details, and Errors and status codes for the error model every endpoint here shares.
Generate from feedback#
POST /engines/:id/suggestions/from-textSend a plain-text description of what the engine is getting wrong. The platform reasons over that text plus the engine's current configuration, and proposes atomic edits – it will not re-propose something the engine already has. Generation runs asynchronously, so the call returns the moment the work is accepted, not when the suggestions are ready.
| Parameter | Type | Description |
|---|---|---|
id (path) | string | The engine to generate suggestions for. |
text | string | Free-text feedback about the engine's output. 1–10,000 characters; must contain at least one non-whitespace character. |
const response = await fetch(
`https://api.lingo.dev/engines/${engineId}/suggestions/from-text`,
{
method: "POST",
headers: {
"X-API-Key": process.env.LINGO_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Our German (de-DE) translations keep using the informal 'du'. For our B2B audience they must always use the formal 'Sie'.",
}),
},
);
const { enqueued } = await response.json();
console.log(enqueued); // true – generation accepted, running in the background{ "enqueued": true }enqueued: true means the platform accepted the work, not that suggestions exist yet. Generation is one background step – it reads your text, reasons over the config, de-duplicates against what is already there, and persists whatever it proposes. A run can legitimately propose nothing (the feedback was vague, or the engine already covers it). Read the results by listing the engine's suggestions a few moments later.
Empty feedback is rejected
text must contain a real message. An empty string, or whitespace only, is rejected with a 400 – it is not silently turned into a different kind of request. Send something the model can actually reason over.
Generate from review scores instead
The same low-score trigger that powers the dashboard is available from code: POST /engines/:id/suggestions/generate (empty body) asks the platform to propose edits from the engine's recent low-scoring AI reviews instead of from text. Same { "enqueued": true } response, same pending suggestions out. Reach for from-text when you have specific written feedback; reach for generate to pull suggestions from what your reviewers have already flagged.
List pending suggestions#
GET /engines/:id/suggestionsReturns the engine's suggestions – the result of any generation run, whether triggered from text, from the manual button, or automatically from low review scores. Each entry is a proposed edit with its reasoning attached.
[
{
"id": "egs_A1b2C3d4E5f6G7h8",
"ownerOrganizationId": "org_X1y2Z3a4B5c6D7e8",
"ownerEngineId": "eng_X1y2Z3a4B5c6D7e8",
"actionType": "add_instruction",
"targetKind": "instruction",
"targetId": null,
"targetLocale": "de-DE",
"payload": { "instruction": "Use the formal 'Sie' form in all German translations; never use the informal 'du'." },
"reasoning": "Feedback states the B2B audience requires formal address, but the engine has no instruction enforcing it.",
"sourceReviewLogIds": [],
"status": "pending",
"appliedTargetId": null,
"createdAt": "2026-06-18T10:30:00.000Z"
}
]| Field | Description |
|---|---|
id | egs_-prefixed suggestion identifier. Pass it to apply or dismiss. |
actionType | One of add_glossary_item, update_glossary_item, add_instruction, update_instruction, add_brand_voice, update_brand_voice. |
targetKind | The part of the engine the edit touches: glossary_item, instruction, or brand_voice. |
targetId | For an update_* action, the id of the entry to change (gli_ / ins_ / bvc_). null for an add_* action. |
targetLocale | The locale the suggestion applies to. |
payload | The ready-to-apply edit. Its fields depend on targetKind – it is exactly what the create/update operation needs, which is why applying requires no further input from you. |
reasoning | A short explanation of why this edit is proposed. |
sourceReviewLogIds | The review logs whose failures motivated the suggestion (esrl_ ids); empty when the suggestion came from feedback text. |
status | pending, applied, or dismissed. |
appliedTargetId | The entry created or updated once the suggestion is applied; null while pending. |
The payload is the detail that makes applying cheap: the proposed change is fully structured at generation time, so applying it is a plain write, not another round of AI. You decide; the platform does not re-reason.
Apply a suggestion#
POST /engine-suggestions/:id/applyWrites the proposed change into the engine and marks the suggestion applied. This is a deterministic write of the payload you already saw in the list – there is no second AI call, so what you reviewed is exactly what gets written. An add_* suggestion creates a new glossary item, instruction, or brand voice; an update_* suggestion changes the existing entry named by targetId.
const response = await fetch(
`https://api.lingo.dev/engine-suggestions/${suggestionId}/apply`,
{
method: "POST",
headers: { "X-API-Key": process.env.LINGO_API_KEY },
},
);
const applied = await response.json();
console.log(applied.status); // "applied"
console.log(applied.appliedTargetId); // "ins_…" – the instruction it just createdThe response is the suggestion in its applied state, with appliedTargetId now pointing at the real engine entry it created or updated. That entry is an ordinary glossary item, instruction, or brand voice from this point on – open it, edit it, or delete it like any other.
Apply changes config, not past translations
Applying edits the engine's configuration. Content already translated keeps its current output; the change shows up the next time the engine translates. Apply does not re-localize anything on its own.
Dismiss a suggestion#
POST /engine-suggestions/:id/dismissDrops a suggestion you do not want, marking it dismissed and leaving the engine untouched. Use it when a proposal is wrong for your product – the engine is not changed, and the suggestion stops showing up as pending.
await fetch(
`https://api.lingo.dev/engine-suggestions/${suggestionId}/dismiss`,
{
method: "POST",
headers: { "X-API-Key": process.env.LINGO_API_KEY },
},
);
// The suggestion is now "dismissed"; nothing was written to the engine.The loop, end to end#
The four endpoints form one cycle you can drive entirely from code: feed in feedback, read what was proposed, and commit the edits you agree with.
Generate
POST …/suggestions/from-text with your written feedback (or …/suggestions/generate to draw from low review scores instead). You get { "enqueued": true } immediately.
List
GET /engines/:id/suggestions a moment later to read the pending suggestions, each with its payload and reasoning.
Apply or dismiss
POST /engine-suggestions/:id/apply to commit the edit, or …/dismiss to drop it. Applying takes effect on the engine's next translation.
