text string required Text to reduce to its UTS #39 skeleton. Limited to 256 UTF-16 code units; whitespace is significant and is not trimmed.
UTS #39 skeletons, mixed-script and identifier-status checks: whether two usernames or domains are visual lookalikes, and which characters differ.
Authoritative reference data or standards computation
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
text
required
| string | allowed: 1 – 256 example: раураl | Text to reduce to its UTS #39 skeleton. Limited to 256 UTF-16 code units; whitespace is significant and is not trimmed. |
text string required Text to reduce to its UTS #39 skeleton. Limited to 256 UTF-16 code units; whitespace is significant and is not trimmed.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: text,nfd | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: characters | Return all fields except these (comma-separated). |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
text | string | The resolved input, unchanged. | раураl |
nfd | string | The input after step 1 of the algorithm (Normalization Form D). The `characters` rows below are positions in this string, not in `text`. | раураl |
skeleton | string | The skeleton. Not intended for display and not stable across Unicode versions — treat it as a hash, and recompute rather than store it. | paypal |
skeletonCodePoints | string[] | The skeleton as U+XXXX code points, so an invisible result is still readable. | ["U+0070","U+0061","U+0079","U+0070","U+0061","U+006C"] |
changed | boolean | Whether the skeleton differs from the input. The skeleton is NFD, so a precomposed input can be `changed` with no prototype substitution at all — check `charactersMapped` for that. | true |
definition | string | "skeleton" when the value returned is exactly UTS #39 skeleton(X) = bidiSkeleton(LTR, X) via the section 4 fast path; "internalSkeleton" when the bidi steps were skipped. | skeleton |
bidiExact | boolean | Whether the UTS #39 4 left-to-right fast path applied, making the value returned exactly skeleton(X). False when the input contains a right-to-left or explicit directional-formatting character: we do not implement the UAX #9 bidirectional algorithm. | true |
bidiNote nullable | string | Why the fast path did not apply, naming the first offending character and its Bidi_Class. Null when the result is bidi-exact. | null |
ignorablesRemoved | integer | How many Default_Ignorable_Code_Point characters step 2 dropped. | 0 |
charactersMapped | integer | How many characters survived step 2 and had a prototype in the confusables data, so were substituted in step 3. | 5 |
characters | object[] | One row per character of `nfd`, in order: character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes, plus `index` (UTF-16 code-unit offset within `nfd`), `ignorable` (removed by step 2), `prototype` (null when the character has none) and `prototypeCodePoints`. | [{"index":0,"character":"р","codePoint":"U+0440","name":"CYRILLIC SMALL LETTER ER","script":"Cyrl","scriptName":"Cyrillic","identifierStatus":"Allowed","identifierTypes":["Recommended"],"ignorable":false,"prototype":"p","prototypeCodePoints":["U+0070"]}] |
text string The resolved input, unchanged.
example: раураl
nfd string The input after step 1 of the algorithm (Normalization Form D). The `characters` rows below are positions in this string, not in `text`.
example: раураl
skeleton string The skeleton. Not intended for display and not stable across Unicode versions — treat it as a hash, and recompute rather than store it.
example: paypal
skeletonCodePoints string[] The skeleton as U+XXXX code points, so an invisible result is still readable.
example: ["U+0070","U+0061","U+0079","U+0070","U+0061","U+006C"]
changed boolean Whether the skeleton differs from the input. The skeleton is NFD, so a precomposed input can be `changed` with no prototype substitution at all — check `charactersMapped` for that.
example: true
definition string "skeleton" when the value returned is exactly UTS #39 skeleton(X) = bidiSkeleton(LTR, X) via the section 4 fast path; "internalSkeleton" when the bidi steps were skipped.
example: skeleton
bidiExact boolean Whether the UTS #39 4 left-to-right fast path applied, making the value returned exactly skeleton(X). False when the input contains a right-to-left or explicit directional-formatting character: we do not implement the UAX #9 bidirectional algorithm.
example: true
bidiNote string nullable Why the fast path did not apply, naming the first offending character and its Bidi_Class. Null when the result is bidi-exact.
example: null
ignorablesRemoved integer How many Default_Ignorable_Code_Point characters step 2 dropped.
example: 0
charactersMapped integer How many characters survived step 2 and had a prototype in the confusables data, so were substituted in step 3.
example: 5
characters object[] One row per character of `nfd`, in order: character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes, plus `index` (UTF-16 code-unit offset within `nfd`), `ignorable` (removed by step 2), `prototype` (null when the character has none) and `prototypeCodePoints`.
example: [{"index":0,"character":"р","codePoint":"U+0440","name":"CYRILLIC SMALL LETTER ER","script":"Cyrl","scriptName":"Cyrillic","identifierStatus":"Allowed","identifierTypes":["Recommended"],"ignorable":false,"prototype":"p","prototypeCodePoints":["U+0070"]}]
/api/confusables/skeleton?text=%D1%80%D0%B0%D1%83%D1%80%D0%B0l {
"data": {
"text": "раураl",
"nfd": "раураl",
"skeleton": "paypal",
"skeletonCodePoints": [
"U+0070",
"U+0061",
"U+0079",
"U+0070",
"U+0061",
"U+006C"
],
"changed": true,
"definition": "skeleton",
"bidiExact": true,
"bidiNote": null,
"ignorablesRemoved": 0,
"charactersMapped": 5,
"characters": [
{
"index": 0,
"character": "р",
"codePoint": "U+0440",
"name": "CYRILLIC SMALL LETTER ER",
"script": "Cyrl",
"scriptName": "Cyrillic",
"identifierStatus": "Allowed",
"identifierTypes": [
"Recommended"
],
"ignorable": false,
"prototype": "p",
"prototypeCodePoints": [
"U+0070"
]
},
{
"index": 1,
"character": "а",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/confusables/skeleton?text=paypa1 {
"data": {
"text": "paypa1",
"nfd": "paypa1",
"skeleton": "paypal",
"skeletonCodePoints": [
"U+0070",
"U+0061",
"U+0079",
"U+0070",
"U+0061",
"U+006C"
],
"changed": true,
"definition": "skeleton",
"bidiExact": true,
"bidiNote": null,
"ignorablesRemoved": 0,
"charactersMapped": 1,
"characters": [
{
"index": 0,
"character": "p",
"codePoint": "U+0070",
"name": "LATIN SMALL LETTER P",
"script": "Latn",
"scriptName": "Latin",
"identifierStatus": "Allowed",
"identifierTypes": [
"Recommended"
],
"ignorable": false,
"prototype": null,
"prototypeCodePoints": []
},
{
"index": 1,
"character": "a",
"codePoint": "U+0061",
"name": "LATIN SMALL LETTER A",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/confusables/skeleton?text=%D9%85%D8%AB%D8%A7%D9%84 {
"data": {
"text": "مثال",
"nfd": "مثال",
"skeleton": "مىۛlل",
"skeletonCodePoints": [
"U+0645",
"U+0649",
"U+06DB",
"U+006C",
"U+0644"
],
"changed": true,
"definition": "internalSkeleton",
"bidiExact": false,
"bidiNote": "U+0645 ARABIC LETTER MEEM (Bidi_Class AL) takes the UTS #39 section 4 left-to-right fast path away, because UAX #9 reordering could change the result. The value returned is internalSkeleton(X), not skeleton(X).",
"ignorablesRemoved": 0,
"charactersMapped": 2,
"characters": [
{
"index": 0,
"character": "م",
"codePoint": "U+0645",
"name": "ARABIC LETTER MEEM",
"script": "Arab",
"scriptName": "Arabic",
"identifierStatus": "Allowed",
"identifierTypes": [
"Recommended"
],
"ignorable": false,
"prototype": null,
"prototypeCodePoints": []
},
{
"index": 1,
"character": "ث",
"codePoint": "U+062B",
"name": "ARABIC LETTER THEH",
"script": "Arab",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} | Parameter | Type | Default & allowed | Description |
|---|---|---|---|
a
required
| string | allowed: 1 – 256 example: paypal | The first string — for example the username or domain that already exists. Limited to 256 UTF-16 code units; whitespace is significant and is not trimmed. |
b
required
| string | allowed: 1 – 256 example: раураl | The second string — for example the one being registered. Limited to 256 UTF-16 code units; whitespace is significant and is not trimmed. |
a string required The first string — for example the username or domain that already exists. Limited to 256 UTF-16 code units; whitespace is significant and is not trimmed.
b string required The second string — for example the one being registered. Limited to 256 UTF-16 code units; whitespace is significant and is not trimmed.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: a,b | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: differences | Return all fields except these (comma-separated). |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
a | string | The first resolved input, unchanged. | paypal |
b | string | The second resolved input, unchanged. | раураl |
equal | boolean | Whether the two inputs are identical UTF-16 sequences. | false |
skeletonA | string | The UTS #39 skeleton of `a`. | paypal |
skeletonB | string | The UTS #39 skeleton of `b`. | paypal |
confusable nullable | boolean | Whether the two skeletons are identical. Null when either side is not bidi-exact: UAX #9 reordering could change either skeleton, so no verdict is issued rather than a possibly-wrong false. | true |
confusableClass nullable | string | Per UTS #39 4 Definitions: "single-script" when the resolved script sets share an element, "mixed-script" when they are disjoint, "whole-script" when they are disjoint and both strings are single-script. The three are not mutually exclusive in the spec — "all whole-script confusables are also mixed-script confusables" — and this field reports the narrowest class that applies, so read "whole-script" as implying mixed-script and do not filter on the string "mixed-script" alone. Null when `confusable` is false or null. | mixed-script |
bidiExact | boolean | Whether the UTS #39 4 left-to-right fast path applied, making the value returned exactly skeleton(X). False when the input contains a right-to-left or explicit directional-formatting character: we do not implement the UAX #9 bidirectional algorithm. | true |
undeterminedReason nullable | string | Why `confusable` is null, naming the side, the character and its Bidi_Class. Null otherwise. | null |
resolvedScriptsA | string[] | The resolved script set of `a` as ISO 15924 codes. Empty for a mixed-script string; ["ALL"] when every character is Common or Inherited, which section 5.1 treats as every script. | ["Latn"] |
resolvedScriptsB | string[] | The resolved script set of `b`, same conventions as `resolvedScriptsA`. | [] |
differences | object[] | Rows where the two inputs differ, compared code point by code point at the same position up to the longer length — a positional comparison, not a diff algorithm. Keys: `index` (zero-based code-point position, not a code-unit offset), `aCharacter`, `aCodePoint`, `aName`, `aScript`, `bCharacter`, `bCodePoint`, `bName`, `bScript`. The shorter side's four keys are null past its end. | [{"index":1,"aCharacter":"a","aCodePoint":"U+0061","aName":"LATIN SMALL LETTER A","aScript":"Latn","bCharacter":"а","bCodePoint":"U+0430","bName":"CYRILLIC SMALL LETTER A","bScript":"Cyrl"}] |
a string The first resolved input, unchanged.
example: paypal
b string The second resolved input, unchanged.
example: раураl
equal boolean Whether the two inputs are identical UTF-16 sequences.
example: false
skeletonA string The UTS #39 skeleton of `a`.
example: paypal
skeletonB string The UTS #39 skeleton of `b`.
example: paypal
confusable boolean nullable Whether the two skeletons are identical. Null when either side is not bidi-exact: UAX #9 reordering could change either skeleton, so no verdict is issued rather than a possibly-wrong false.
example: true
confusableClass string nullable Per UTS #39 4 Definitions: "single-script" when the resolved script sets share an element, "mixed-script" when they are disjoint, "whole-script" when they are disjoint and both strings are single-script. The three are not mutually exclusive in the spec — "all whole-script confusables are also mixed-script confusables" — and this field reports the narrowest class that applies, so read "whole-script" as implying mixed-script and do not filter on the string "mixed-script" alone. Null when `confusable` is false or null.
example: mixed-script
bidiExact boolean Whether the UTS #39 4 left-to-right fast path applied, making the value returned exactly skeleton(X). False when the input contains a right-to-left or explicit directional-formatting character: we do not implement the UAX #9 bidirectional algorithm.
example: true
undeterminedReason string nullable Why `confusable` is null, naming the side, the character and its Bidi_Class. Null otherwise.
example: null
resolvedScriptsA string[] The resolved script set of `a` as ISO 15924 codes. Empty for a mixed-script string; ["ALL"] when every character is Common or Inherited, which section 5.1 treats as every script.
example: ["Latn"]
resolvedScriptsB string[] The resolved script set of `b`, same conventions as `resolvedScriptsA`.
example: []
differences object[] Rows where the two inputs differ, compared code point by code point at the same position up to the longer length — a positional comparison, not a diff algorithm. Keys: `index` (zero-based code-point position, not a code-unit offset), `aCharacter`, `aCodePoint`, `aName`, `aScript`, `bCharacter`, `bCodePoint`, `bName`, `bScript`. The shorter side's four keys are null past its end.
example: [{"index":1,"aCharacter":"a","aCodePoint":"U+0061","aName":"LATIN SMALL LETTER A","aScript":"Latn","bCharacter":"а","bCodePoint":"U+0430","bName":"CYRILLIC SMALL LETTER A","bScript":"Cyrl"}]
/api/confusables/compare?a=paypal&b=%D1%80%D0%B0%D1%83%D1%80%D0%B0l {
"data": {
"a": "paypal",
"b": "раураl",
"equal": false,
"skeletonA": "paypal",
"skeletonB": "paypal",
"confusable": true,
"confusableClass": "mixed-script",
"bidiExact": true,
"undeterminedReason": null,
"resolvedScriptsA": [
"Latn"
],
"resolvedScriptsB": [],
"differences": [
{
"index": 0,
"aCharacter": "p",
"aCodePoint": "U+0070",
"aName": "LATIN SMALL LETTER P",
"aScript": "Latn",
"bCharacter": "р",
"bCodePoint": "U+0440",
"bName": "CYRILLIC SMALL LETTER ER",
"bScript": "Cyrl"
},
{
"index": 1,
"aCharacter": "a",
"aCodePoint": "U+0061",
"aName": "LATIN SMALL LETTER A",
"aScript": "Latn",
"bCharacter": "а",
"bCodePoint": "U+0430",
"bName": "CYRILLIC SMALL LETTER A",
"bScript": "Cyrl"
},
{
"index": 2,
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/confusables/compare?a=scope&b=%D1%95%D1%81%D0%BE%D1%80%D0%B5 {
"data": {
"a": "scope",
"b": "ѕсоре",
"equal": false,
"skeletonA": "scope",
"skeletonB": "scope",
"confusable": true,
"confusableClass": "whole-script",
"bidiExact": true,
"undeterminedReason": null,
"resolvedScriptsA": [
"Latn"
],
"resolvedScriptsB": [
"Cyrl"
],
"differences": [
{
"index": 0,
"aCharacter": "s",
"aCodePoint": "U+0073",
"aName": "LATIN SMALL LETTER S",
"aScript": "Latn",
"bCharacter": "ѕ",
"bCodePoint": "U+0455",
"bName": "CYRILLIC SMALL LETTER DZE",
"bScript": "Cyrl"
},
{
"index": 1,
"aCharacter": "c",
"aCodePoint": "U+0063",
"aName": "LATIN SMALL LETTER C",
"aScript": "Latn",
"bCharacter": "с",
"bCodePoint": "U+0441",
"bName": "CYRILLIC SMALL LETTER ES",
"bScript": "Cyrl"
},
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/confusables/compare?a=microsoft&b=rnicrosoft {
"data": {
"a": "microsoft",
"b": "rnicrosoft",
"equal": false,
"skeletonA": "rnicrosoft",
"skeletonB": "rnicrosoft",
"confusable": true,
"confusableClass": "single-script",
"bidiExact": true,
"undeterminedReason": null,
"resolvedScriptsA": [
"Latn"
],
"resolvedScriptsB": [
"Latn"
],
"differences": [
{
"index": 0,
"aCharacter": "m",
"aCodePoint": "U+006D",
"aName": "LATIN SMALL LETTER M",
"aScript": "Latn",
"bCharacter": "r",
"bCodePoint": "U+0072",
"bName": "LATIN SMALL LETTER R",
"bScript": "Latn"
},
{
"index": 1,
"aCharacter": "i",
"aCodePoint": "U+0069",
"aName": "LATIN SMALL LETTER I",
"aScript": "Latn",
"bCharacter": "n",
"bCodePoint": "U+006E",
"bName": "LATIN SMALL LETTER N",
"bScript": "Latn"
},
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} | Parameter | Type | Default & allowed | Description |
|---|---|---|---|
text
required
| string | allowed: 1 – 256 example: ѕсоре | Text to evaluate as a candidate identifier. Limited to 256 UTF-16 code units; whitespace is significant and is not trimmed. |
text string required Text to evaluate as a candidate identifier. Limited to 256 UTF-16 code units; whitespace is significant and is not trimmed.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: text,codePointCount | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: characters | Return all fields except these (comma-separated). |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
text | string | The resolved input, unchanged. | ѕсоре |
codePointCount | integer | Unicode scalar values in the input (not UTF-16 code units). | 5 |
nonAsciiCount | integer | How many of those scalar values are above U+007F. | 5 |
scripts | string[] | One minimal cover set (UTS #39 5.1): a smallest set of scripts such that every character shares at least one script with it, ordered by first appearance. Section 5.1 notes minimal cover sets are not unique, so this is one of them. Empty when every character is Common or Inherited, because then every script covers the string. | ["Cyrl"] |
scriptsMinimal | boolean | Whether `scripts` is proven to be a smallest cover set. Proving it is an exact set-cover search run under a node budget; it completes for anything resembling an identifier, and only a deliberately pathological mix of dozens of multi-script characters falls back to a greedy cover (still a valid cover, possibly one script too large). | true |
singleScript | boolean | Whether the resolved script set is non-empty (UTS #39 5.1). Mind the spec's own warning: "single-script" means at least one script in the set, not exactly one — 日本語 is single-script with four. | true |
mixedScript | boolean | The negation of `singleScript`: the resolved script set is empty. | false |
restrictionLevel | string | One of ASCII-Only, Single Script, Highly Restrictive, Moderately Restrictive, Minimally Restrictive, Unrestricted (UTS #39 5.2, all six computed exactly). The identifier profile applied is the General Security Profile, so a character outside it — a space, an exclamation mark — makes the string Unrestricted no matter how clean its scripts are. | Single Script |
identifierAllowed | boolean | Whether every character has Identifier_Status=Allowed under the General Security Profile (UTS #39 3.1). | true |
restrictedCharacters | object[] | Every distinct Identifier_Status=Restricted character, first appearance first: character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes. `identifierTypes` is the reason for the status — Not_XID, Limited_Use, Obsolete, Not_NFKC, Default_Ignorable, Not_Character and so on. Empty when every character is Allowed. | [] |
confusableCharacters | object[] | Every distinct character that has a UTS #39 prototype: character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes, plus `prototype` and `confusableWithCount`. | [{"character":"ѕ","codePoint":"U+0455","name":"CYRILLIC SMALL LETTER DZE","script":"Cyrl","scriptName":"Cyrillic","identifierStatus":"Allowed","identifierTypes":["Recommended"],"prototype":"s","confusableWithCount":21}] |
mixedNumbers | boolean | Whether more than one decimal number system appears (UTS #39 5.3). | false |
numberSystems | object[] | One row per distinct decimal system found — `zeroCodePoint`, `zeroName` and `digitCount` (how many of the input's digits came from that system) — first appearance first. Only General_Category=Nd characters participate, so Roman numerals and vulgar fractions are absent (they surface in `restrictedCharacters` instead). Empty when the input has no decimal digits. | [] |
skeleton | string | The same value /skeleton returns, so /inspect answers on its own. | scope |
bidiExact | boolean | Whether the UTS #39 4 left-to-right fast path applied, making the value returned exactly skeleton(X). False when the input contains a right-to-left or explicit directional-formatting character: we do not implement the UAX #9 bidirectional algorithm. | true |
characters | object[] | The full walk over the input's scalar values: character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes, plus `index` (UTF-16 code-unit offset) and `augmentedScripts` (the section 5.1 augmented script set, ["ALL"] for Common and Inherited characters). | [{"index":0,"character":"ѕ","codePoint":"U+0455","name":"CYRILLIC SMALL LETTER DZE","script":"Cyrl","scriptName":"Cyrillic","identifierStatus":"Allowed","identifierTypes":["Recommended"],"augmentedScripts":["Cyrl"]}] |
text string The resolved input, unchanged.
example: ѕсоре
codePointCount integer Unicode scalar values in the input (not UTF-16 code units).
example: 5
nonAsciiCount integer How many of those scalar values are above U+007F.
example: 5
scripts string[] One minimal cover set (UTS #39 5.1): a smallest set of scripts such that every character shares at least one script with it, ordered by first appearance. Section 5.1 notes minimal cover sets are not unique, so this is one of them. Empty when every character is Common or Inherited, because then every script covers the string.
example: ["Cyrl"]
scriptsMinimal boolean Whether `scripts` is proven to be a smallest cover set. Proving it is an exact set-cover search run under a node budget; it completes for anything resembling an identifier, and only a deliberately pathological mix of dozens of multi-script characters falls back to a greedy cover (still a valid cover, possibly one script too large).
example: true
singleScript boolean Whether the resolved script set is non-empty (UTS #39 5.1). Mind the spec's own warning: "single-script" means at least one script in the set, not exactly one — 日本語 is single-script with four.
example: true
mixedScript boolean The negation of `singleScript`: the resolved script set is empty.
example: false
restrictionLevel string One of ASCII-Only, Single Script, Highly Restrictive, Moderately Restrictive, Minimally Restrictive, Unrestricted (UTS #39 5.2, all six computed exactly). The identifier profile applied is the General Security Profile, so a character outside it — a space, an exclamation mark — makes the string Unrestricted no matter how clean its scripts are.
example: Single Script
identifierAllowed boolean Whether every character has Identifier_Status=Allowed under the General Security Profile (UTS #39 3.1).
example: true
restrictedCharacters object[] Every distinct Identifier_Status=Restricted character, first appearance first: character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes. `identifierTypes` is the reason for the status — Not_XID, Limited_Use, Obsolete, Not_NFKC, Default_Ignorable, Not_Character and so on. Empty when every character is Allowed.
example: []
confusableCharacters object[] Every distinct character that has a UTS #39 prototype: character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes, plus `prototype` and `confusableWithCount`.
example: [{"character":"ѕ","codePoint":"U+0455","name":"CYRILLIC SMALL LETTER DZE","script":"Cyrl","scriptName":"Cyrillic","identifierStatus":"Allowed","identifierTypes":["Recommended"],"prototype":"s","confusableWithCount":21}]
mixedNumbers boolean Whether more than one decimal number system appears (UTS #39 5.3).
example: false
numberSystems object[] One row per distinct decimal system found — `zeroCodePoint`, `zeroName` and `digitCount` (how many of the input's digits came from that system) — first appearance first. Only General_Category=Nd characters participate, so Roman numerals and vulgar fractions are absent (they surface in `restrictedCharacters` instead). Empty when the input has no decimal digits.
example: []
skeleton string The same value /skeleton returns, so /inspect answers on its own.
example: scope
bidiExact boolean Whether the UTS #39 4 left-to-right fast path applied, making the value returned exactly skeleton(X). False when the input contains a right-to-left or explicit directional-formatting character: we do not implement the UAX #9 bidirectional algorithm.
example: true
characters object[] The full walk over the input's scalar values: character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes, plus `index` (UTF-16 code-unit offset) and `augmentedScripts` (the section 5.1 augmented script set, ["ALL"] for Common and Inherited characters).
example: [{"index":0,"character":"ѕ","codePoint":"U+0455","name":"CYRILLIC SMALL LETTER DZE","script":"Cyrl","scriptName":"Cyrillic","identifierStatus":"Allowed","identifierTypes":["Recommended"],"augmentedScripts":["Cyrl"]}]
/api/confusables/inspect?text=%D1%95%D1%81%D0%BE%D1%80%D0%B5 {
"data": {
"text": "ѕсоре",
"codePointCount": 5,
"nonAsciiCount": 5,
"scripts": [
"Cyrl"
],
"scriptsMinimal": true,
"singleScript": true,
"mixedScript": false,
"restrictionLevel": "Single Script",
"identifierAllowed": true,
"restrictedCharacters": [],
"confusableCharacters": [
{
"character": "ѕ",
"codePoint": "U+0455",
"name": "CYRILLIC SMALL LETTER DZE",
"script": "Cyrl",
"scriptName": "Cyrillic",
"identifierStatus": "Allowed",
"identifierTypes": [
"Recommended"
],
"prototype": "s",
"confusableWithCount": 21
},
{
"character": "с",
"codePoint": "U+0441",
"name": "CYRILLIC SMALL LETTER ES",
"script": "Cyrl",
"scriptName": "Cyrillic",
"identifierStatus": "Allowed",
"identifierTypes": [
"Recommended"
],
"prototype": "c",
"confusableWithCount": 23
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/confusables/inspect?text=%E6%97%A5%E6%9C%AC%E8%AA%9Eabc {
"data": {
"text": "日本語abc",
"codePointCount": 6,
"nonAsciiCount": 3,
"scripts": [
"Hani",
"Latn"
],
"scriptsMinimal": true,
"singleScript": false,
"mixedScript": true,
"restrictionLevel": "Highly Restrictive",
"identifierAllowed": true,
"restrictedCharacters": [],
"confusableCharacters": [],
"mixedNumbers": false,
"numberSystems": [],
"skeleton": "日本語abc",
"bidiExact": true,
"characters": [
{
"index": 0,
"character": "日",
"codePoint": "U+65E5",
"name": "CJK UNIFIED IDEOGRAPH-65E5",
"script": "Hani",
"scriptName": "Han",
"identifierStatus": "Allowed",
"identifierTypes": [
"Recommended"
],
"augmentedScripts": [
"Hani",
"Hanb",
"Jpan",
"Kore"
]
},
{
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/confusables/inspect?text=a1%D9%A2 {
"data": {
"text": "a1٢",
"codePointCount": 3,
"nonAsciiCount": 1,
"scripts": [
"Latn",
"Arab"
],
"scriptsMinimal": true,
"singleScript": false,
"mixedScript": true,
"restrictionLevel": "Moderately Restrictive",
"identifierAllowed": true,
"restrictedCharacters": [],
"confusableCharacters": [
{
"character": "1",
"codePoint": "U+0031",
"name": "DIGIT ONE",
"script": "Zyyy",
"scriptName": "Common",
"identifierStatus": "Allowed",
"identifierTypes": [
"Recommended"
],
"prototype": "l",
"confusableWithCount": 79
}
],
"mixedNumbers": true,
"numberSystems": [
{
"zeroCodePoint": "U+0030",
"zeroName": "DIGIT ZERO",
"digitCount": 1
},
{
"zeroCodePoint": "U+0660",
"zeroName": "ARABIC-INDIC DIGIT ZERO",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} A compute route rather than a lookup, on purpose: every Unicode scalar value is a valid key, so there is no honest "unknown key" 404. A character simply absent from confusables.txt answers prototype: null with an empty confusableWith, which is the correct answer rather than a substitute record.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
char
required
| string | allowed: 1 – 8 example: а | The single character to describe. Give the character itself or its code point in U+XXXX notation, so %20 and U+0020 both address the space. Not trimmed: a stray space makes the value two characters and a 400. |
char string required The single character to describe. Give the character itself or its code point in U+XXXX notation, so %20 and U+0020 both address the space. Not trimmed: a stray space makes the value two characters and a 400.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: character,codePoint | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: inConfusablesData | Return all fields except these (comma-separated). |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
character | string | The character itself. | а |
codePoint | string | Its code point in U+XXXX notation. | U+0430 |
decimal | integer | The same scalar value as a number. | 1072 |
name nullable | string | The Unicode Name, from the pinned UnicodeData.txt. Non-null two ways: the 7559 bundled name rows, which cover everything but the CJK ideographs among the 8853 code points this endpoint can be asked about meaningfully (the 8809 the confusables data mentions plus the 44 decimal-number-system zeros it does not already mention), and every CJK Unified Ideograph, whose name the UAX #44 4.8 rule generates from the code point itself — that rule covers the blocks whole, so all 101984 of them get a name, not only the 1294 the confusables data mentions. Null for everything else: U+2603 SNOWMAN, unassigned code points, Hangul syllables outside the bundled rows. 109543 code points in all — a stated boundary, not a general character-name lookup. | CYRILLIC SMALL LETTER A |
script | string | The ISO 15924 script code (UAX #24); Zzzz for unassigned code points. | Cyrl |
scriptName | string | The script's Unicode long name with underscores rendered as spaces ("Canadian_Aboriginal" becomes "Canadian Aboriginal"). | Cyrillic |
augmentedScripts | string[] | The UTS #39 5.1 augmented script set: Script_Extensions plus the multi-script writing systems (Han adds Hanb, Jpan and Kore; Hiragana and Katakana add Jpan; Hangul adds Kore; Bopomofo adds Hanb). ["ALL"] for Common and Inherited characters. | ["Cyrl"] |
identifierStatus | string | Allowed or Restricted under the UTS #39 General Security Profile. | Allowed |
identifierTypes | string[] | The Identifier_Type set — the reason behind the status. Code points the property does not list default to ["Not_Character"]. | ["Recommended"] |
prototype nullable | string | The UTS #39 prototype this character maps to, which may be several characters (m maps to rn). Null when confusables.txt does not map it. | a |
prototypeCodePoints | string[] | The prototype as U+XXXX code points; empty when there is none. | ["U+0061"] |
skeleton | string | The skeleton of this single character. Empty for a Default_Ignorable character, which step 2 removes entirely. | a |
confusableWith | object[] | Every other code point in the confusables data whose one-character skeleton is identical: character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes, plus `prototype`. Bounded — the largest group in this snapshot is 80 code points. | [{"character":"a","codePoint":"U+0061","name":"LATIN SMALL LETTER A","script":"Latn","scriptName":"Latin","identifierStatus":"Allowed","identifierTypes":["Recommended"],"prototype":null}] |
confusableWithCount | integer | The length of `confusableWith`. | 23 |
intentionalPartners | object[] | Characters paired with this one in Unicode's hand-picked intentional-confusables list (glyphs designed to be identical in shape): character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes. Empty for most characters — the list has only 77 pairs. | [] |
nfkc | string | The character under NFKC, for contrast: NFKC is not a confusable check. Computed by the runtime's normalizer rather than from this pinned snapshot. | а |
inConfusablesData | boolean | Whether confusables.txt mentions this code point at all, as a mapped source or inside a prototype. | true |
character string The character itself.
example: а
codePoint string Its code point in U+XXXX notation.
example: U+0430
decimal integer The same scalar value as a number.
example: 1072
name string nullable The Unicode Name, from the pinned UnicodeData.txt. Non-null two ways: the 7559 bundled name rows, which cover everything but the CJK ideographs among the 8853 code points this endpoint can be asked about meaningfully (the 8809 the confusables data mentions plus the 44 decimal-number-system zeros it does not already mention), and every CJK Unified Ideograph, whose name the UAX #44 4.8 rule generates from the code point itself — that rule covers the blocks whole, so all 101984 of them get a name, not only the 1294 the confusables data mentions. Null for everything else: U+2603 SNOWMAN, unassigned code points, Hangul syllables outside the bundled rows. 109543 code points in all — a stated boundary, not a general character-name lookup.
example: CYRILLIC SMALL LETTER A
script string The ISO 15924 script code (UAX #24); Zzzz for unassigned code points.
example: Cyrl
scriptName string The script's Unicode long name with underscores rendered as spaces ("Canadian_Aboriginal" becomes "Canadian Aboriginal").
example: Cyrillic
augmentedScripts string[] The UTS #39 5.1 augmented script set: Script_Extensions plus the multi-script writing systems (Han adds Hanb, Jpan and Kore; Hiragana and Katakana add Jpan; Hangul adds Kore; Bopomofo adds Hanb). ["ALL"] for Common and Inherited characters.
example: ["Cyrl"]
identifierStatus string Allowed or Restricted under the UTS #39 General Security Profile.
example: Allowed
identifierTypes string[] The Identifier_Type set — the reason behind the status. Code points the property does not list default to ["Not_Character"].
example: ["Recommended"]
prototype string nullable The UTS #39 prototype this character maps to, which may be several characters (m maps to rn). Null when confusables.txt does not map it.
example: a
prototypeCodePoints string[] The prototype as U+XXXX code points; empty when there is none.
example: ["U+0061"]
skeleton string The skeleton of this single character. Empty for a Default_Ignorable character, which step 2 removes entirely.
example: a
confusableWith object[] Every other code point in the confusables data whose one-character skeleton is identical: character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes, plus `prototype`. Bounded — the largest group in this snapshot is 80 code points.
example: [{"character":"a","codePoint":"U+0061","name":"LATIN SMALL LETTER A","script":"Latn","scriptName":"Latin","identifierStatus":"Allowed","identifierTypes":["Recommended"],"prototype":null}]
confusableWithCount integer The length of `confusableWith`.
example: 23
intentionalPartners object[] Characters paired with this one in Unicode's hand-picked intentional-confusables list (glyphs designed to be identical in shape): character, codePoint (U+XXXX), name (nullable), script and scriptName (ISO 15924 code plus its Unicode long name), identifierStatus and identifierTypes. Empty for most characters — the list has only 77 pairs.
example: []
nfkc string The character under NFKC, for contrast: NFKC is not a confusable check. Computed by the runtime's normalizer rather than from this pinned snapshot.
example: а
inConfusablesData boolean Whether confusables.txt mentions this code point at all, as a mapped source or inside a prototype.
example: true
/api/confusables/character?char=%D0%B0 {
"data": {
"character": "а",
"codePoint": "U+0430",
"decimal": 1072,
"name": "CYRILLIC SMALL LETTER A",
"script": "Cyrl",
"scriptName": "Cyrillic",
"augmentedScripts": [
"Cyrl"
],
"identifierStatus": "Allowed",
"identifierTypes": [
"Recommended"
],
"prototype": "a",
"prototypeCodePoints": [
"U+0061"
],
"skeleton": "a",
"confusableWith": [
{
"character": "a",
"codePoint": "U+0061",
"name": "LATIN SMALL LETTER A",
"script": "Latn",
"scriptName": "Latin",
"identifierStatus": "Allowed",
"identifierTypes": [
"Recommended"
],
"prototype": null
},
{
"character": "ɑ",
"codePoint": "U+0251",
"name": "LATIN SMALL LETTER ALPHA",
"script": "Latn",
"scriptName": "Latin",
"identifierStatus": "Restricted",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/confusables/character?char=U%2B2160 {
"data": {
"character": "Ⅰ",
"codePoint": "U+2160",
"decimal": 8544,
"name": "ROMAN NUMERAL ONE",
"script": "Latn",
"scriptName": "Latin",
"augmentedScripts": [
"Latn"
],
"identifierStatus": "Restricted",
"identifierTypes": [
"Not_NFKC"
],
"prototype": "l",
"prototypeCodePoints": [
"U+006C"
],
"skeleton": "l",
"confusableWith": [
{
"character": "1",
"codePoint": "U+0031",
"name": "DIGIT ONE",
"script": "Zyyy",
"scriptName": "Common",
"identifierStatus": "Allowed",
"identifierTypes": [
"Recommended"
],
"prototype": "l"
},
{
"character": "I",
"codePoint": "U+0049",
"name": "LATIN CAPITAL LETTER I",
"script": "Latn",
"scriptName": "Latin",
"identifierStatus": "Allowed",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} | Parameter | Type | Default & allowed | Description |
|---|---|---|---|
char | string | allowed: 1 – 8 example: а | Keep only pairs containing this character on either side. Give the character itself or its code point in U+XXXX notation, so %20 and U+0020 both address the space. Not trimmed: a stray space makes the value two characters and a 400. |
char string Keep only pairs containing this character on either side. Give the character itself or its code point in U+XXXX notation, so %20 and U+0020 both address the space. Not trimmed: a stray space makes the value two characters and a 400.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
count | int | default: all matches allowed: 1 – 100 example: 3 | Limit the number of returned records (1–100). Defaults to all matches. |
fields | list | example: a,b | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: sameSkeleton | Return all fields except these (comma-separated). |
format | enum | default: json allowed: json | ndjson | csv example: csv | Response format: json envelope, ndjson (one record per line) or csv. |
pretty | boolean | default: false example: true | Pretty-print the JSON response. |
unwrap | boolean | default: false example: true | Drop the envelope: return the raw array/object without data/meta wrapper. |
count int Limit the number of returned records (1–100). Defaults to all matches.
fields list Return only these fields (comma-separated). Mutually exclusive with 'exclude'.
exclude list Return all fields except these (comma-separated).
format enum Response format: json envelope, ndjson (one record per line) or csv.
pretty boolean Pretty-print the JSON response.
unwrap boolean Drop the envelope: return the raw array/object without data/meta wrapper.
| Field | Type | Description | Example |
|---|---|---|---|
a | string | The pair's first character, in the file's own order. | A |
b | string | The pair's second character. | Α |
aCodePoint | string | `a` in U+XXXX notation. | U+0041 |
bCodePoint | string | `b` in U+XXXX notation. | U+0391 |
aName nullable | string | The Unicode Name of `a`; null if this snapshot bundles none. | LATIN CAPITAL LETTER A |
bName nullable | string | The Unicode Name of `b`; null if this snapshot bundles none. | GREEK CAPITAL LETTER ALPHA |
aScript | string | The ISO 15924 script code of `a`. | Latn |
bScript | string | The ISO 15924 script code of `b`. | Grek |
aIdentifierStatus | string | Allowed or Restricted for `a` under the General Security Profile. | Allowed |
bIdentifierStatus | string | Allowed or Restricted for `b`. | Allowed |
sameSkeleton | boolean | Whether the pair also shares a UTS #39 skeleton. True for all 77 rows in this snapshot — the field exists so a future release that breaks the agreement is visible rather than silent. | true |
a string The pair's first character, in the file's own order.
example: A
b string The pair's second character.
example: Α
aCodePoint string `a` in U+XXXX notation.
example: U+0041
bCodePoint string `b` in U+XXXX notation.
example: U+0391
aName string nullable The Unicode Name of `a`; null if this snapshot bundles none.
example: LATIN CAPITAL LETTER A
bName string nullable The Unicode Name of `b`; null if this snapshot bundles none.
example: GREEK CAPITAL LETTER ALPHA
aScript string The ISO 15924 script code of `a`.
example: Latn
bScript string The ISO 15924 script code of `b`.
example: Grek
aIdentifierStatus string Allowed or Restricted for `a` under the General Security Profile.
example: Allowed
bIdentifierStatus string Allowed or Restricted for `b`.
example: Allowed
sameSkeleton boolean Whether the pair also shares a UTS #39 skeleton. True for all 77 rows in this snapshot — the field exists so a future release that breaks the agreement is visible rather than silent.
example: true
/api/confusables/intentional {
"data": [
{
"a": "!",
"b": "ǃ",
"aCodePoint": "U+0021",
"bCodePoint": "U+01C3",
"aName": "EXCLAMATION MARK",
"bName": "LATIN LETTER RETROFLEX CLICK",
"aScript": "Zyyy",
"bScript": "Latn",
"aIdentifierStatus": "Restricted",
"bIdentifierStatus": "Restricted",
"sameSkeleton": true
},
{
"a": "A",
"b": "Α",
"aCodePoint": "U+0041",
"bCodePoint": "U+0391",
"aName": "LATIN CAPITAL LETTER A",
"bName": "GREEK CAPITAL LETTER ALPHA",
"aScript": "Latn",
"bScript": "Grek",
"aIdentifierStatus": "Allowed",
"bIdentifierStatus": "Allowed",
"sameSkeleton": true
},
{
"a": "B",
"b": "Β",
"aCodePoint": "U+0042",
"bCodePoint": "U+0392",
"aName": "LATIN CAPITAL LETTER B",
"bName": "GREEK CAPITAL LETTER BETA",
"aScript": "Latn",
"bScript": "Grek",
"aIdentifierStatus": "Allowed",
"bIdentifierStatus": "Allowed",
"sameSkeleton": true
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/confusables/intentional?char=A {
"data": [
{
"a": "A",
"b": "Α",
"aCodePoint": "U+0041",
"bCodePoint": "U+0391",
"aName": "LATIN CAPITAL LETTER A",
"bName": "GREEK CAPITAL LETTER ALPHA",
"aScript": "Latn",
"bScript": "Grek",
"aIdentifierStatus": "Allowed",
"bIdentifierStatus": "Allowed",
"sameSkeleton": true
}
],
"meta": {
"endpoint": "confusables",
"route": "/intentional",
"count": 1,
"params": {
"char": "A"
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} Unicode's own confusables data, computed exactly: Unicode Technical Standard #39 17.0.0, 6565 characters mapped to 3416 prototypes, the 77 intentional-confusable pairs, and the Script, Script_Extensions, Identifier_Status, Identifier_Type, Bidi_Class and character-name tables the checks need. Every file is pinned by SHA-256 to a dated 17.0.0 snapshot and regenerated by a committed parity script; nothing is fetched while your request is served.
/skeleton runs the four steps of UTS #39 section 4 in order: convert to NFD, drop Default_Ignorable_Code_Point characters, replace each remaining character with its prototype, then NFD again. Two strings are confusable when their skeletons are identical, which is why раураl (Cyrillic р, а, у) and paypal both reduce to paypal.
It means Unicode records those characters as visually confusable in some typefaces. Section 4 chose a simple, transitive mechanism over an expensive "closeness" metric and says so plainly: in some cases "the test may be overly inclusive". Fonts differ, contextual shaping changes shapes for whole clusters at a time, and section 6 opens by admitting confusability cannot be an exact science. This endpoint hands you the evidence and the reasons; the policy is yours. It is not an anti-phishing product and it issues no verdict about intent.
/skeleton — the skeleton of one string, plus a row per character showing exactly which substitution happened./compare — whether two strings are confusable, and if so which of section 4's three classes the pair falls into: single-script (their resolved script sets share a script), mixed-script (disjoint), whole-script (disjoint and both strings single-script, as scope against Cyrillic ѕсоре). Those classes nest rather than partition — section 4 states that every whole-script confusable is also a mixed-script confusable — so confusableClass reports the narrowest label that applies and whole-script implies mixed-script. Plus a positional, code-point-by-code-point list of where they differ./inspect — one string as a candidate identifier: its cover set of scripts, its restriction level, which characters the General Security Profile restricts and why, which have prototypes, and whether it mixes decimal number systems./character — everything about one character, including every other code point in the data that reduces to the same skeleton./intentional — Unicode's hand-picked list of pairs whose glyphs are designed to be identical in shape.skeleton(X) is defined as bidiSkeleton(LTR, X), which reorders X by UAX #9 before reducing it. We implement only the fast path section 4 spells out — no right-to-left or explicit directional-formatting characters — and we are deliberately stricter than the one sentence describing it: an override such as U+202E RIGHT-TO-LEFT OVERRIDE is given class R by UAX #9 rule X6 and can reorder a string of otherwise left-to-right characters, so it also disqualifies the fast path. When it does not apply you get internalSkeleton instead, clearly labelled (definition, bidiExact, bidiNote), and /compare returns confusable: null with a reason rather than a guess. We do not implement UAX #9.
hello world is Unrestricted/inspect computes all six levels of section 5.2 exactly. The identifier profile applied is the General Security Profile of section 3.1 — Identifier_Status=Allowed — and not the UAX #31 Default Identifier Syntax. Step 1 of the algorithm returns Unrestricted for any character outside that profile, and U+0020 SPACE has Identifier_Type=Not_XID. So hello world and paypal! are Unrestricted: the levels answer "would this be an acceptable identifier", not "is this text suspicious". ㄅㄆabc is Unrestricted too, because as of Unicode 17 Bopomofo is not a UAX #31 Recommended script and its characters are Limited_Use.
Section 5.3 covers only General_Category=Nd characters: each digit maps to its number system's zero, and more than one zero means mixed numbers. Roman numerals and vulgar fractions are not decimal digits, so they are reported through restrictedCharacters instead — every one of them is Restricted anyway (U+2160 ROMAN NUMERAL ONE is Not_NFKC).
Of the 6565 characters UTS #39 maps to a prototype, 2784 are left completely unchanged by NFKC, and 637 of those are Identifier_Status=Allowed — including the Cyrillic letters that make раураl look like paypal. /character returns nfkc next to prototype so the two can be compared directly: U+2011 NON-BREAKING HYPHEN has NFKC U+2010 but prototype U+002D.
bidiSkeleton(RTL, ·) and bidiSkeleton(FS, ·) are not offered at all.m maps to rn, so microsoft and rnicrosoft share a skeleton). Section 5.4 notes they do not cover sequence-to-sequence cases, so w against vv is not confusable here. We add no rules of our own./inspect route already has a utf16 parameter for surrogate analysis.Zzzz (which makes a string mixed-script), Identifier_Type ["Not_Character"], status Restricted./inspect returns one minimal cover set per section 5.1 and reports scriptsMinimal. Proving minimality is an exact set-cover search, which is bounded here by a node budget; a greedy cover that already matches the disjointness lower bound needs no search at all, which covers everything identifier-shaped. Only a deliberately pathological mix of dozens of overlapping multi-script characters could exhaust the budget, and then scriptsMinimal is false rather than a claim we cannot back.name is bounded on purpose, and the bound is "bundled rows plus the CJK rule". The bundled rows cover the 8853 code points that can appear in an answer — the 8809 the confusables data mentions plus the 44 decimal-number-system zeros it does not already mention — as 7559 stored rows plus the 1294 CJK ideographs among them, which the UAX #44 4.8 rule names from the code point instead of a row. That rule is a range test over whole CJK Unified Ideograph blocks, so name is also non-null for the other 100690 ideographs the data never mentions: answering null for a name the UCD rule already gives correctly would be worse. Everything else is null (U+2603 SNOWMAN, unassigned code points, Hangul syllables outside the bundled rows), so /api/confusables/character names 109543 of Unicode's 1,114,112 code points and is not a general character-name lookup.Everything except normalization is pinned to Unicode 17.0.0 and read from the bundle — never from \p{…} property escapes, whose Unicode version belongs to the runtime rather than to us. NFD (and the nfkc field) come from the runtime's String.prototype.normalize, which cannot be bundled; Unicode's Normalization Stability Policy guarantees that an already-encoded character's decomposition never changes, so runtime skew can only affect characters assigned after the runtime's Unicode version.
The UTS #39 section 4 prototype string: convert to NFD, drop default-ignorable characters, replace each character with its confusables prototype, then NFD again. Two strings are confusable when their skeletons are identical — раураl and paypal both reduce to paypal.
No. It means Unicode records those characters as visually confusable in some typefaces. It is evidence for a review rule of your own, not a verdict, and this is not an anti-phishing product.
No. Of the 6565 characters UTS #39 maps to a prototype, 2784 are left completely unchanged by NFKC, and 637 of those are Identifier_Status=Allowed — including the Cyrillic letters that make раураl look like paypal.
/api/confusables/inspect returns all six levels exactly. A space, or any other character outside the General Security Profile, makes a string Unrestricted — the levels answer whether something is an acceptable identifier, not whether it looks suspicious.
rn looks like m? Yes, in the one direction Unicode records it: confusables.txt maps U+006D LATIN SMALL LETTER M to the two-character prototype rn, so microsoft and rnicrosoft share a skeleton. We invent no sequence rules of our own, so w and vv are not confusable in this data.
For a pair, yes: /api/confusables/compare classifies a confusable pair as single-script, mixed-script or whole-script exactly as section 4 defines them, so scope against Cyrillic ѕсоре comes back whole-script. Section 4's classes nest — every whole-script confusable is also a mixed-script one — and confusableClass reports the narrowest label, so treat whole-script as implying mixed-script rather than filtering on the string mixed-script alone. Searching for whether one string has some whole-script confusable is deliberately not implemented.
Convert internationalized domains to punycode and back, and get the exact IDNA rule that rejected a label — length, hyphens, combining marks or the bidi rule.
Inspect UTF-16 and Unicode scalar values, apply the four standard normalization forms, and segment text into graphemes, words or sentences.
Hostile-but-safe text fixtures: RTL, bidi controls, CJK width, combining marks, ZWJ emoji, zero-width and length bombs — plus pseudo-localization.
Random usernames in four distinct styles — handle, gamer, professional, anonymous — with a maxLength that is honored by construction, never by truncation.
Validate email syntax against RFC 5322, RFC 5321 transport limits or the HTML input profile — local/domain split and the exact rule that failed. No DNS, ever.