You have a piece of text and no reliable record of what language it is in – a comment a user typed, a string from an uploaded file, the body of an inbound support ticket. Before you can route it, translate it, or even render it correctly, you need its locale: which language, which region, which script, and which way it reads. Recognize closes that gap in one call. You send the text; you get back a structured locale identity, as specific as the text allows.
This page covers the whole endpoint – the request, the response and every field it returns, the language bindings, and what the response does when the text doesn't pin down a region or script. It is a synchronous call: you POST text, the request blocks while it analyzes the text, and the answer comes back in the same round-trip. Authentication is the shared X-API-Key header – see Authentication for how keys work – and any error follows the standard error model.
Request#
POST /process/recognize| Parameter | Type | Description |
|---|---|---|
text | string | The text to analyze |
labelLocale | string (optional) | Locale for the human-readable label (default: en) |
Only text is required. labelLocale controls the language of the human-readable label in the response – set it to de and the label for a French input comes back in German instead of English. It does not change what gets detected, only how the result is named back to you.
{
"text": "Bonjour le monde",
"labelLocale": "en"
}Response#
{
"locale": "fr",
"language": "fr",
"region": null,
"script": null,
"label": "French",
"direction": "ltr"
}| Field | Type | Description |
|---|---|---|
locale | string | BCP-47 locale code at the most specific level of confidence |
language | string | ISO 639 language subtag |
region | string | null | ISO 3166 region subtag, or null if indistinguishable |
script | string | null | ISO 15924 script subtag, or null if default for the language |
label | string | Human-readable locale name in the requested labelLocale |
direction | "ltr" | "rtl" | Text direction |
Two things in that shape are worth reading closely, because they are what make the result usable rather than just informative.
First, every code is a published standard, not a Lingo.dev invention. The locale is BCP-47; language is an ISO 639 subtag; region is ISO 3166; script is ISO 15924. So whatever you already use to parse locales – your i18n library, an Intl call, a CLDR lookup – consumes this output directly. You are not adapting to a proprietary code system; you are getting the same identifiers the rest of your stack already speaks.
Second, region and script are nullable on purpose. They come back filled only when the text actually demonstrates them – which is the subject of the next two sections, and the property that keeps the endpoint from guessing.
Region and script are returned only when the text shows them#
The obvious worry about any language detector is that it over-reaches: that it will stamp a region or a writing system onto text that never proved one, and you build logic on a guess. Recognize does the opposite. It reports a subtag only when the evidence supports it, and returns null when it does not.
When regional markers are present in the text – Brazilian Portuguese vocabulary, say – the response includes the full tag (pt-BR). When the regional variant is indistinguishable, only the language subtag is returned (pt):
{
"locale": "pt-BR",
"language": "pt",
"region": "BR",
"script": null,
"label": "Portuguese (Brazil)",
"direction": "ltr"
}{
"locale": "pt",
"language": "pt",
"region": null,
"script": null,
"label": "Portuguese",
"direction": "ltr"
}Same language, two honest answers. The first text carried enough to name the region; the second did not, so region is null and the locale collapses to pt. script follows the same rule from the other direction: it is null when the writing system is the default for the language – Latin for French, for instance – and named only when the script is the distinguishing fact.
A null is information, not a gap
region: null does not mean detection failed. It means the text did not contain enough to distinguish a region, so the endpoint declined to invent one – and locale is the language subtag alone. Read it as "as specific as the text allows": branch on locale and let null route you to the language-level default rather than treating it as an error.
This is why locale is the field to build on. It is always the most specific tag the text supports – pt-BR when the evidence is there, pt when it is not – so reading locale gives you the right granularity automatically, without you having to reassemble it from the parts or second-guess a confident-looking region that was really a guess.
direction is there so you can render before you translate#
Language detection is rarely the end goal – usually you detect in order to do something with the text, and the first thing you often do is show it. direction is in the response for exactly that: it tells you whether the text reads left-to-right or right-to-left, so you can set dir="rtl", pick a layout, or choose a font before any translation step. Arabic text comes back "rtl"; the French example above comes back "ltr". You do not have to maintain your own language-to-direction table – the endpoint that identifies the language also hands you the one rendering fact you need first.
Examples#
A single POST with the text and an optional labelLocale. The response is the structured locale object above.
const response = await fetch(
"https://api.lingo.dev/process/recognize",
{
method: "POST",
headers: {
"X-API-Key": "your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
text: "Bonjour le monde",
labelLocale: "en",
}),
}
);
const result = await response.json();
// { locale: "fr", language: "fr", label: "French", direction: "ltr", ... }Next steps#
The common reason to detect a language is to act on it – most often, to translate it. Recognize tells you the source locale; the localization endpoints take it from there.
