Integrate pronunciation scoring into your application with our REST API
For AI agents and coding assistants: this reference as Markdown (indexed in llms.txt), plus the OpenAPI schema and Swagger UI to try the endpoint with your key.
Authenticate your requests using an API key in the Authorization header:
Authorization: Bearer lx_your_api_key_here⚠️ Keep your API key secure: Never expose your API key in client-side code or public repositories.
🔬 Syllable-Level Precision
The V3 API delivers the deepest pronunciation analysis available: every word is broken down to individual syllables with IPA transcription, pitch class and millisecond timing, across 17 languages.
/api/pronunciation/v3/checkSyllable-level pronunciation analysis with 17-language support
✨ Operating Modes:
sentence parameter → Scores pronunciation syllable-by-syllable against expected textsentence parameter → Whisper AI transcribes your speech first, then scores pronunciation| Parameter | Type | Required | Description |
|---|---|---|---|
| speechdata | file | Yes | Audio file (WAV, MP3, M4A, OGG, FLAC, WEBM). Max 10 MiB and 60 seconds; longer recordings are rejected with status code 400. |
| sentence | string | No | Expected text to compare against. If omitted, Whisper transcribes speech first ("Just Talk" mode). |
| language_code | string | No | Language code (default: ja). One of the 17 codes listed below. |
💡 Send compressed audio
Encode client-side to MP3, Opus/WebM or AAC before uploading. A 10-second utterance is roughly 320 KB as 16 kHz mono WAV but under 40 KB as 64 kbps Opus — the same score, a fraction of the upload time. Uploads over 10 MiB are rejected with status code 413.
lingolix (PyPI) · @lingolix/sdk (npm)
you should set the API key as an environment variable:
export LINGOLIX_API_KEY=your_api_key_here
# pip install lingolix (or: uv add lingolix)
from lingolix import Lingolix, QuotaExceededError
# WARNING: Keep API keys server-side only! Never expose in client-side code.
# Lingolix() reads $LINGOLIX_API_KEY. AsyncLingolix is the async twin.
with Lingolix() as client:
try:
# Known text mode
r = client.check("audio.wav", sentence="Guten Morgen", language="de")
# Just Talk mode (sentence omitted)
# r = client.check("audio.wav", language="de")
print(r.overall, r.accuracy, r.fluency, r.speaking_rate)
for word in r.words:
for s in word.syllables:
print(s.text, s.expected_ipa, "→", s.detected_ipa, s.pitch, round(s.accuracy, 2))
except QuotaExceededError as e:
print(f"{e.plan} plan is out of minutes — upgrade at {e.upgrade_url}")Each word contains a syllables array with per-syllable IPA and timing.
{
"text": "Guten Morgen",
"overall": 0.81,
"accuracy": 0.78,
"fluency": 1,
"completeness": 1,
"speaking_rate": 4.4,
"target_speaking_rate": 3,
"model_version": "2026-10",
"words": [
{
"text": "Guten",
"syllables": [
{
"text": "Gu",
"expected_ipa": "ɡuː",
"detected_ipa": "ɡuː",
"accuracy": 1,
"completeness": 1,
"pitch": "low",
"duration_ms": 200,
"start_ms": 300,
"end_ms": 500,
"is_missing": false,
"is_extra": false
},
{
"text": "ten",
"expected_ipa": "tn̩",
"detected_ipa": "tn̩",
"accuracy": 1,
"completeness": 1,
"pitch": "high",
"duration_ms": 200,
"start_ms": 520,
"end_ms": 720,
"is_missing": false,
"is_extra": false
}
],
"accuracy": 1,
"completeness": 1,
"start_ms": 300,
"end_ms": 720,
"char_start": 0,
"char_end": 5,
"hesitation_ms": null
},
{
"text": "Morgen",
"syllables": [
{
"text": "Mor",
"expected_ipa": "mɔʁ",
"detected_ipa": "ma",
"accuracy": 0.33,
"completeness": 0.67,
"pitch": "high",
"duration_ms": 200,
"start_ms": 760,
"end_ms": 960,
"is_missing": false,
"is_extra": false
},
{
"text": "gen",
"expected_ipa": "ɡn̩",
"detected_ipa": "ɡn̩",
"accuracy": 1,
"completeness": 1,
"pitch": "flat",
"duration_ms": 220,
"start_ms": 980,
"end_ms": 1200,
"is_missing": false,
"is_extra": false
}
],
"accuracy": 0.42,
"completeness": 1,
"start_ms": 760,
"end_ms": 1200,
"char_start": 6,
"char_end": 12,
"hesitation_ms": null
}
]
}Show overall as the score; accuracy and fluency explain it, and the words and syllables show where.
| Field | Type | Description |
|---|---|---|
| text | string | The expected sentence, or the transcript in Just Talk mode |
| overall | number | The score to show (0.0 – 1.0): accuracy and fluency weighted as human raters weigh them. Always present; equals accuracy when fluency is null |
| accuracy | number | How well the sounds were pronounced (0.0 – 1.0) |
| fluency | number | null | Pace and pauses (0.0 – 1.0): full credit from target_speaking_rate on, less for each hesitation. null when the utterance is too short to time (under 4 syllables) |
| completeness | number | Fraction of the expected utterance that was said |
| speaking_rate | number | Syllables (Japanese: morae) per second, from the first syllable's start to the last's |
| target_speaking_rate | number | null | Pace from which fluency gets full credit, in the same unit: 60% of a typical native pace for the language, so learners have room |
| model_version | string | null | Which model and scoring produced these numbers, e.g. "2026-10". It changes when an update shifts scores, so you can tell results from different versions apart in stored history |
| words | Word[] | Per-word results, see Word Fields |
| Field | Type | Description |
|---|---|---|
| text | string | Word text |
| accuracy | number | How well the word's sounds were pronounced, judged by its weakest sound (0.0 – 1.0) |
| completeness | number | Fraction of the word's syllables detected |
| start_ms | number | Start offset in milliseconds |
| end_ms | number | End offset in milliseconds |
| char_start | integer | Character index of this word in the response's text field. -1 for Japanese and for extra words the user said that weren't in the expected text |
| char_end | integer | Character index (exclusive) where this word ends in the response's text field. -1 under the same conditions as char_start |
| is_scored | boolean | false when the word couldn't be scored (no reference pronunciation); its accuracy is then not a verdict |
| hesitation_ms | number | null | Pause before or inside this word, in milliseconds beyond the speaker's usual gap; null if none. Pauses at punctuation never count |
| syllables | Syllable[] | Per-syllable results, see Syllable Fields |
| Field | Type | Description |
|---|---|---|
| text | string | Syllable text (e.g. "ni") |
| expected_ipa | string | Expected IPA transcription |
| detected_ipa | string | Detected IPA from audio |
| accuracy | number | Phoneme-level accuracy (0.0 – 1.0) |
| completeness | number | Fraction of expected phonemes detected |
| pitch | "high" | "low" | "flat" | "unknown" | Pitch class: high | low | flat | unknown |
| duration_ms | number | Syllable duration in milliseconds |
| start_ms | number | Start offset in milliseconds |
| end_ms | number | End offset in milliseconds |
| is_missing | boolean | true if the user skipped this syllable |
| is_extra | boolean | true if the user said something not in target |
| is_scored | boolean | false when the syllable has no reference IPA; its accuracy is then not a verdict, which tells it apart from a real 0 |
| Language | Code |
|---|---|
| 🇯🇵 Japanese | ja |
| 🇬🇧 English | en |
| 🇩🇪 German | de |
| 🇫🇷 French | fr |
| 🇪🇸 Spanish | es |
| 🇭🇺 Hungarian | hu |
| 🇧🇬 Bulgarian | bg |
| 🇨🇿 Czech | cs |
| 🇬🇷 Greek | el |
| 🇫🇮 Finnish | fi |
| 🇮🇹 Italian | it |
| 🇰🇷 Korean | ko |
| 🇳🇱 Dutch | nl |
| 🇵🇱 Polish | pl |
| 🇵🇹 Portuguese | pt |
| 🇸🇪 Swedish | sv |
| 🇺🇦 Ukrainian | uk |
Your monthly quota is measured in audio minutes and is shared across all your API keys. Check your current usage in the Dashboard.
💡 Tip: Monitor your usage regularly to avoid unexpected charges. Upgrade your plan in the Subscription page.
| Status Code | Error | Solution |
|---|---|---|
| 401 | Invalid or missing API key | Check your Authorization header |
| 403 | No active API subscription | Subscribe to a plan on the pricing page |
| 400 | Invalid audio format | Use WAV, MP3, M4A, OGG, FLAC, or WEBM format |
| 413 | Audio file too large | Keep uploads under 10 MiB; encode to MP3/Opus instead of WAV |
| 429 | Quota exceeded | Upgrade plan or wait for next billing period |
| 503 | Service unavailable | Retry after a short delay |