text string required The suspect text, exactly as it appears (whitespace is preserved). Maximum 2000 UTF-16 code units.
Diagnose and repair mojibake — the byte round-trip that turned café into café and привет into привет — across 28 legacy encodings.
Authoritative reference data or standards computation
Encodes the input back through all 28 supported legacy encoders and reads the bytes as strict UTF-8. Returns the byte evidence, a labelled-confidence hypothesis, every rival reading and the recovered text — or an honest none when nothing decodes cleanly.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
text
required
| string | allowed: ≥ 1 example: café | The suspect text, exactly as it appears (whitespace is preserved). Maximum 2000 UTF-16 code units. |
layers | int | default: 2 allowed: 1 – 3 example: 3 | How many stacked broken decodes to unwind. 1 diagnoses single mojibake only; 2 also catches double-encoded text such as café. When this ceiling stops the unwind early the answer is marked `complete: false` and warns, never silently returned as finished. |
text string required The suspect text, exactly as it appears (whitespace is preserved). Maximum 2000 UTF-16 code units.
layers int How many stacked broken decodes to unwind. 1 diagnoses single mojibake only; 2 also catches double-encoded text such as café. When this ceiling stops the unwind early the answer is marked `complete: false` and warns, never silently returned as finished.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: input,hypothesis | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: complete | 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 |
|---|---|---|---|
input | string | The suspect text exactly as received; whitespace is never trimmed. | café |
hypothesis | string | `utf8-as-<encoding>` with the punctuation removed (utf8-as-windows1252, utf8-as-windows1251, utf8-as-koi8r…), plus the two historical spellings utf8-as-latin1 (ISO-8859-1) and utf8-as-latin9 (ISO-8859-15); `double-encoded` for two or more layers; `none` when nothing decodes cleanly. | utf8-as-windows1252 |
encoding nullable | string | The encoding this hypothesis blames — the one whose decoder would have produced the input. Null when there is no recovery. Use this instead of parsing `hypothesis`. | windows-1252 |
confidence | string | high | medium | low — how strong the byte evidence is for this hypothesis. | medium |
confidenceRationale | string | Plain-English reason for the confidence label, naming the actual bytes, how many rival encodings agree or disagree and any reason the answer is not final. | Encoding the input as windows-1252 gives 5 byte(s) holding a single 2-byte UTF-8 sequence. It decodes cleanly, but a string can legitimately contain those two characters, so this stays a hypothesis. iso-8859-1, iso-8859-15, windows-1254 and 1 more produce the identical recovery; macintosh also decodes, but to a different string. |
evidence | object[] | The byte trail: one row per non-ASCII character with its code point, the byte it encodes to, its zero-based code-point position and its role in the UTF-8 sequence. Capped at 32 rows. | [{"codePoint":"U+00C3","byte":"0xC3","position":3,"note":"Byte 1 of a 2-byte UTF-8 sequence decoding to U+00E9 'é'."},{"codePoint":"U+00A9","byte":"0xA9","position":4,"note":"Byte 2 of 2 in the UTF-8 sequence for U+00E9 'é'."}] |
recovered nullable | string | The text the hypothesis recovers, or null when no clean recovery exists. | café |
recoveredLayers | integer | How many broken decodes were undone; 0 when nothing was recovered. | 1 |
alternatives | object[] | Every other supported encoding that also unwinds the input, grouped by the text it recovers (`recovered`, `layers`, `encodings`). A group matching `recovered` above corroborates the answer; one that differs is genuine ambiguity and is listed first. Capped at 8 groups. | [{"recovered":"caf̩","layers":1,"encodings":["macintosh"]},{"recovered":"café","layers":1,"encodings":["iso-8859-1","iso-8859-15","windows-1254","iso-8859-14"]}] |
complete nullable | boolean | True when the recovery no longer unwinds under any supported encoding. False when it still does — normally because `layers` ran out, so the text is only partly repaired. Null when there is no recovery. | true |
input string The suspect text exactly as received; whitespace is never trimmed.
example: café
hypothesis string `utf8-as-<encoding>` with the punctuation removed (utf8-as-windows1252, utf8-as-windows1251, utf8-as-koi8r…), plus the two historical spellings utf8-as-latin1 (ISO-8859-1) and utf8-as-latin9 (ISO-8859-15); `double-encoded` for two or more layers; `none` when nothing decodes cleanly.
example: utf8-as-windows1252
encoding string nullable The encoding this hypothesis blames — the one whose decoder would have produced the input. Null when there is no recovery. Use this instead of parsing `hypothesis`.
example: windows-1252
confidence string high | medium | low — how strong the byte evidence is for this hypothesis.
example: medium
confidenceRationale string Plain-English reason for the confidence label, naming the actual bytes, how many rival encodings agree or disagree and any reason the answer is not final.
example: Encoding the input as windows-1252 gives 5 byte(s) holding a single 2-byte UTF-8 sequence. It decodes cleanly, but a string can legitimately contain those two characters, so this stays a hypothesis. iso-8859-1, iso-8859-15, windows-1254 and 1 more produce the identical recovery; macintosh also decodes, but to a different string.
evidence object[] The byte trail: one row per non-ASCII character with its code point, the byte it encodes to, its zero-based code-point position and its role in the UTF-8 sequence. Capped at 32 rows.
example: [{"codePoint":"U+00C3","byte":"0xC3","position":3,"note":"Byte 1 of a 2-byte UTF-8 sequence decoding to U+00E9 'é'."},{"codePoint":"U+00A9","byte":"0xA9","position":4,"note":"Byte 2 of 2 in the UTF-8 sequence for U+00E9 'é'."}]
recovered string nullable The text the hypothesis recovers, or null when no clean recovery exists.
example: café
recoveredLayers integer How many broken decodes were undone; 0 when nothing was recovered.
example: 1
alternatives object[] Every other supported encoding that also unwinds the input, grouped by the text it recovers (`recovered`, `layers`, `encodings`). A group matching `recovered` above corroborates the answer; one that differs is genuine ambiguity and is listed first. Capped at 8 groups.
example: [{"recovered":"caf̩","layers":1,"encodings":["macintosh"]},{"recovered":"café","layers":1,"encodings":["iso-8859-1","iso-8859-15","windows-1254","iso-8859-14"]}]
complete boolean nullable True when the recovery no longer unwinds under any supported encoding. False when it still does — normally because `layers` ran out, so the text is only partly repaired. Null when there is no recovery.
example: true
/api/text-encoding/diagnose?text=caf%C3%83%C2%A9 {
"data": {
"input": "café",
"hypothesis": "utf8-as-windows1252",
"encoding": "windows-1252",
"confidence": "medium",
"confidenceRationale": "Encoding the input as windows-1252 gives 5 byte(s) holding a single 2-byte UTF-8 sequence. It decodes cleanly, but a string can legitimately contain those two characters, so this stays a hypothesis. iso-8859-1, iso-8859-15, windows-1254 and 1 more produce the identical recovery; macintosh also decodes, but to a different string.",
"evidence": [
{
"codePoint": "U+00C3",
"byte": "0xC3",
"position": 3,
"note": "Byte 1 of a 2-byte UTF-8 sequence decoding to U+00E9 'é'."
},
{
"codePoint": "U+00A9",
"byte": "0xA9",
"position": 4,
"note": "Byte 2 of 2 in the UTF-8 sequence for U+00E9 'é'."
}
],
"recovered": "café",
"recoveredLayers": 1,
"alternatives": [
{
"recovered": "caf̩",
"layers": 1,
"encodings": [
"macintosh"
]
},
{
"recovered": "café",
"layers": 1,
"encodings": [
"iso-8859-1",
"iso-8859-15",
"windows-1254",
"iso-8859-14"
]
…
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} /api/text-encoding/diagnose?text=%C3%90%C2%BF%C3%91%E2%82%AC%C3%90%C2%B8%C3%90%C2%B2%C3%90%C2%B5%C3%91%E2%80%9A {
"data": {
"input": "привет",
"hypothesis": "utf8-as-windows1252",
"encoding": "windows-1252",
"confidence": "high",
"confidenceRationale": "Encoding the input as windows-1252 gives 12 byte(s) holding 6 well-formed multi-byte UTF-8 sequence(s), the longest 2 bytes — a trail that is very hard to write by accident.",
"evidence": [
{
"codePoint": "U+00D0",
"byte": "0xD0",
"position": 0,
"note": "Byte 1 of a 2-byte UTF-8 sequence decoding to U+043F 'п'."
},
{
"codePoint": "U+00BF",
"byte": "0xBF",
"position": 1,
"note": "Byte 2 of 2 in the UTF-8 sequence for U+043F 'п'."
},
{
"codePoint": "U+00D1",
"byte": "0xD1",
"position": 2,
"note": "Byte 1 of a 2-byte UTF-8 sequence decoding to U+0440 'р'."
},
{
"codePoint": "U+20AC",
"byte": "0x80",
"position": 3,
"note": "Byte 2 of 2 in the UTF-8 sequence for U+0440 'р'."
},
{
"codePoint": "U+00D0",
"byte": "0xD0",
"position": 4,
"note": "Byte 1 of a 2-byte UTF-8 sequence decoding to U+0438 'и'."
},
{
"codePoint": "U+00B8",
…
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} /api/text-encoding/diagnose?text=caf%C3%83%C6%92%C3%82%C2%A9 {
"data": {
"input": "café",
"hypothesis": "double-encoded",
"encoding": "windows-1252",
"confidence": "high",
"confidenceRationale": "Encoding the input as windows-1252 gives 7 byte(s) holding 2 well-formed multi-byte UTF-8 sequence(s), the longest 2 bytes, and the result needed 2 decode layers to come back — a trail that is very hard to write by accident. windows-1254 produces the identical recovery.",
"evidence": [
{
"codePoint": "U+00C3",
"byte": "0xC3",
"position": 3,
"note": "Byte 1 of a 2-byte UTF-8 sequence decoding to U+00C3 'Ã'."
},
{
"codePoint": "U+0192",
"byte": "0x83",
"position": 4,
"note": "Byte 2 of 2 in the UTF-8 sequence for U+00C3 'Ã'."
},
{
"codePoint": "U+00C2",
"byte": "0xC2",
"position": 5,
"note": "Byte 1 of a 2-byte UTF-8 sequence decoding to U+00A9 '©'."
},
{
"codePoint": "U+00A9",
"byte": "0xA9",
"position": 6,
"note": "Byte 2 of 2 in the UTF-8 sequence for U+00A9 '©'."
}
],
"recovered": "café",
"recoveredLayers": 2,
"alternatives": [
{
"recovered": "café",
"layers": 2,
"encodings": [
…
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} /api/text-encoding/diagnose?text=caf%C3%83%C6%92%C3%82%C2%A9&layers=1 {
"data": {
"input": "café",
"hypothesis": "utf8-as-windows1252",
"encoding": "windows-1252",
"confidence": "medium",
"confidenceRationale": "Encoding the input as windows-1252 gives 7 byte(s) holding 2 well-formed multi-byte UTF-8 sequence(s), the longest 2 bytes — a trail that is very hard to write by accident. windows-1254 produces the identical recovery. All 1 layer(s) allowed by 'layers' were used and the recovery still unwinds, so this is a partial repair.",
"evidence": [
{
"codePoint": "U+00C3",
"byte": "0xC3",
"position": 3,
"note": "Byte 1 of a 2-byte UTF-8 sequence decoding to U+00C3 'Ã'."
},
{
"codePoint": "U+0192",
"byte": "0x83",
"position": 4,
"note": "Byte 2 of 2 in the UTF-8 sequence for U+00C3 'Ã'."
},
{
"codePoint": "U+00C2",
"byte": "0xC2",
"position": 5,
"note": "Byte 1 of a 2-byte UTF-8 sequence decoding to U+00A9 '©'."
},
{
"codePoint": "U+00A9",
"byte": "0xA9",
"position": 6,
"note": "Byte 2 of 2 in the UTF-8 sequence for U+00A9 '©'."
}
],
"recovered": "café",
"recoveredLayers": 1,
"alternatives": [
{
"recovered": "café",
"layers": 1,
"encodings": [
…
]
}
} /api/text-encoding/diagnose?text=Don%C3%A2%E2%82%AC%E2%84%A2t%20panic {
"data": {
"input": "Don’t panic",
"hypothesis": "utf8-as-windows1252",
"encoding": "windows-1252",
"confidence": "high",
"confidenceRationale": "Encoding the input as windows-1252 gives 13 byte(s) holding 1 well-formed multi-byte UTF-8 sequence(s), the longest 3 bytes — a trail that is very hard to write by accident. windows-1250, windows-1256, windows-1254 and 1 more produce the identical recovery.",
"evidence": [
{
"codePoint": "U+00E2",
"byte": "0xE2",
"position": 3,
"note": "Byte 1 of a 3-byte UTF-8 sequence decoding to U+2019 '’'."
},
{
"codePoint": "U+20AC",
"byte": "0x80",
"position": 4,
"note": "Byte 2 of 3 in the UTF-8 sequence for U+2019 '’'."
},
{
"codePoint": "U+2122",
"byte": "0x99",
"position": 5,
"note": "Byte 3 of 3 in the UTF-8 sequence for U+2019 '’'."
}
],
"recovered": "Don’t panic",
"recoveredLayers": 1,
"alternatives": [
{
"recovered": "Don’t panic",
"layers": 1,
"encodings": [
"windows-1250",
"windows-1256",
"windows-1254",
"windows-1258"
]
}
…
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} /api/text-encoding/diagnose?text=%C3%83%C2%86r%C3%83%C5%BE {
"data": {
"input": "ÃrÞ",
"hypothesis": "utf8-as-latin9",
"encoding": "iso-8859-15",
"confidence": "high",
"confidenceRationale": "Encoding the input as iso-8859-15 gives 5 byte(s) holding 2 well-formed multi-byte UTF-8 sequence(s), the longest 2 bytes — a trail that is very hard to write by accident. iso-8859-4 and iso-8859-10 also decode, but to different strings.",
"evidence": [
{
"codePoint": "U+00C3",
"byte": "0xC3",
"position": 0,
"note": "Byte 1 of a 2-byte UTF-8 sequence decoding to U+00C6 'Æ'."
},
{
"codePoint": "U+0086",
"byte": "0x86",
"position": 1,
"note": "Byte 2 of 2 in the UTF-8 sequence for U+00C6 'Æ'."
},
{
"codePoint": "U+00C3",
"byte": "0xC3",
"position": 3,
"note": "Byte 1 of a 2-byte UTF-8 sequence decoding to U+00F8 'ø'."
},
{
"codePoint": "U+017E",
"byte": "0xB8",
"position": 4,
"note": "Byte 2 of 2 in the UTF-8 sequence for U+00F8 'ø'."
}
],
"recovered": "Ærø",
"recoveredLayers": 1,
"alternatives": [
{
"recovered": "Ærþ",
"layers": 1,
"encodings": [
…
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} /api/text-encoding/diagnose?text=caf%C3%A9 {
"data": {
"input": "café",
"hypothesis": "none",
"encoding": null,
"confidence": "high",
"confidenceRationale": "No supported single-byte encoding turns the input into well-formed UTF-8 bytes, and no UTF-8 lead byte is followed by a continuation byte, so there is no mojibake trail to explain.",
"evidence": [
{
"codePoint": "U+00E9",
"byte": "0xE9",
"position": 3,
"note": "Not part of a well-formed UTF-8 sequence."
}
],
"recovered": null,
"recoveredLayers": 0,
"alternatives": [],
"complete": null
},
"meta": {
"endpoint": "text-encoding",
"route": "/diagnose",
"params": {
"text": "café",
"layers": 2
},
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} Encodes the text with from, then decodes those exact bytes with to. Mismatch the pair and you get reproducible mojibake for your fixtures; match it and you get a plain byte dump.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
text
required
| string | allowed: ≥ 1 example: café | The text to round-trip, exactly as given (whitespace is preserved). Maximum 2000 UTF-16 code units. |
from | enum | default: utf-8 allowed: utf-8 | windows-1252 | iso-8859-1 | iso-8859-15 | windows-1251 | windows-1250 | windows-1256 | windows-1254 | windows-1253 | windows-1255 | windows-1257 | windows-1258 | windows-874 | koi8-r | koi8-u | ibm866 | iso-8859-2 | iso-8859-5 | iso-8859-7 | iso-8859-6 | iso-8859-8 | iso-8859-4 | iso-8859-13 | iso-8859-3 | iso-8859-10 | iso-8859-14 | iso-8859-16 | macintosh | x-mac-cyrillic example: windows-1252 | Encoding used to turn the text into bytes. Characters it cannot represent are reported in `unmappable`, never replaced. |
to
required
| enum | allowed: utf-8 | windows-1252 | iso-8859-1 | iso-8859-15 | windows-1251 | windows-1250 | windows-1256 | windows-1254 | windows-1253 | windows-1255 | windows-1257 | windows-1258 | windows-874 | koi8-r | koi8-u | ibm866 | iso-8859-2 | iso-8859-5 | iso-8859-7 | iso-8859-6 | iso-8859-8 | iso-8859-4 | iso-8859-13 | iso-8859-3 | iso-8859-10 | iso-8859-14 | iso-8859-16 | macintosh | x-mac-cyrillic example: windows-1252 | Encoding used to read those bytes back. 20 of the 28 single-byte targets decode all 256 byte values; the other 8 leave some bytes unassigned, which decode to U+FFFD with a warning. `utf-8` uses the Encoding Standard's U+FFFD replacement mode. |
text string required The text to round-trip, exactly as given (whitespace is preserved). Maximum 2000 UTF-16 code units.
from enum Encoding used to turn the text into bytes. Characters it cannot represent are reported in `unmappable`, never replaced.
to enum required Encoding used to read those bytes back. 20 of the 28 single-byte targets decode all 256 byte values; the other 8 leave some bytes unassigned, which decode to U+FFFD with a warning. `utf-8` uses the Encoding Standard's U+FFFD replacement mode.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: input,from | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: unmappable | 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 |
|---|---|---|---|
input | string | The text exactly as received; whitespace is never trimmed. | café |
from | string | Encoding used to turn the text into bytes. | utf-8 |
to | string | Encoding used to read those bytes back. | windows-1252 |
bytes | string (space-separated uppercase hex) | The exact intermediate bytes, one two-digit pair per byte. | 63 61 66 C3 A9 |
byteLength | integer | Number of intermediate bytes. | 5 |
output | string | Result of decoding those bytes with the target encoding — the mojibake, when the pair is mismatched. | café |
unmappable | object[] | Characters the source encoding cannot represent, with their zero-based code-point position. They are omitted from the bytes, never substituted; always empty for from=utf-8, which can represent every Unicode scalar value. | [{"codePoint":"U+20AC","position":0}] |
input string The text exactly as received; whitespace is never trimmed.
example: café
from string Encoding used to turn the text into bytes.
example: utf-8
to string Encoding used to read those bytes back.
example: windows-1252
bytes string (space-separated uppercase hex) The exact intermediate bytes, one two-digit pair per byte.
example: 63 61 66 C3 A9
byteLength integer Number of intermediate bytes.
example: 5
output string Result of decoding those bytes with the target encoding — the mojibake, when the pair is mismatched.
example: café
unmappable object[] Characters the source encoding cannot represent, with their zero-based code-point position. They are omitted from the bytes, never substituted; always empty for from=utf-8, which can represent every Unicode scalar value.
example: [{"codePoint":"U+20AC","position":0}]
/api/text-encoding/transcode?text=caf%C3%A9&to=windows-1252 {
"data": {
"input": "café",
"from": "utf-8",
"to": "windows-1252",
"bytes": "63 61 66 C3 A9",
"byteLength": 5,
"output": "café",
"unmappable": []
},
"meta": {
"endpoint": "text-encoding",
"route": "/transcode",
"params": {
"text": "café",
"from": "utf-8",
"to": "windows-1252"
},
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} /api/text-encoding/transcode?text=%D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82&to=windows-1252 {
"data": {
"input": "привет",
"from": "utf-8",
"to": "windows-1252",
"bytes": "D0 BF D1 80 D0 B8 D0 B2 D0 B5 D1 82",
"byteLength": 12,
"output": "привет",
"unmappable": []
},
"meta": {
"endpoint": "text-encoding",
"route": "/transcode",
"params": {
"text": "привет",
"from": "utf-8",
"to": "windows-1252"
},
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} /api/text-encoding/transcode?text=%F0%9F%98%80&to=iso-8859-1 {
"data": {
"input": "😀",
"from": "utf-8",
"to": "iso-8859-1",
"bytes": "F0 9F 98 80",
"byteLength": 4,
"output": "ð",
"unmappable": []
},
"meta": {
"endpoint": "text-encoding",
"route": "/transcode",
"params": {
"text": "😀",
"from": "utf-8",
"to": "iso-8859-1"
},
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} /api/text-encoding/transcode?text=Gr%C3%BC%C3%9Fe&from=windows-1252&to=macintosh {
"data": {
"input": "Grüße",
"from": "windows-1252",
"to": "macintosh",
"bytes": "47 72 FC DF 65",
"byteLength": 5,
"output": "Gr¸fle",
"unmappable": []
},
"meta": {
"endpoint": "text-encoding",
"route": "/transcode",
"params": {
"text": "Grüße",
"from": "windows-1252",
"to": "macintosh"
},
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} /api/text-encoding/transcode?text=%CE%95%CE%BB%CE%BB%CE%AC%CE%B4%CE%B1&from=windows-1253&to=koi8-r {
"data": {
"input": "Ελλάδα",
"from": "windows-1253",
"to": "koi8-r",
"bytes": "C5 EB EB DC E4 E1",
"byteLength": 6,
"output": "еККэДА",
"unmappable": []
},
"meta": {
"endpoint": "text-encoding",
"route": "/transcode",
"params": {
"text": "Ελλάδα",
"from": "windows-1253",
"to": "koi8-r"
},
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} /api/text-encoding/transcode?text=%E2%82%AC10&from=iso-8859-1&to=utf-8 {
"data": {
"input": "€10",
"from": "iso-8859-1",
"to": "utf-8",
"bytes": "31 30",
"byteLength": 2,
"output": "10",
"unmappable": [
{
"codePoint": "U+20AC",
"position": 0
}
]
},
"meta": {
"endpoint": "text-encoding",
"route": "/transcode",
"params": {
"text": "€10",
"from": "iso-8859-1",
"to": "utf-8"
},
"generatedAt": "2026-07-29T08:14:28.000Z",
"warnings": [
"1 character(s) have no iso-8859-1 byte and were omitted from 'bytes' rather than substituted; they are listed in 'unmappable'."
]
}
} /api/text-encoding/transcode?text=%E2%82%AC10&from=iso-8859-15&to=windows-1252 {
"data": {
"input": "€10",
"from": "iso-8859-15",
"to": "windows-1252",
"bytes": "A4 31 30",
"byteLength": 3,
"output": "¤10",
"unmappable": []
},
"meta": {
"endpoint": "text-encoding",
"route": "/transcode",
"params": {
"text": "€10",
"from": "iso-8859-15",
"to": "windows-1252"
},
"generatedAt": "2026-07-29T08:14:28.000Z"
}
} /api/text-encoding/transcode?text=%C2%A1%C2%A2%C2%A3&from=iso-8859-1&to=iso-8859-6 {
"data": {
"input": "¡¢£",
"from": "iso-8859-1",
"to": "iso-8859-6",
"bytes": "A1 A2 A3",
"byteLength": 3,
"output": "���",
"unmappable": []
},
"meta": {
"endpoint": "text-encoding",
"route": "/transcode",
"params": {
"text": "¡¢£",
"from": "iso-8859-1",
"to": "iso-8859-6"
},
"generatedAt": "2026-07-29T08:14:28.000Z",
"warnings": [
"3 byte(s) (0xA1, 0xA2 and 0xA3) are unassigned in iso-8859-6 and decoded to the U+FFFD replacement character, as the Encoding Standard's single-byte decoder specifies; iso-8859-6 leaves 45 of its 128 high bytes unassigned."
]
}
} Mojibake is what you get when text is encoded as UTF-8 and then read back with a single-byte legacy encoding: café becomes café, don’t becomes don’t, привет becomes привет, Ελλάδα becomes Ελλάδα. This endpoint does both directions of that round-trip using the WHATWG Encoding Standard's index tables — all 27 of them, plus the ISO/IEC 8859-1 identity mapping, which the Encoding Standard does not publish because it hands that label to windows-1252. The tables are bundled and pinned (each with its own publication date and the SHA-256 of the exact file) rather than delegated to TextDecoder — legacy encoding labels are not guaranteed to exist in every runtime, and an answer that changes with the host is not an answer.
Every name below is a registered Encoding Standard label — with one deliberate exception, iso-8859-1, which the Standard maps onto windows-1252 and this API keeps separate (see below). The order here is the order /diagnose breaks ties in — roughly how often each one is the culprit in a real mojibake report.
windows-1252, iso-8859-1, iso-8859-15, iso-8859-16, macintosh (Mac OS Roman)windows-1250, windows-1257, iso-8859-2, iso-8859-4, iso-8859-13, iso-8859-10 (Nordic), iso-8859-3 (South European), iso-8859-14 (Celtic)windows-1251, koi8-r, koi8-u, iso-8859-5, ibm866, x-mac-cyrillicwindows-1253, iso-8859-7windows-1255, iso-8859-8windows-1256, iso-8859-6windows-1254, windows-1258, windows-874/diagnose encodes your suspect text back into bytes with every one of those 28 legacy encoders and reads the result as strict UTF-8. evidence shows the resulting byte trail character by character — à → 0xC3 → "Byte 1 of a 2-byte UTF-8 sequence decoding to U+00E9 'é'" — so you can check the reasoning instead of trusting it. encoding names the culprit, and alternatives lists every rival reading grouped by the text it recovers, so the encodings that merely agree are visibly separated from the one that disagrees. layers (1–3, default 2) controls how many stacked decodes it will unwind, which is what separates café from the double-encoded café. When that ceiling is what stopped the unwind, the answer says so instead of pretending to be finished: complete is false, the confidence never reaches high, and a warning names the cause and how to go further.
/transcode is the generator: ?text=café&to=windows-1252 returns the bytes 63 61 66 C3 A9 and the string café, ready to paste into a test fixture. Deliberate garbling is the half of this problem that repair tools tend to skip, and a repair path you cannot feed broken input to is a repair path you have not tested.
é really is a legitimate two-character string, and the Latin-family encodings agree on most of 0xA0–0xFF, so a dozen of them routinely recover the same text — so every answer carries a confidence of high, medium or low, a confidenceRationale that counts the rivals on both sides, and an alternatives array naming them. high needs two or more UTF-8 sequences, a sequence of three or more bytes, or two stacked layers, and a recovery that no longer unwinds; a single two-byte trail unwound in one layer is only ever medium.hypothesis is none — a partly-repaired string is exactly the kind of plausible wrong answer this API refuses to produce. Half-fixed text such as café naïve lands there: the confidence drops to low and a warning points you at /transcode for the broken fragment. Ordinary text such as café, which simply is not mojibake, is none with high confidence instead. A decode that does succeed is still rejected when it introduces junk the input did not already contain — a U+FFFD replacement character, a C1 control, a Unicode noncharacter or a private-use code point — and the rationale names the exact code point and position it found rather than describing the rule. The one partial answer that is returned is labelled rather than dressed up: a recovery that still unwinds — because layers ran out, or because a different encoding continues the chain — comes back with complete: false, a confidence capped below high, and a warning saying which of the two it is.日本語 → 日本語); it is the encoding that misread it that must be single-byte.iso-8859-1 here means ISO/IEC 8859-1. The Encoding Standard maps the label iso-8859-1 (and latin1, cp819, ascii) onto windows-1252, which is why browsers turn byte 0x92 into ’ rather than a C1 control. This API exposes both behaviours as separately named encodings so you can tell which one your pipeline actually performed. They differ for exactly 27 byte values, all inside 0x80–0x9F, which windows-1252 fills with printable characters and ISO-8859-1 leaves as C1 controls — which is why one emoji has two different mojibakes: 😀 becomes 😀 through windows-1252 but ð plus three invisible controls through true ISO-8859-1. Encoding labels are deliberately not accepted as values: latin1 and cp1252 both mean windows-1252 to the Standard, which is exactly the confusion this endpoint exists to expose./transcode never substitutes ? or U+FFFD on the encode side: € has no ISO-8859-1 byte, so it is listed in unmappable and omitted from bytes. from=utf-8 never reports anything, because UTF-8 can represent every Unicode scalar value. On the decode side, to=utf-8 uses the Encoding Standard's replacement error mode — which is what a real system does — and warns you how many decoding errors it replaced. That count excludes any U+FFFD your text genuinely contained, so ?text=caf%EF%BF%BD&to=utf-8 is correctly reported as well-formed.windows-1253 3, windows-1255 10, windows-1257 2, windows-874 8, iso-8859-7 3, iso-8859-6 45, iso-8859-8 36, iso-8859-3 7 unassigned bytes — and the Encoding Standard's decoder errors on those. /transcode follows the replacement error mode, emitting U+FFFD and warning with the exact byte values, rather than inventing a character for a byte the Standard does not define. That step is lossy: the byte value survives nowhere in the output, so /diagnose answers none for any text containing U+FFFD and says exactly why, instead of guessing what the byte used to be.Both routes accept up to 2,000 UTF-16 code units and are pure computation: no clock, no randomness, no network. The same query returns the same bytes forever.
Because é was stored as its two UTF-8 bytes 0xC3 0xA9 and then read back one byte at a time as windows-1252, where those bytes are à and ©. /diagnose?text=café shows that byte trail and returns café.
It is a curly apostrophe, U+2019. In UTF-8 that is the three bytes 0xE2 0x80 0x99, which windows-1252 renders as â, € and ™. The same fault turns “ into “ and ” into †followed by an invisible control character.
That is UTF-8 Cyrillic read back as windows-1252 — each two-byte Cyrillic character became Ð or Ñ plus one more character. /diagnose?text=привет returns привет with the byte trail. Text mangled through windows-1251, KOI8-R, IBM866, ISO-8859-5 or x-mac-cyrillic is diagnosed the same way.
Yes — that is what /transcode is for. ?text=café&from=utf-8&to=windows-1252 returns the bytes 63 61 66 C3 A9 and the garbled string café, so you can feed your repair path real broken input.
All 27 legacy single-byte encodings in the WHATWG Encoding Standard — every windows-125x and ISO-8859 part, KOI8-R/U, IBM866, windows-874, macintosh and x-mac-cyrillic — plus true ISO/IEC 8859-1, which the Standard itself folds into windows-1252. The multi-byte CJK encodings (Shift_JIS, EUC-JP, EUC-KR, Big5, GBK/gb18030) are not supported. Japanese or Chinese text garbled through a single-byte encoding still recovers perfectly.
It reports a hypothesis with a high/medium/low confidence, a written rationale, an evidence array giving every byte and its role in the UTF-8 sequence, and an alternatives array naming every rival encoding grouped by the text it recovers. A single two-byte trail unwound in one layer is never labelled high — and neither is a recovery that still unwinds, which is flagged with complete: false and a warning.
Inspect UTF-16 and Unicode scalar values, apply the four standard normalization forms, and segment text into graphemes, words or sentences.
Strict RFC 4648 base16, base32, base64 and base64url encoding/decoding with Unicode-safe UTF-8, padding control and malformed-input errors.
The complete IANA media type registry with file extensions merged in — look up any type or extension, or strictly parse a Content-Type value into its parts.
Parse absolute URLs or resolve relative references with the WHATWG URL algorithm — canonical hosts, ordered query pairs and no credential leakage.
Fake file metadata — kebab-case names, the correct MIME type for every extension, plausible per-kind sizes, unix paths and seeded SHA-256-style checksums.
Parse RFC 9110 Accept media ranges and deterministically select the best available representation using specificity, parameters and q weights.