Anvil βΊ Docs βΊ /v1/text/inspect
Text X-ray API: code points, graphemes, UTF-8/16 and invisible characters
Paste any short string and get an X-ray of what is really inside it: how many grapheme clusters a person sees, how many Unicode code points, how many UTF-8 bytes on the wire, and how long JavaScript's string.length thinks it is (UTF-16 units). Every character is named, categorised and labelled (emoji, zero-width joiner, skin tone, flag letter, controlβ¦). Findings call out bidirectional overrides, invisible characters, look-alike letters from other alphabets, mixed scripts and characters that need 4-byte UTF-8 (MySQL utf8mb4).
/v1/text/inspect?text=%F0%9F%91%A8%E2%80%8D%F0%9F%91%A9%E2%80%8D%F0%9F%91%A7&mojibake=1 returns JSON. No key, CORS on, 60 requests a minute./v1/text/inspect
Text X-ray: code points, graphemes, UTF-8/16 lengths and invisible characters. Change the values and press Send request to call the live API.
Parameters
| Name | Type | What it does | Default |
|---|---|---|---|
text required | string | The string to inspect, up to 4 KB of UTF-8. Never stored. | β |
form | string | Optional normalisation to return: nfc, nfd, nfkc or nfkd | β |
mojibake | boolean | If true, also show the UTF-8 bytes misread as Windows-1252 | false |
Example
Request:
GET https://api.oddlathe.com/v1/text/inspect?text=%F0%9F%91%A8%E2%80%8D%F0%9F%91%A9%E2%80%8D%F0%9F%91%A7&mojibake=1
Response (generated by the real code when these docs were last built):
{
"text": "π¨βπ©βπ§",
"grade": "clean",
"summary": {
"graphemes": 1,
"codepoints": 5,
"utf8_bytes": 18,
"utf16_units": 8,
"js_length": 8,
"is_nfc": true,
"is_nfd": true,
"is_nfkc": true,
"scripts": [
"Emoji"
]
},
"utf8_hex": "F0 9F 91 A8 E2 80 8D F0 9F 91 A9 E2 80 8D F0 9F 91 A7",
"utf16_hex": "D83D DC68 200D D83D DC69 200D D83D DC67",
"normalization": {},
"findings": [
{
"code": "utf16_vs_codepoints",
"severity": "info",
"message": "JavaScript/Java string length is 8 (UTF-16 units) but there are 5 Unicode code points (astral characters need a surrogate pair)."
},
{
"code": "needs_4byte",
"severity": "info",
"message": "Contains characters above U+FFFF (4-byte UTF-8). MySQL's old utf8/utf8mb3 charset would reject or squash them β use utf8mb4."
}
],
"goods": [
"Contains zero-width joiners β likely a multi-person or multi-part emoji.",
"UTF-8 uses 18 bytes for 5 code point(s).",
"1 grapheme cluster(s) from 5 code point(s) β some characters combine visually."
],
β¦
Code examples
curl -G 'https://api.oddlathe.com/v1/text/inspect' \
--data-urlencode 'text=π¨βπ©βπ§' \
--data-urlencode 'mojibake=1'const params = new URLSearchParams({
text: "π¨βπ©βπ§",
mojibake: "1"
});
const res = await fetch(`https://api.oddlathe.com/v1/text/inspect?${params}`);
if (!res.ok) throw new Error((await res.json()).error.message);
const data = await res.json();
console.log(data);import json, urllib.parse, urllib.request
params = urllib.parse.urlencode({"text": "π¨βπ©βπ§", "mojibake": "1"})
url = "https://api.oddlathe.com/v1/text/inspect?" + params
# Send a User-Agent: the edge network refuses Python's default one.
req = urllib.request.Request(url, headers={"User-Agent": "my-script/1.0"})
with urllib.request.urlopen(req, timeout=10) as r:
data = json.load(r)
print(data)When it's useful
- Explaining why an emoji 'is 8 characters long' in JavaScript but one picture on screen
- Catching bidi / zero-width spoofing in usernames, hostnames or chat before you store them
- Debugging mojibake (UTF-8 read as Windows-1252) and MySQL 'Incorrect string value' / utf8mb3 issues
- CI checks that display names don't contain RLO, soft hyphens or homoglyphs
- Teaching Unicode: graphemes vs code points vs bytes
Good to know
- Pass
text(required, up to 4 KB of UTF-8). Optionalform=nfc|nfd|nfkc|nfkdreturns that normalised form undernormalized. Optionalmojibake=1adds a Windows-1252 misread preview of the UTF-8 bytes. gradeisclean,notable(warnings) orsuspicious(bidi overrides / lone surrogates). Each item infindingshas acode, aseverity(error, warn, info) and a plain-wordsmessage.- Finding codes include
bidi_override,invisible,bom,homoglyph,mixed_scripts,utf16_vs_codepoints,not_nfc,nfkc_changes,needs_4byte,tag_chars,noncharacterandsurrogate. summary.js_lengthmatches JavaScript / Java / .NET string length (UTF-16 code units). A family emoji is often 1 grapheme, 5 code points, 8 UTF-16 units and 18 UTF-8 bytes.- Grapheme clustering follows common emoji rules (ZWJ, variation selectors, skin tones, keycaps, tags, regional-indicator pairs) plus combining marks β enough for emoji X-rays without a full UAX #29 dependency.
- Detail lists are capped (80 graphemes / 200 code points);
truncatedsays when that happened. Nothing is fetched or stored. - For a clickable kid-friendly X-ray, see Wirebook's emoji lesson. For tofu boxes, mojibake and utf8mb4 fixes see Tooltern's emoji tofu, mojibake and utf8mb4 guides.
X-rays a short string the way Unicode sees it: grapheme clusters vs code points vs UTF-8 bytes vs JavaScript string.length (UTF-16 units), every character named and labelled (emoji, zero-width joiner, skin tone, flag letter, controlβ¦), and findings for bidirectional overrides, invisible characters, look-alike letters from other alphabets, mixed scripts and characters that need 4-byte UTF-8. Optional form= returns a normalised copy; mojibake=1 previews the classic Windows-1252 misread. Pure text analysis: nothing is fetched or stored.
Errors
Errors are JSON like {"error": {"code": "β¦", "message": "β¦"}}. The ones this endpoint can return:
missing_param(400): a required parameter is missing or emptybad_param(400): a value is the wrong type or out of range (the message says which)too_long(413): text is over 4 KBrate_limited(429): over 60 requests a minute; wait for Retry-After seconds
Questions
Why is π length 2 in JavaScript?
JavaScript counts UTF-16 code units. Characters above U+FFFF need a surrogate pair, so one emoji is length 2. This API reports both <code>codepoints</code> and <code>js_length</code>.
What's a grapheme?
What a person sees as one character. A family emoji is several code points glued with zero-width joiners, but one grapheme.
Can it tell me a string is safe?
No. It flags common spoofing ingredients (bidi overrides, invisible chars, look-alikes). A clean grade is not a security verdict.
Does it fetch anything?
Never. It only looks at the characters you sent.
Related endpoints
/v1/slugTurn a title into a clean URL slug/v1/hashHash a short text (MD5, SHA-1, SHA-2, SHA-3, BLAKE2b)/v1/url/inspectLink X-ray: who really owns a link, and its warning signs
Prefer a page to an API?
- What is this? detector (in your browser) on Tooltern: same job, nothing leaves your browser.
- Guide: Mojibake: UTF-8 read as Windows-1252
Published Β· OpenAPI 3.1 spec Β· Limits and privacy