Core Concepts
Language, alias, provider, mapping — and the one invariant that makes resolution a function.
Four nouns. Everything else in the API is a view over them.
Language
A language the platform can translate, identified by a canonical code in BCP-47 casing: en, pt-BR, zh-Hans, sr-Latn.
Languages come in two kinds:
- base — no qualifier:
en,de,ceb. Its own code is what providers receive; nothing needs configuring. - regional — carries a script, a region or both:
pt-BR,zh-Hans,sr-Latn-RS. These are the ones providers disagree about, and the ones the catalog exists to pin down.
A regional language points at its base (pt-BR → pt), which is what makes the fallback in Resolution Ladder possible.
Why
ceb is a base language
"Base" means no qualifier, not two letters. Cebuano's canonical code is ceb — three letters, no script, no region — so it behaves like en: it needs no per-provider row.
Alias
Any other spelling that resolves to a language. Aliases are stored normalised — lowercase, - separated — and matching is case- and separator-insensitive, so pt_BR matches the alias pt-br without a row of its own.
They cover the spellings that turn up in practice: ISO 639-2/3 codes (deu, ger, zho), deprecated ISO 639-1 ones (iw → he, in → id) that TMS exports still emit, and whatever else an integration arrives with.
An alias belongs to exactly one language
alias is globally unique, and the database enforces it. Inbound resolution has to be a function — the same code must always mean the same language, on every request. A code that could mean two languages would make the answer depend on which row was read first.
See Aliases and Normalisation.
Provider
A translation engine, identified by the same code provider_credentials uses: deepl_api, openai_api, microsoft_translator.
A provider also answers to its aliases — a short name (deepl), and any other identifier callers are known to send for it. Any of them resolves to the same provider, so you do not have to normalise on your side. GET /v1/providers/{code} lists them.
Language support is a property of the vendor's API endpoint, not of the model behind it, so one provider covers every model served by that endpoint — provider_credentials already carries the model as a model_id field under a single credential.
Each provider also carries a fallback policy, which decides what happens for a regional language nobody has configured. See Fallback Policies.
Mapping
One row: this provider, this language, this code.
Two things about mappings surprise people:
They are overrides, not a support matrix. A missing row means "nothing special to say", not "unsupported". Most languages need no row at all — a base language resolves to its own code. Genuinely unsupported pairs are recorded explicitly, with is_supported: false.
There is exactly one per pair. This is the invariant the service is built on: (provider, language) is unique, so there is never a choice to make at request time. Whatever the answer is, it is the same answer every time, and match tells you how it was reached.
How they fit together
inbound code ──normalise──▶ alias ──▶ LANGUAGE ◀── base language
│
provider name ──normalise──▶ alias ──▶ PROVIDER
│
MAPPING (provider, language) ──▶ code on the wire
│
(missing) ──▶ fallback policy
Next
- Resolution Ladder — the exact order the lookup runs in
- Language Catalog — what is in the catalog
- Errors and Conventions — response shapes, versioning, caching