A real payload is rarely all prose. The same object that holds a title and a body also holds an id, a slug, an asset URL, a template name, an enum code – values that identify or wire your content and must come out of translation exactly as they went in. The risk is quiet: hand a model a field called id next to text it's translating, and it may decide "post-42" reads better localized, or normalize a URL, or "correct" an enum. One mutated identifier is a broken link or a failed lookup in production, in whichever locale the model felt helpful.
lockedKeys removes the guesswork. You name the keys that must not change – by exact name or by glob – and the localization engine excludes those values from translation, then merges the source values back verbatim into outputData for every target locale. A locked value is not translated, normalized, or rewritten. Same identifier in, same identifier out, in every locale.
lockedKeys is a field on the create-jobs request. See Create jobs for the full request shape and the 202 response; this page covers only what to put in lockedKeys and how the matching works.
Lock a key by name#
Pass lockedKeys alongside your data. Each entry is a pattern – at its simplest, the bare name of a key you want preserved.
{
"sourceLocale": "en",
"targetLocales": ["de", "fr"],
"data": {
"id": "post-42",
"title": "How async APIs reduce latency",
"tags": ["performance", "infra"],
"author": { "id": "u_abc", "name": "Sam" },
"body": "Async APIs let your app stay responsive while translations process in the background."
},
"lockedKeys": ["id"]
}The bare pattern id matches the key id wherever it appears as a complete segment – here, both the top-level id and the nested author.id. Every German and French job's outputData keeps "post-42" and "u_abc" exactly. Only title, name, and body are translated; tags stays as-is because it holds no locked path, and its string values translate like any other text.
That last point is worth pinning down, because it answers the first question a skeptic asks.
Is a locked value translated?
No. A key you name in lockedKeys is excluded from translation, and its source value is merged back into outputData verbatim for every target locale. The value you sent comes back unchanged – not translated, not normalized, not rewritten. Locking is a guarantee about the result, expressed through lockedKeys, not a hint the model is asked to honor.
Match by name, anywhere – or by position#
A bare pattern is a key name, and it matches that name as a complete segment, at any depth, anywhere in the tree. If audioSrc appears in twelve places nested under different parents, the single pattern audioSrc locks all twelve. You don't enumerate paths to catch every occurrence – that's the common case, and it's one line.
When you need positional control – lock one occurrence but not another, or every element of an array but nothing else – use a glob with / as the path separator. Array indices appear as ordinary segments, so users/0/email and users/*/email are both valid paths.
| Pattern | What it locks |
|---|---|
audioSrc | Every audioSrc leaf in the tree, at any depth |
metadata | The whole metadata subtree wherever it appears |
metadata/author | The metadata/author sequence anywhere it appears, plus below |
users/*/email | Every user's email – * is one segment, matches any index |
users/0/email | Only the first user's email |
**/{audioSrc,imageSrc} | Both leaf names via brace alternation |
Two patterns above lock more than a single leaf, by design. metadata locks the entire subtree under that key – every value beneath it, translatable-looking or not, is preserved. metadata/author locks that sequence wherever it occurs and everything below it. Reach for a subtree lock when a whole block is structural – a config object, a raw embed – and for a leaf lock (metadata/author/name) when only one field inside an otherwise-translatable block must hold.
Glob, not regex
* matches exactly one path segment; ** spans any number of segments; {a,b} is brace alternation across alternatives. There is no character-class or partial-token matching – patterns operate on whole path segments, not substrings. Write users/*/email, not a regular expression.
What comes back#
Locking changes what the model translates – it does not change the shape of your result. outputData mirrors the input structure exactly: locked keys sit in their original positions holding their original values, and translatable strings around them are translated. Nothing is dropped, renamed, or reordered.
For the input above, every locale's outputData carries id: "post-42" and author.id: "u_abc" unchanged, with title, name, and body in the target language. The full job response – outputData, per-stage steps, and status – is documented on Get a single job.
One limit, named up front#
lockedKeys accepts up to 100 patterns per request. That's a ceiling on the number of patterns, not the number of keys they match – a single audioSrc or users/*/email can lock thousands of values across a large payload, and counts as one pattern. If you're approaching 100 distinct patterns, it's usually a sign that a broader glob (**/{id,slug,href}) or a subtree lock will express the same intent in far fewer lines.
lockedKeys is also per-request and ad hoc: it locks keys for this job group only. So for terms that should never translate in any job – a product name, a trademarked feature, a unit that must stay literal – the durable home is a non-translatable entry in your engine's glossary, applied automatically on every call. See Glossaries. Use lockedKeys for structural fields tied to a specific payload's shape; use the glossary for vocabulary that's constant across all your content.
