The data, as JSON.
Every character and every word on this site is available as JSON — frequency ranks, readings, definitions, radicals, TOCFL levels and example words. No key, no sign-up, no rate limit to negotiate. Point a browser, a script, or a language-learning app at it.
The endpoints
Everything hangs off https://api.taiwanmandarin.com. Responses are application/json, UTF-8, and identical for every caller — there is nothing to authenticate. Two entities — characters and words — two ways to browse them, by Kangxi radical and by TOCFL level, and a search for when you have a meaning rather than a glyph.
List characters, ordered by Taiwan frequency rank.
The whole inventory of 4,974 characters, most common first, narrowed by any combination of the filters below. Filters combine with AND.
| Name | Type | Notes |
|---|---|---|
| minRank | integer | Only characters whose taiwan_rank is greater than or equal to this. |
| maxRank | integer | Only characters whose taiwan_rank is less than or equal to this. maxRank=100 is “the hundred characters worth learning first”. |
| radical | string | Exact Kangxi radical character, e.g. 木. Percent-encode it in the URL. |
| strokes | integer | Exact total stroke count. |
| page | integerdefault 1 | 1-indexed, up to 1,000. Past that is a 400 — never a different page than the one you asked for. |
| pageSize | integerdefault 100 | Clamped to the range 1–500. |
https://api.taiwanmandarin.com/characters?maxRank=100
https://api.taiwanmandarin.com/characters?strokes=7&radical=木
{
"page": 1,
"pageSize": 100,
"total": 2,
"totalPages": 1,
"results": [ { "char": "的", … }, { "char": "一", … } ]
}A page past the end is not an error — it returns an empty results array with the real total.
Look up one character by exact match.
The character itself is the path segment, percent-encoded — /characters/%E6%B0%B4 for 水. Returns the full row plus example_words. A character we don't hold returns 404.
List words, ordered by Taiwan frequency rank.
The same envelope as /characters, over the 30,117 words in the table. Only words carrying a frequency rank are listed — the handful of TOCFL-graded words the corpus never attests are still reachable by name, and in full at /tocfl/:level.
| Name | Type | Notes |
|---|---|---|
| minRank | integer | Only words whose taiwan_rank is greater than or equal to this. |
| maxRank | integer | Only words whose taiwan_rank is less than or equal to this. |
| contains | string | One character; returns the words containing it anywhere — contains=學 gives 學生, 大學, 同學. The character-to-vocabulary direction. More than one character is a 400, not an empty page. |
| page | integerdefault 1 | 1-indexed, up to 1,000. Past that is a 400 — never a different page than the one you asked for. |
| pageSize | integerdefault 100 | Clamped to the range 1–500. |
https://api.taiwanmandarin.com/words?maxRank=50
https://api.taiwanmandarin.com/words?contains=學
Look up one word by exact match.
The word itself is the path segment, percent-encoded. Returns the word's row plus characters — the complete record for each component character, so a client can explain the word from its parts without a second round-trip. A word we don't hold returns 404.
https://api.taiwanmandarin.com/words/電腦
The Kangxi radicals in use, in Kangxi order.
The 214 semantic components characters are built from — the traditional index of a Chinese dictionary. Each entry carries the glyph, its ordinal, a one-word English gloss, and count: how many characters in the inventory use it. Radicals no character in the inventory uses are omitted.
https://api.taiwanmandarin.com/radicals
The characters built on one radical.
The radical glyph is the path segment, percent-encoded. Ordered most useful first — Taiwan frequency, then stroke count, the way a paper dictionary arranges a radical's section. The response leads with the radical's own number and meaning. A glyph outside the 214 returns 404; a radical with no characters is simply an empty results array.
| Name | Type | Notes |
|---|---|---|
| page | integerdefault 1 | 1-indexed, up to 1,000. Past that is a 400 — never a different page than the one you asked for. |
| pageSize | integerdefault 100 | Clamped to the range 1–500. |
https://api.taiwanmandarin.com/radicals/水
/characters?radical=水 answers a similar question, but as a plain frequency list with no radical metadata and without the characters the frequency corpus never attests. Use that one to combine the radical with other filters, this one to present the radical as a group.
The seven TOCFL levels, with entry counts.
Taiwan's official graded vocabulary, 華語八千詞. Each level carries its slug — pass that to the endpoint below. The slugs matter because the stored ordinal and the display name differ: ordinal 5 is “Level 3”.
https://api.taiwanmandarin.com/tocfl
The vocabulary at one TOCFL level, frequency-ordered.
Characters and words together, projected from both tables, unranked entries last. The level is addressed by slug — novice-1, novice-2, level-1 … level-5. An unknown slug returns 404 with the valid list.
| Name | Type | Notes |
|---|---|---|
| kind | stringdefault both | char for single-character entries, word for multi-character vocabulary, both for everything. |
| page | integerdefault 1 | 1-indexed, up to 1,000. Past that is a 400 — never a different page than the one you asked for. |
| pageSize | integerdefault 100 | Clamped to the range 1–500. |
https://api.taiwanmandarin.com/tocfl/novice-1?kind=word
{
"level": 1,
"levelName": "Novice 1",
"levelSlug": "novice-1",
"kind": "both",
"page": 1,
"pageSize": 100,
"total": 1268,
"totalPages": 13,
"results": [
{ "kind": "word", "word": "我們", "taiwan_rank": 63, "tocfl_level": 1, … },
{ "kind": "char", "char": "的", "taiwan_rank": 1, "tocfl_level": 1, … }
]
}Entries are ordinary character and word objects — the same fields you get from /characters and /words. Read kind to know which of char or word holds the term; no separate parser needed.
Find characters and words by meaning or pronunciation.
For when you have an English meaning or a reading rather than a glyph. Matches definitions and pinyin — with tone marks, tone digits, or neither — and returns characters and words in separate arrays, best match first: exact reading, then a definition that leads with the searched meaning, then frequency. Lexical, not semantic: a near-synonym the definition doesn't use won't be found.
| Name | Type | Notes |
|---|---|---|
| q | stringrequired | English, pinyin, or the Han text itself. Missing or empty returns 400. |
| kind | stringdefault both | char, word, or both. The unsearched array comes back empty rather than absent. |
| limit | integerdefault 20 | Results per kind. Clamped to the range 1–50. |
https://api.taiwanmandarin.com/search?q=computer
https://api.taiwanmandarin.com/search?q=shui3&kind=char
{
"query": "computer",
"kind": "both",
"characters": [],
"words": [ { "word": "電腦", … }, { "word": "記憶體", … }, { "word": "滑鼠", … } ]
}A query that matches nothing returns two empty arrays, not an error.
The root, https://api.taiwanmandarin.com/, returns a short discovery document listing these same endpoints — handy for a client that wants to introspect rather than read this page.
What a character looks like
One flat object per character. Every field except char and unicode can be null — the honest answer when a source doesn't cover that character, rather than a filled-in guess.
| Name | Type | Notes |
|---|---|---|
| char | string | The character itself. Traditional script, Taiwan standard glyph forms. |
| unicode | string | Codepoint, e.g. U+6C34. |
| taiwan_rank | integer | 1 = most frequent in Taiwan. Derived from the COCT 通用詞頻表, a Taiwan-native traditional-script corpus. |
| pinyin | string | Primary reading, tone marks included. |
| pinyin_readings | string[] | Every reading, most frequent first. Always an array, never null — a single-reading character returns one element, matching pinyin. |
| bopomofo | string | 注音 for the primary reading, Moedict format (the neutral-tone dot leads). |
| english | string | A learner-facing gloss, written by a language model from the Unihan and Moedict zh-TW definitions — the one field here that is generated rather than sourced. |
| stroke_count | integer | Total strokes, from Unihan kTotalStrokes. |
| radical | string | The Kangxi radical character, from Unihan kRSUnicode. |
| radical_meaning | string | One-word English gloss for that radical — 木 → “tree”. |
| radical_number | integer | Its Kangxi ordinal, 1–214 — the key that lines a character up with /radicals, and the traditional sort order. |
| tocfl_level | integer | 1–7 if the character is itself a TOCFL 華語八千詞 term: 1–2 Novice, 3–7 Levels 1–5. Note the offset — the ordinal is not the displayed level. |
| tocfl_level_name | string | What that ordinal is called: 5 → “Level 3”. Read this rather than rendering the ordinal. |
| tocfl_level_slug | string | The same level as a URL key — level-3. What /tocfl/:level takes. |
| pos | string | Part of speech from the TOCFL list, raw tag (N, V, Ptc…). Composite tags are slash-joined. |
| pos_label | string | The same tag in English — Vs → “stative verb (adj.)”. |
| example_words | string[]single lookup only | Up to ten common words built on the character, most frequent first. Absent from list rows. |
GET https://api.taiwanmandarin.com/characters/水{
"char": "水",
"unicode": "U+6C34",
"taiwan_rank": 181,
"pinyin": "shuǐ",
"pinyin_readings": ["shuǐ"],
"bopomofo": "ㄕㄨㄟˇ",
"english": "Water; liquid; juice; river; to swim; …",
"stroke_count": 4,
"radical": "水",
"radical_meaning": "water",
"radical_number": 85,
"tocfl_level": 1,
"tocfl_level_name": "Novice 1",
"tocfl_level_slug": "novice-1",
"pos": "N",
"pos_label": "noun",
"example_words": ["水果", "水準", "水庫", "薪水", "淡水"]
}What a word looks like
Shorter than a character — a word has no radical and no stroke count of its own, because those belong to the characters it's made of. Everything else carries the same meaning as the character field of the same name.
| Name | Type | Notes |
|---|---|---|
| word | string | The word itself, in traditional characters. |
| taiwan_rank | integer | 1 = most frequent in Taiwan, from the same COCT 通用詞頻表 counts that rank the characters. null for a TOCFL word the corpus never attests. |
| pinyin | string | TOCFL's reading where the word is graded — pinned to the graded pronunciation — Moedict's otherwise. Note the two sources space syllables differently. |
| bopomofo | string | 注音 for the same reading. |
| english | string | A learner-facing gloss, written by a language model. Roughly half the word inventory is still null here — the generation run is incremental and unfinished. |
| tocfl_level | integer | 1–7 if the word is on the 華語八千詞 list, with tocfl_level_name and tocfl_level_slug alongside it, exactly as on a character. |
| pos | string | Part of speech from the TOCFL list, raw tag, plus pos_label in English. |
| characters | object[]single lookup only | The component characters, in written order, each a full character object. Absent from list rows. |
GET https://api.taiwanmandarin.com/words/電腦{
"word": "電腦",
"taiwan_rank": 538,
"pinyin": "diànnǎo",
"bopomofo": "ㄉㄧㄢˋ ㄋㄠˇ",
"english": "computer; an electronic device that processes data …",
"tocfl_level": 1,
"tocfl_level_name": "Novice 1",
"tocfl_level_slug": "novice-1",
"pos": "N",
"pos_label": "noun",
"characters": [
{ "char": "電", "pinyin": "diàn", "english": "Lightning; electricity; …", … },
{ "char": "腦", "pinyin": "nǎo", "english": "brain; mind; head; …", … }
]
}Using it
The whole contract above is published as OpenAPI 3.1 at https://api.taiwanmandarin.com/openapi.json — point a client generator, an HTTP client or an agent at it and skip reading this page. It's generated from the same modules that enforce the limits, so it can't promise bounds the Worker doesn't apply.
There are no keys and no accounts. The data is public, read-only, and free to use — including in something you charge for. An attribution link back is appreciated, not required.
Every response carries Access-Control-Allow-Origin: *, so a browser app can call this directly with no proxy in between.
Responses are cacheable (five minutes in the browser, an hour at the edge). The underlying data only changes when the database is rebuilt, so a cached response is a current one.
A parameter that should be an integer and isn't returns 400 with {"error": …, "value": …}. A character or word we don't hold returns 404 with {"error": "not found", …}. A filter that matches nothing is not an error — it's an empty results array.