domain string required Unicode domain or single label to convert, up to 1024 characters. The three UTS #46 label separators U+3002, U+FF0E and U+FF61 are folded to '.' before splitting.
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.
Authoritative reference data or standards computation
Maps Unicode labels with UTS #46 — an xn-- label is validated without mapping, as the standard requires — then Punycode-encodes only the labels that need it. A label that breaks a rule but can still be converted keeps its A-label, alongside valid=false and a warning; ascii is null only when a label cannot be converted at all.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
domain
required
| string | allowed: 1 – 1024 example: münchen.de | Unicode domain or single label to convert, up to 1024 characters. The three UTS #46 label separators U+3002, U+FF0E and U+FF61 are folded to '.' before splitting. |
mode | enum | default: domain allowed: domain | label example: label | domain splits the input at dots and treats each part as a label; label treats the whole input as one label, so an embedded dot is reported as a rule violation instead of a separator. |
transitional | boolean | default: false example: true | Apply UTS #46 transitional processing (deprecated) to the deviation characters: sharp s becomes ss, final sigma becomes sigma, and the zero-width joiner and non-joiner are removed. Existing xn-- labels are never remapped. |
useStd3AsciiRules | boolean | default: true example: false | Require every ASCII code point in a label to be a lower-case letter, digit or hyphen. Turn it off to accept underscore labels such as _dmarc that DNS carries but IDNA does not permit. It cannot admit everything: '# % / : < > ? @ [ \ ] ^ |', space, tab, newline and the other C0 controls are forbidden domain code points in the WHATWG URL host parser — the only UTS #46 entry point the runtime offers — and are reported as forbidden-character whatever this flag is set to. |
domain string required Unicode domain or single label to convert, up to 1024 characters. The three UTS #46 label separators U+3002, U+FF0E and U+FF61 are folded to '.' before splitting.
mode enum domain splits the input at dots and treats each part as a label; label treats the whole input as one label, so an embedded dot is reported as a rule violation instead of a separator.
transitional boolean Apply UTS #46 transitional processing (deprecated) to the deviation characters: sharp s becomes ss, final sigma becomes sigma, and the zero-width joiner and non-joiner are removed. Existing xn-- labels are never remapped.
useStd3AsciiRules boolean Require every ASCII code point in a label to be a lower-case letter, digit or hyphen. Turn it off to accept underscore labels such as _dmarc that DNS carries but IDNA does not permit. It cannot admit everything: '# % / : < > ? @ [ \ ] ^ |', space, tab, newline and the other C0 controls are forbidden domain code points in the WHATWG URL host parser — the only UTS #46 entry point the runtime offers — and are reported as forbidden-character whatever this flag is set to.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: input,ascii | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: labels | 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 domain exactly as supplied, after surrounding whitespace is trimmed. | münchen.de |
ascii nullable | string | ASCII (A-label) form, with 'xn--' added only to labels containing non-ASCII code points. Null when a label could not be converted at all: it holds a code point the URL host parser forbids, the runtime refused to map it, or it is an xn-- label that does not decode. | xn--mnchen-3ya.de |
unicode | string | Unicode (U-label) form: labels supplied as Unicode after UTS #46 mapping (lower-cased, NFC-normalized, compatibility characters folded), and labels supplied as xn-- shown as their decoded content, which the standard validates without mapping and so is not re-normalized. | münchen.de |
isIdn | boolean | True when at least one label needs Punycode. | true |
asciiLength nullable | integer | Byte length of the ASCII form excluding the DNS root label and its dot; the limit is 253. Null when ascii is null. | 17 |
valid | boolean | True when every rule this API checks passes. Call /analyze for the rules that failed. | true |
labels | object[] | Per-label conversion: index, input, unicode, ascii, isAce, isIdn, asciiLength and valid. | [{"index":0,"input":"münchen","unicode":"münchen","ascii":"xn--mnchen-3ya","isAce":false,"isIdn":true,"asciiLength":14,"valid":true}] |
input string The domain exactly as supplied, after surrounding whitespace is trimmed.
example: münchen.de
ascii string nullable ASCII (A-label) form, with 'xn--' added only to labels containing non-ASCII code points. Null when a label could not be converted at all: it holds a code point the URL host parser forbids, the runtime refused to map it, or it is an xn-- label that does not decode.
example: xn--mnchen-3ya.de
unicode string Unicode (U-label) form: labels supplied as Unicode after UTS #46 mapping (lower-cased, NFC-normalized, compatibility characters folded), and labels supplied as xn-- shown as their decoded content, which the standard validates without mapping and so is not re-normalized.
example: münchen.de
isIdn boolean True when at least one label needs Punycode.
example: true
asciiLength integer nullable Byte length of the ASCII form excluding the DNS root label and its dot; the limit is 253. Null when ascii is null.
example: 17
valid boolean True when every rule this API checks passes. Call /analyze for the rules that failed.
example: true
labels object[] Per-label conversion: index, input, unicode, ascii, isAce, isIdn, asciiLength and valid.
example: [{"index":0,"input":"münchen","unicode":"münchen","ascii":"xn--mnchen-3ya","isAce":false,"isIdn":true,"asciiLength":14,"valid":true}]
/api/punycode/encode?domain=m%C3%BCnchen.de {
"data": {
"input": "münchen.de",
"ascii": "xn--mnchen-3ya.de",
"unicode": "münchen.de",
"isIdn": true,
"asciiLength": 17,
"valid": true,
"labels": [
{
"index": 0,
"input": "münchen",
"unicode": "münchen",
"ascii": "xn--mnchen-3ya",
"isAce": false,
"isIdn": true,
"asciiLength": 14,
"valid": true
},
{
"index": 1,
"input": "de",
"unicode": "de",
"ascii": "de",
"isAce": false,
"isIdn": false,
"asciiLength": 2,
"valid": true
}
]
},
"meta": {
"endpoint": "punycode",
"route": "/encode",
"params": {
"domain": "münchen.de",
"mode": "domain",
"transitional": false,
"useStd3AsciiRules": true
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/punycode/encode?domain=fa%C3%9F.de&transitional=true {
"data": {
"input": "faß.de",
"ascii": "fass.de",
"unicode": "fass.de",
"isIdn": false,
"asciiLength": 7,
"valid": true,
"labels": [
{
"index": 0,
"input": "faß",
"unicode": "fass",
"ascii": "fass",
"isAce": false,
"isIdn": false,
"asciiLength": 4,
"valid": true
},
{
"index": 1,
"input": "de",
"unicode": "de",
"ascii": "de",
"isAce": false,
"isIdn": false,
"asciiLength": 2,
"valid": true
}
]
},
"meta": {
"endpoint": "punycode",
"route": "/encode",
"params": {
"domain": "faß.de",
"mode": "domain",
"transitional": true,
"useStd3AsciiRules": true
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/punycode/encode?domain=%E6%97%A5%E6%9C%AC%E8%AA%9E&mode=label {
"data": {
"input": "日本語",
"ascii": "xn--wgv71a119e",
"unicode": "日本語",
"isIdn": true,
"asciiLength": 14,
"valid": true,
"labels": [
{
"index": 0,
"input": "日本語",
"unicode": "日本語",
"ascii": "xn--wgv71a119e",
"isAce": false,
"isIdn": true,
"asciiLength": 14,
"valid": true
}
]
},
"meta": {
"endpoint": "punycode",
"route": "/encode",
"params": {
"domain": "日本語",
"mode": "label",
"transitional": false,
"useStd3AsciiRules": true
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/punycode/encode?domain=_dmarc.example.com&useStd3AsciiRules=false {
"data": {
"input": "_dmarc.example.com",
"ascii": "_dmarc.example.com",
"unicode": "_dmarc.example.com",
"isIdn": false,
"asciiLength": 18,
"valid": true,
"labels": [
{
"index": 0,
"input": "_dmarc",
"unicode": "_dmarc",
"ascii": "_dmarc",
"isAce": false,
"isIdn": false,
"asciiLength": 6,
"valid": true
},
{
"index": 1,
"input": "example",
"unicode": "example",
"ascii": "example",
"isAce": false,
"isIdn": false,
"asciiLength": 7,
"valid": true
},
{
"index": 2,
"input": "com",
"unicode": "com",
"ascii": "com",
"isAce": false,
"isIdn": false,
"asciiLength": 3,
"valid": true
}
]
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} Pure RFC 3492 decoding: no UTS #46 mapping beyond ASCII case folding, and no validity checking. roundTripsExactly is false when re-encoding the decoded label does not reproduce the supplied A-label byte for byte.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
domain
required
| string | allowed: 1 – 1024 example: xn--mnchen-3ya.de | ASCII domain or single label to decode, up to 1024 characters. Non-ASCII input is a 400 — use /encode for that. |
mode | enum | default: domain allowed: domain | label example: label | domain splits the input at dots and treats each part as a label; label treats the whole input as one label, so an embedded dot is reported as a rule violation instead of a separator. |
domain string required ASCII domain or single label to decode, up to 1024 characters. Non-ASCII input is a 400 — use /encode for that.
mode enum domain splits the input at dots and treats each part as a label; label treats the whole input as one label, so an embedded dot is reported as a rule violation instead of a separator.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: input,ascii | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: labels | 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 domain exactly as supplied, after surrounding whitespace is trimmed. | xn--mnchen-3ya.de |
ascii | string | The input lower-cased, which is the canonical case for A-labels in the DNS. | xn--mnchen-3ya.de |
unicode | string | Decoded U-label form; a label that fails to decode is left unchanged. | münchen.de |
isIdn | boolean | True when at least one label carried the xn-- ACE prefix and decoded successfully. | true |
roundTripsExactly | boolean | True when re-encoding every decoded label with RFC 3492 reproduces the supplied A-label exactly. | true |
labels | object[] | Per-label decoding: index, ascii, unicode, isAce, isIdn, roundTripsExactly, canonicalAscii (the form a conforming encoder would produce) and error. | [{"index":0,"ascii":"xn--mnchen-3ya","unicode":"münchen","isAce":true,"isIdn":true,"roundTripsExactly":true,"canonicalAscii":"xn--mnchen-3ya","error":null}] |
input string The domain exactly as supplied, after surrounding whitespace is trimmed.
example: xn--mnchen-3ya.de
ascii string The input lower-cased, which is the canonical case for A-labels in the DNS.
example: xn--mnchen-3ya.de
unicode string Decoded U-label form; a label that fails to decode is left unchanged.
example: münchen.de
isIdn boolean True when at least one label carried the xn-- ACE prefix and decoded successfully.
example: true
roundTripsExactly boolean True when re-encoding every decoded label with RFC 3492 reproduces the supplied A-label exactly.
example: true
labels object[] Per-label decoding: index, ascii, unicode, isAce, isIdn, roundTripsExactly, canonicalAscii (the form a conforming encoder would produce) and error.
example: [{"index":0,"ascii":"xn--mnchen-3ya","unicode":"münchen","isAce":true,"isIdn":true,"roundTripsExactly":true,"canonicalAscii":"xn--mnchen-3ya","error":null}]
/api/punycode/decode?domain=xn--mnchen-3ya.de {
"data": {
"input": "xn--mnchen-3ya.de",
"ascii": "xn--mnchen-3ya.de",
"unicode": "münchen.de",
"isIdn": true,
"roundTripsExactly": true,
"labels": [
{
"index": 0,
"ascii": "xn--mnchen-3ya",
"unicode": "münchen",
"isAce": true,
"isIdn": true,
"roundTripsExactly": true,
"canonicalAscii": "xn--mnchen-3ya",
"error": null
},
{
"index": 1,
"ascii": "de",
"unicode": "de",
"isAce": false,
"isIdn": false,
"roundTripsExactly": true,
"canonicalAscii": null,
"error": null
}
]
},
"meta": {
"endpoint": "punycode",
"route": "/decode",
"params": {
"domain": "xn--mnchen-3ya.de",
"mode": "domain"
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/punycode/decode?domain=xn--wgv71a119e.jp {
"data": {
"input": "xn--wgv71a119e.jp",
"ascii": "xn--wgv71a119e.jp",
"unicode": "日本語.jp",
"isIdn": true,
"roundTripsExactly": true,
"labels": [
{
"index": 0,
"ascii": "xn--wgv71a119e",
"unicode": "日本語",
"isAce": true,
"isIdn": true,
"roundTripsExactly": true,
"canonicalAscii": "xn--wgv71a119e",
"error": null
},
{
"index": 1,
"ascii": "jp",
"unicode": "jp",
"isAce": false,
"isIdn": false,
"roundTripsExactly": true,
"canonicalAscii": null,
"error": null
}
]
},
"meta": {
"endpoint": "punycode",
"route": "/decode",
"params": {
"domain": "xn--wgv71a119e.jp",
"mode": "domain"
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/punycode/decode?domain=xn--mnchen-3ya-.example {
"data": {
"input": "xn--mnchen-3ya-.example",
"ascii": "xn--mnchen-3ya-.example",
"unicode": "mnchen-3ya.example",
"isIdn": true,
"roundTripsExactly": false,
"labels": [
{
"index": 0,
"ascii": "xn--mnchen-3ya-",
"unicode": "mnchen-3ya",
"isAce": true,
"isIdn": true,
"roundTripsExactly": false,
"canonicalAscii": "mnchen-3ya",
"error": null
},
{
"index": 1,
"ascii": "example",
"unicode": "example",
"isAce": false,
"isIdn": false,
"roundTripsExactly": true,
"canonicalAscii": null,
"error": null
}
]
},
"meta": {
"endpoint": "punycode",
"route": "/decode",
"params": {
"domain": "xn--mnchen-3ya-.example",
"mode": "domain"
},
"generatedAt": "2026-07-30T15:14:08.000Z",
"warnings": [
"At least one label is not canonical RFC 3492 punycode; see labels[].canonicalAscii and labels[].error."
]
}
} /api/punycode/decode?domain=xn--wgv71a119e&mode=label {
"data": {
"input": "xn--wgv71a119e",
"ascii": "xn--wgv71a119e",
"unicode": "日本語",
"isIdn": true,
"roundTripsExactly": true,
"labels": [
{
"index": 0,
"ascii": "xn--wgv71a119e",
"unicode": "日本語",
"isAce": true,
"isIdn": true,
"roundTripsExactly": true,
"canonicalAscii": "xn--wgv71a119e",
"error": null
}
]
},
"meta": {
"endpoint": "punycode",
"route": "/decode",
"params": {
"domain": "xn--wgv71a119e",
"mode": "label"
},
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} Runs every rule this API implements and returns one failure entry per violation, each citing the RFC or UTS #46 clause it comes from.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
domain
required
| string | allowed: 1 – 1024 example: münchen.de | Domain to analyze, up to 1024 characters. Unicode labels and xn-- labels may be mixed in the same name. |
transitional | boolean | default: false example: true | Apply UTS #46 transitional processing (deprecated) to the deviation characters: sharp s becomes ss, final sigma becomes sigma, and the zero-width joiner and non-joiner are removed. Existing xn-- labels are never remapped. |
useStd3AsciiRules | boolean | default: true example: false | Require every ASCII code point in a label to be a lower-case letter, digit or hyphen. Turn it off to accept underscore labels such as _dmarc that DNS carries but IDNA does not permit. It cannot admit everything: '# % / : < > ? @ [ \ ] ^ |', space, tab, newline and the other C0 controls are forbidden domain code points in the WHATWG URL host parser — the only UTS #46 entry point the runtime offers — and are reported as forbidden-character whatever this flag is set to. |
domain string required Domain to analyze, up to 1024 characters. Unicode labels and xn-- labels may be mixed in the same name.
transitional boolean Apply UTS #46 transitional processing (deprecated) to the deviation characters: sharp s becomes ss, final sigma becomes sigma, and the zero-width joiner and non-joiner are removed. Existing xn-- labels are never remapped.
useStd3AsciiRules boolean Require every ASCII code point in a label to be a lower-case letter, digit or hyphen. Turn it off to accept underscore labels such as _dmarc that DNS carries but IDNA does not permit. It cannot admit everything: '# % / : < > ? @ [ \ ] ^ |', space, tab, newline and the other C0 controls are forbidden domain code points in the WHATWG URL host parser — the only UTS #46 entry point the runtime offers — and are reported as forbidden-character whatever this flag is set to.
| Parameter | Type | Default & allowed | Description |
|---|---|---|---|
fields | list | example: input,ascii | Return only these fields (comma-separated). Mutually exclusive with 'exclude'. |
exclude | list | example: failures | 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 domain exactly as supplied, after surrounding whitespace is trimmed. | münchen.de |
ascii nullable | string | ASCII (A-label) form, or null when a label could not be converted at all. A label that merely breaks a rule — including the bidi rule — still gets its A-label. | xn--mnchen-3ya.de |
unicode | string | Unicode (U-label) form after mapping and Punycode decoding. For a label the runtime refused to map, this is a best-effort case-folded NFC form and the failures array says so with uts46-processing. | münchen.de |
valid | boolean | True when the failures array is empty. | true |
isIdn | boolean | True when at least one label needs Punycode. | true |
isBidiDomain | boolean | True when some label contains a right-to-left character, which is what makes the RFC 5893 Bidi Rule apply to every label in the name. | false |
asciiLength nullable | integer | Byte length of the ASCII form excluding the DNS root label and its dot; the limit is 253. | 17 |
labels | object[] | Per-label result: index, input, unicode, ascii, isAce, isIdn, asciiLength, valid and direction — rtl when the label contains an R, AL or AN character (RFC 5893 section 1.4), which is the test that makes the whole name a bidi domain, not the first-character test that picks which conditions apply. | [{"index":0,"input":"münchen","unicode":"münchen","ascii":"xn--mnchen-3ya","isAce":false,"isIdn":true,"asciiLength":14,"valid":true,"direction":"ltr"}] |
failures | object[] | One entry per violated rule: label, labelIndex (null for whole-name rules), rule and a detail sentence naming the clause it comes from. Empty when the domain is valid. | [{"label":"ab--cd","labelIndex":0,"rule":"hyphen-3-4","detail":"A label must not contain '--' in the third and fourth character positions; those positions are reserved for ACE prefixes such as 'xn--' (RFC 5891 section 4.2.3.1)."}] |
input string The domain exactly as supplied, after surrounding whitespace is trimmed.
example: münchen.de
ascii string nullable ASCII (A-label) form, or null when a label could not be converted at all. A label that merely breaks a rule — including the bidi rule — still gets its A-label.
example: xn--mnchen-3ya.de
unicode string Unicode (U-label) form after mapping and Punycode decoding. For a label the runtime refused to map, this is a best-effort case-folded NFC form and the failures array says so with uts46-processing.
example: münchen.de
valid boolean True when the failures array is empty.
example: true
isIdn boolean True when at least one label needs Punycode.
example: true
isBidiDomain boolean True when some label contains a right-to-left character, which is what makes the RFC 5893 Bidi Rule apply to every label in the name.
example: false
asciiLength integer nullable Byte length of the ASCII form excluding the DNS root label and its dot; the limit is 253.
example: 17
labels object[] Per-label result: index, input, unicode, ascii, isAce, isIdn, asciiLength, valid and direction — rtl when the label contains an R, AL or AN character (RFC 5893 section 1.4), which is the test that makes the whole name a bidi domain, not the first-character test that picks which conditions apply.
example: [{"index":0,"input":"münchen","unicode":"münchen","ascii":"xn--mnchen-3ya","isAce":false,"isIdn":true,"asciiLength":14,"valid":true,"direction":"ltr"}]
failures object[] One entry per violated rule: label, labelIndex (null for whole-name rules), rule and a detail sentence naming the clause it comes from. Empty when the domain is valid.
example: [{"label":"ab--cd","labelIndex":0,"rule":"hyphen-3-4","detail":"A label must not contain '--' in the third and fourth character positions; those positions are reserved for ACE prefixes such as 'xn--' (RFC 5891 section 4.2.3.1)."}]
/api/punycode/analyze?domain=m%C3%BCnchen.de {
"data": {
"input": "münchen.de",
"ascii": "xn--mnchen-3ya.de",
"unicode": "münchen.de",
"valid": true,
"isIdn": true,
"isBidiDomain": false,
"asciiLength": 17,
"labels": [
{
"index": 0,
"input": "münchen",
"unicode": "münchen",
"ascii": "xn--mnchen-3ya",
"isAce": false,
"isIdn": true,
"asciiLength": 14,
"valid": true,
"direction": "ltr"
},
{
"index": 1,
"input": "de",
"unicode": "de",
"ascii": "de",
"isAce": false,
"isIdn": false,
"asciiLength": 2,
"valid": true,
"direction": "ltr"
}
],
"failures": []
},
"meta": {
"endpoint": "punycode",
"route": "/analyze",
"params": {
"domain": "münchen.de",
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/punycode/analyze?domain=abc%D8%B9%D8%B1%D8%A8%D9%8A.example {
"data": {
"input": "abcعربي.example",
"ascii": "xn--abc-uze8b7bxh.example",
"unicode": "abcعربي.example",
"valid": false,
"isIdn": true,
"isBidiDomain": true,
"asciiLength": 25,
"labels": [
{
"index": 0,
"input": "abcعربي",
"unicode": "abcعربي",
"ascii": "xn--abc-uze8b7bxh",
"isAce": false,
"isIdn": true,
"asciiLength": 17,
"valid": false,
"direction": "rtl"
},
{
"index": 1,
"input": "example",
"unicode": "example",
"ascii": "example",
"isAce": false,
"isIdn": false,
"asciiLength": 7,
"valid": true,
"direction": "ltr"
}
],
"failures": [
{
"label": "abcعربي",
"labelIndex": 0,
"rule": "bidi-rule",
"detail": "RFC 5893 condition 5: U+0639 has Bidi property AL, which an LTR label may not contain."
},
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/punycode/analyze?domain=ab--cd.example {
"data": {
"input": "ab--cd.example",
"ascii": "ab--cd.example",
"unicode": "ab--cd.example",
"valid": false,
"isIdn": false,
"isBidiDomain": false,
"asciiLength": 14,
"labels": [
{
"index": 0,
"input": "ab--cd",
"unicode": "ab--cd",
"ascii": "ab--cd",
"isAce": false,
"isIdn": false,
"asciiLength": 6,
"valid": false,
"direction": "ltr"
},
{
"index": 1,
"input": "example",
"unicode": "example",
"ascii": "example",
"isAce": false,
"isIdn": false,
"asciiLength": 7,
"valid": true,
"direction": "ltr"
}
],
"failures": [
{
"label": "ab--cd",
"labelIndex": 0,
"rule": "hyphen-3-4",
"detail": "A label must not contain '--' in the third and fourth character positions; those positions are reserved for ACE prefixes such as 'xn--' (RFC 5891 section 4.2.3.1)."
}
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} /api/punycode/analyze?domain=_dmarc.example.com {
"data": {
"input": "_dmarc.example.com",
"ascii": "_dmarc.example.com",
"unicode": "_dmarc.example.com",
"valid": false,
"isIdn": false,
"isBidiDomain": false,
"asciiLength": 18,
"labels": [
{
"index": 0,
"input": "_dmarc",
"unicode": "_dmarc",
"ascii": "_dmarc",
"isAce": false,
"isIdn": false,
"asciiLength": 6,
"valid": false,
"direction": "ltr"
},
{
"index": 1,
"input": "example",
"unicode": "example",
"ascii": "example",
"isAce": false,
"isIdn": false,
"asciiLength": 7,
"valid": true,
"direction": "ltr"
},
{
"index": 2,
"input": "com",
"unicode": "com",
"ascii": "com",
"isAce": false,
"isIdn": false,
"asciiLength": 3,
…
"generatedAt": "2026-07-30T15:14:08.000Z"
}
} A complete RFC 3492 Punycode codec plus the label rules that decide whether an internationalized domain name is legal — and, when it is not, which rule rejected it.
/encode converts a Unicode domain to its ASCII (A-label) form. Only labels containing non-ASCII code points receive the xn-- prefix; plain ASCII labels are lower-cased and passed through./decode converts xn-- labels back to Unicode with this API's own RFC 3492 decoder and reports roundTripsExactly, which catches non-canonical punycode: strings that decode successfully but are not the encoding a conforming encoder would produce. In practice almost every real hit is an A-label whose content turns out to be pure ASCII (xn--ab- decodes to ab, which must never carry the prefix) or one carrying a redundant delimiter; the check is a byte comparison against this module's own encoder, so it catches any deviation, but do not expect it to flag much beyond those./analyze is the explain route. It returns a failures array of { label, labelIndex, rule, detail } naming every rule that failed, drawn from this vocabulary: empty-label, label-too-long, domain-too-long, label-contains-dot, not-nfc, hyphen-3-4, hyphen-start-end, leading-combining-mark, std3-ascii-rules, bidi-rule, forbidden-character, punycode-decode, non-canonical-punycode and uts46-processing.The Punycode codec (RFC 3492), the DNS limits of 63 bytes per label and 253 bytes per name, the hyphen restrictions and the leading-combining-mark rule of RFC 5891 section 4.2.3, the NFC check, the STD3 ASCII rules and all six numbered conditions of the RFC 5893 Bidi Rule are implemented in this endpoint from the specification text. Condition 1 is applied as written: the label's own first character decides whether the rest is scored as an RTL label (conditions 2, 3, 4) or an LTR label (conditions 5, 6), while "contains an R, AL or AN character anywhere" is the separate section 1.4 test for whether the name is a Bidi domain name at all. The Bidi Rule needs the Unicode Bidi_Class property, which JavaScript does not expose through regular-expression property escapes, so a pinned Unicode 17.0.0 extract of DerivedBidiClass.txt ships with the Worker. The NFC and General_Category=Mark tests use the runtime's Unicode data, because that is what String.normalize and \p{M} are.
Code-point mapping and repertoire — which characters are valid, ignored, mapped or disallowed — is performed by the serving runtime's own UTS #46 implementation through the WHATWG URL host parser, exactly as a browser does it. IdnaMappingTable.txt is over 750 KB of source data covering every code point, and is deliberately not bundled, so mapping results follow the runtime's Unicode version and can shift when the runtime is upgraded; no frozen Unicode version is claimed for that step. The runtime also applies the CONTEXTJ joiner rules.
That parser insists on running the Bidi Rule itself, and refuses to hand back an ASCII form for a label that breaks it. Since mapping is per-code-point and normalization is closed under concatenation (UAX #15), such a label is mapped in contiguous pieces cut at the boundaries between right-to-left and other characters, and reassembled — so a name like abcعربي.example still reports its A-label xn--abc-uze8b7bxh.example next to valid: false. The pieces come from the runtime, never from a guess: if the runtime refuses one of them too, ascii stays null, the rule is reported as uts46-processing, and the rules that read ASCII positions out of a mapped label (hyphen-3-4, hyphen-start-end, std3-ascii-rules) are not scored at all, because the string they would be scored on is not the mapped one.
valid: true says nothing about whether a name is visually confusable with another — that question belongs to /api/confusables, which implements the UTS #39 skeleton and mixed-script tests.useStd3AsciiRules says: # % / : < > ? @ [ \ ] ^ |, space, tab, newline and the other C0 controls are forbidden domain code points in the WHATWG URL host parser, which is the only UTS #46 entry point the runtime offers. They are reported as forbidden-character rather than std3-ascii-rules. (< and > composed with U+0338 into ≮ and ≯ are ordinary valid characters and do pass.)Call /api/punycode/encode?domain=münchen.de and read ascii, which is xn--mnchen-3ya.de. Only labels containing non-ASCII code points get the xn-- prefix.
/api/punycode/analyze answers exactly that: failures names each broken rule — hyphen-3-4, leading-combining-mark, label-too-long, bidi-rule and the rest — with a detail sentence citing the clause it comes from.
One right-to-left character anywhere in a name makes it a Bidi domain name, and RFC 5893's six conditions then apply to every label in it. Each label's own first character picks the conditions: abcعربي starts with a Latin letter, so it is an LTR label and its Arabic letters break conditions 5 and 6, while 1عربي starts with a digit and breaks condition 1.
No — this endpoint only answers whether a name is well-formed under IDNA. Confusable detection is Unicode Technical Standard #39, a separate standard with its own data, and it lives at /api/confusables.
No. No DNS query is made at any point, and registry rules such as per-TLD script restrictions are out of scope, so a valid name may still be unregistrable or unresolvable.
It maps the UTS #46 deviation characters, so faß.de encodes to fass.de instead of xn--fa-hia.de. It is deprecated, defaults to false, and never remaps a label that is already in xn-- form.
The current IANA delegated TLD ASCII list — search all labels, filter punycode IDNs, or look up one TLD with an honest 404.
Parse absolute URLs or resolve relative references with the WHATWG URL algorithm — canonical hosts, ordered query pairs and no credential leakage.
Random internet building blocks — valid IPv4/IPv6 addresses, locally-administered MACs, non-resolvable .test domains and URLs, slugs, ports and TLDs.
Inspect UTF-16 and Unicode scalar values, apply the four standard normalization forms, and segment text into graphemes, words or sentences.
Canonicalize BCP 47 language tags and run RFC 4647 lookup or basic filtering with bounded, deterministic matching.
UTS #39 skeletons, mixed-script and identifier-status checks: whether two usernames or domains are visual lookalikes, and which characters differ.