Skip to main content

BCP 47 Language Tag API

REAL DATA

Canonicalize BCP 47 language tags and run RFC 4647 lookup or basic filtering with bounded, deterministic matching.

Authoritative reference data or standards computation

Base URL
/api/language-tags
Capabilities
3 routes
Last updated
July 29, 2026
Data source

GET /api/language-tags/canonicalize

Live requestRuns against the public API
No key required
GET/api/language-tags/canonicalize?tag=zh-hant-cn-u-ca-chinese-x-team

Request parameters

A whitespace-free BCP 47 language tag, grandfathered tag or private-use-only tag.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/language-tags/canonicalize?tag=zh-hant-cn-u-ca-chinese-x-team"
const res = await fetch("https://randomapi.dev/api/language-tags/canonicalize?tag=zh-hant-cn-u-ca-chinese-x-team");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/language-tags/canonicalize?tag=zh-hant-cn-u-ca-chinese-x-team").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/language-tags/canonicalize?tag=zh-hant-cn-u-ca-chinese-x-team"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 1
tag string required

A whitespace-free BCP 47 language tag, grandfathered tag or private-use-only tag.

allowed: 1 – 255
example: tag=zh-hant-cn-u-ca-chinese-x-team
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=input,canonical
exclude list

Return all fields except these (comma-separated).

example: exclude=privateUse
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 10
input string

Tag exactly as supplied.

example: EN-us

canonical string

Canonical casing, ordering and preferred replacement where supported.

example: en-US

isCanonical boolean

Whether input already exactly equals canonical output.

example: false

kind string

language-tag, grandfathered or private-use.

example: language-tag

baseName string nullable

Tag without extensions/private use, or null when that decomposition is not defined.

example: zh-Hant-CN

language string nullable

Canonical primary language subtag, or null for opaque forms.

example: zh

script string nullable

Four-letter script subtag when present.

example: Hant

region string nullable

Two-letter or three-digit region subtag when present.

example: CN

extensions string[]

Canonical singleton extension sequences excluding private use.

example: ["u-ca-chinese"]

privateUse string nullable

Complete x- private-use sequence when present.

example: x-team

Documented examples

Build-generated requests and complete responses
3
Canonicalize script, region and extensions
GET /api/language-tags/canonicalize?tag=zh-hant-cn-u-ca-chinese-x-team
{
  "data": {
    "input": "zh-hant-cn-u-ca-chinese-x-team",
    "canonical": "zh-Hant-CN-u-ca-chinese-x-team",
    "isCanonical": false,
    "kind": "language-tag",
    "baseName": "zh-Hant-CN",
    "language": "zh",
    "script": "Hant",
    "region": "CN",
    "extensions": [
      "u-ca-chinese"
    ],
    "privateUse": "x-team"
  },
  "meta": {
    "endpoint": "language-tags",
    "route": "/canonicalize",
    "params": {
      "tag": "zh-Hant-CN-u-ca-chinese-x-team"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Replace a grandfathered tag
GET /api/language-tags/canonicalize?tag=i-klingon
{
  "data": {
    "input": "i-klingon",
    "canonical": "tlh",
    "isCanonical": false,
    "kind": "grandfathered",
    "baseName": "tlh",
    "language": "tlh",
    "script": null,
    "region": null,
    "extensions": [],
    "privateUse": null
  },
  "meta": {
    "endpoint": "language-tags",
    "route": "/canonicalize",
    "params": {
      "tag": "tlh"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Normalize a private-use-only tag
GET /api/language-tags/canonicalize?tag=X-TEAM-Internal
{
  "data": {
    "input": "X-TEAM-Internal",
    "canonical": "x-team-internal",
    "isCanonical": false,
    "kind": "private-use",
    "baseName": null,
    "language": null,
    "script": null,
    "region": null,
    "extensions": [],
    "privateUse": "x-team-internal"
  },
  "meta": {
    "endpoint": "language-tags",
    "route": "/canonicalize",
    "params": {
      "tag": "x-team-internal"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}

GET /api/language-tags/lookup

Live requestRuns against the public API
No key required
GET/api/language-tags/lookup?ranges=zh-Hant-TW%2Czh&available=en%2Czh%2Czh-Hant

Request parameters

Priority-ordered basic language ranges; at most 20. '*' is skipped during lookup.

Available BCP 47 tags to canonicalize and search; at most 100.

Optional canonicalizable tag returned only if every priority range fails.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/language-tags/lookup?ranges=zh-Hant-TW%2Czh&available=en%2Czh%2Czh-Hant"
const res = await fetch("https://randomapi.dev/api/language-tags/lookup?ranges=zh-Hant-TW%2Czh&available=en%2Czh%2Czh-Hant");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/language-tags/lookup?ranges=zh-Hant-TW%2Czh&available=en%2Czh%2Czh-Hant").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/language-tags/lookup?ranges=zh-Hant-TW%2Czh&available=en%2Czh%2Czh-Hant"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 3
ranges list required

Priority-ordered basic language ranges; at most 20. '*' is skipped during lookup.

example: ranges=zh-Hant-TW,zh-Hant,zh
available list required

Available BCP 47 tags to canonicalize and search; at most 100.

example: available=en,zh,zh-Hant
default string

Optional canonicalizable tag returned only if every priority range fails.

allowed: 1 – 255
example: default=en
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=ranges,available
exclude list

Return all fields except these (comma-separated).

example: exclude=usedDefault
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 6
ranges string[]

Validated lowercase priority ranges.

example: ["zh-hant-tw","zh-hant","zh"]

available string[]

Canonical, de-duplicated available tags.

example: ["en","zh","zh-Hant"]

match string nullable

Selected available tag, explicit default, or null.

example: zh-Hant

matchedRange string nullable

Normalized lowercase input priority range that succeeded, or null for a default/miss.

example: zh-hant-tw

matchedCandidate string nullable

Normalized input-form fallback candidate whose original or canonical equivalent matched.

example: zh-hant

usedDefault boolean

Whether the explicit default supplied the result.

example: false

Documented examples

Build-generated requests and complete responses
3
Fall back from region to script
GET /api/language-tags/lookup?ranges=zh-Hant-TW%2Czh&available=en%2Czh%2Czh-Hant
{
  "data": {
    "ranges": [
      "zh-hant-tw",
      "zh"
    ],
    "available": [
      "en",
      "zh",
      "zh-Hant"
    ],
    "match": "zh-Hant",
    "matchedRange": "zh-hant-tw",
    "matchedCandidate": "zh-hant",
    "usedDefault": false
  },
  "meta": {
    "endpoint": "language-tags",
    "route": "/lookup",
    "params": {
      "ranges": [
        "zh-hant-tw",
        "zh"
      ],
      "available": [
        "en",
        "zh",
        "zh-Hant"
      ]
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Use a default after every range fails
GET /api/language-tags/lookup?ranges=da-DK%2Cda&available=en%2Cfr&default=en
{
  "data": {
    "ranges": [
      "da-dk",
      "da"
    ],
    "available": [
      "en",
      "fr"
    ],
    "match": "en",
    "matchedRange": null,
    "matchedCandidate": null,
    "usedDefault": true
  },
  "meta": {
    "endpoint": "language-tags",
    "route": "/lookup",
    "params": {
      "ranges": [
        "da-dk",
        "da"
      ],
      "available": [
        "en",
        "fr"
      ],
      "default": "en"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Skip wildcard and try the next range
GET /api/language-tags/lookup?ranges=*%2Cfr-CA&available=en%2Cfr&default=en
{
  "data": {
    "ranges": [
      "*",
      "fr-ca"
    ],
    "available": [
      "en",
      "fr"
    ],
    "match": "fr",
    "matchedRange": "fr-ca",
    "matchedCandidate": "fr",
    "usedDefault": false
  },
  "meta": {
    "endpoint": "language-tags",
    "route": "/lookup",
    "params": {
      "ranges": [
        "*",
        "fr-ca"
      ],
      "available": [
        "en",
        "fr"
      ],
      "default": "en"
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}

GET /api/language-tags/filter

Live requestRuns against the public API
No key required
GET/api/language-tags/filter?ranges=de-CH%2Cde%2Cen&available=en-US%2Cde%2Cde-CH-1996%2Cfr

Request parameters

Priority-ordered basic language ranges; at most 20. '*' matches every available tag.

Available BCP 47 tags to canonicalize and filter; at most 100.

Advanced response options4 options

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

Return all fields except these (comma-separated).

Pretty-print the JSON response.

Drop the envelope: return the raw array/object without data/meta wrapper.

Response

Example parameters are ready. Send the request to inspect the live response.

Route reference

Code samples Ready-to-copy requests in 4 languages
Choose a code sample language
curl "https://randomapi.dev/api/language-tags/filter?ranges=de-CH%2Cde%2Cen&available=en-US%2Cde%2Cde-CH-1996%2Cfr"
const res = await fetch("https://randomapi.dev/api/language-tags/filter?ranges=de-CH%2Cde%2Cen&available=en-US%2Cde%2Cde-CH-1996%2Cfr");
const { data, meta } = await res.json();
import requests

data = requests.get("https://randomapi.dev/api/language-tags/filter?ranges=de-CH%2Cde%2Cen&available=en-US%2Cde%2Cde-CH-1996%2Cfr").json()["data"]
$json = json_decode(file_get_contents(
  "https://randomapi.dev/api/language-tags/filter?ranges=de-CH%2Cde%2Cen&available=en-US%2Cde%2Cde-CH-1996%2Cfr"
), true);
$data = $json["data"];
Parameters Route-specific request inputs 2
ranges list required

Priority-ordered basic language ranges; at most 20. '*' matches every available tag.

example: ranges=de-CH,de,en
available list required

Available BCP 47 tags to canonicalize and filter; at most 100.

example: available=en-US,de,de-CH-1996,fr
Universal parameters Shared response and formatting options 4
fields list

Return only these fields (comma-separated). Mutually exclusive with 'exclude'.

example: fields=ranges,available
exclude list

Return all fields except these (comma-separated).

example: exclude=matches
pretty boolean

Pretty-print the JSON response.

default: false
example: pretty=true
unwrap boolean

Drop the envelope: return the raw array/object without data/meta wrapper.

default: false
example: unwrap=true
Response schema Fields returned in each record 3
ranges string[]

Validated lowercase priority ranges.

example: ["de-ch","de","en"]

available string[]

Canonical, de-duplicated available tags.

example: ["en-US","de","de-CH-1996","fr"]

matches string[]

Prefix matches ordered by range priority then available input order, without duplicates.

example: ["de-CH-1996","de","en-US"]

Documented examples

Build-generated requests and complete responses
3
Filter German before English
GET /api/language-tags/filter?ranges=de-CH%2Cde%2Cen&available=en-US%2Cde%2Cde-CH-1996%2Cfr
{
  "data": {
    "ranges": [
      "de-ch",
      "de",
      "en"
    ],
    "available": [
      "en-US",
      "de",
      "de-CH-1996",
      "fr"
    ],
    "matches": [
      "de-CH-1996",
      "de",
      "en-US"
    ]
  },
  "meta": {
    "endpoint": "language-tags",
    "route": "/filter",
    "params": {
      "ranges": [
        "de-ch",
        "de",
        "en"
      ],
      "available": [
        "en-US",
        "de",
        "de-CH-1996",
        "fr"
      ]
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
Match every canonicalized tag
GET /api/language-tags/filter?ranges=*&available=EN-us%2Cfr-FR%2Czh-Hant
{
  "data": {
    "ranges": [
      "*"
    ],
    "available": [
      "en-US",
      "fr-FR",
      "zh-Hant"
    ],
    "matches": [
      "en-US",
      "fr-FR",
      "zh-Hant"
    ]
  },
  "meta": {
    "endpoint": "language-tags",
    "route": "/filter",
    "params": {
      "ranges": [
        "*"
      ],
      "available": [
        "en-US",
        "fr-FR",
        "zh-Hant"
      ]
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}
A range can return an empty set
GET /api/language-tags/filter?ranges=ja&available=en%2Cfr%2Cde
{
  "data": {
    "ranges": [
      "ja"
    ],
    "available": [
      "en",
      "fr",
      "de"
    ],
    "matches": []
  },
  "meta": {
    "endpoint": "language-tags",
    "route": "/filter",
    "params": {
      "ranges": [
        "ja"
      ],
      "available": [
        "en",
        "fr",
        "de"
      ]
    },
    "generatedAt": "2026-07-29T08:13:22.000Z"
  }
}

About this API

Coverage & behavior

Canonicalizes well-formed BCP 47 language tags, exposes their language, script, region, extension and private-use components, and implements the two RFC 4647 operations developers most often need: lookup and basic filtering.

Canonicalization uses the Worker's standards-based Intl runtime, plus RFC 5646's fixed grandfathered-tag and private-use-only forms. Deprecated tags with a Preferred-Value, such as i-klingon, resolve to that preferred tag. This endpoint deliberately makes no completeness claim for language, script, region or variant assignment in the live IANA Language Subtag Registry: structurally valid private or unassigned subtags can be accepted.

Matching is case-insensitive and bounded to 20 priority ranges × 100 available tags. Each normalized input range and lookup fallback candidate is compared in both its input and safely canonicalized form, so deprecated aliases match their preferred available tags without rewriting the echoed input ranges. Basic filtering returns every prefix match in range-priority then input order. Lookup searches for an exact tag and progressively removes rightmost subtags, removing a singleton with its nearest trailing subtag; * is skipped and an optional explicit default is returned only after every range fails. Available tags are canonicalized and duplicate canonical forms keep their first position.

Use it for

  • Normalize locale identifiers before storing user preferences
  • Select one localized resource with standards-defined fallback
  • Filter a content catalog by an ordered Accept-Language-style range list

Frequently asked questions

Does this API validate every subtag against the live IANA registry?

No. It validates and canonicalizes supported BCP 47 structure but does not claim that every language, region or variant is currently assigned by IANA.

What is the difference between lookup and basic filtering?

Lookup returns one exact available tag after progressively truncating each priority range. Basic filtering returns every tag equal to a range or beginning with that range plus a hyphen.

Are grandfathered and private-use-only tags supported?

Yes. RFC 5646's fixed grandfathered forms are recognized, preferred replacements are applied where defined, and private-use-only tags such as x-team are lowercased.

Standards & references