October 10, 2026 · 6 min read
Your first pronunciation score in five minutes
The official Lingolix SDKs for Python and TypeScript wrap one endpoint with typed results and typed errors. This guide takes you from install to a feature your learners can use.

1. Get a key
Create an API key in the API portal and put it in your environment. Both SDKs read it from there.
export LINGOLIX_API_KEY=your-keyKeep the key on your server. Anyone who has it can spend your quota, so it never belongs in a mobile app or a web page. Section 4 shows the usual setup.
2. Install
pip install lingolixnpm install @lingolix/sdkThe TypeScript client has no runtime dependencies: it uses the platform's fetch, FormData and Blob.
3. Score a recording
Send the audio and the sentence the learner was asked to say. Any format ffmpeg reads works: WAV, MP3, M4A, Ogg/Opus, FLAC, WebM, up to 10 MiB.
from lingolix import Lingolix
with Lingolix() as client:
r = client.check("hello.m4a", sentence="Guten Morgen", language="de")
print(f"{r.overall:.0%}")
for word in r.words:
for s in word.syllables:
print(s.text, s.expected_ipa, "→", s.detected_ipa, f"{s.accuracy:.0%}")import { readFile } from "node:fs/promises";
import { Lingolix } from "@lingolix/sdk";
const client = new Lingolix();
const r = await client.check({
audio: await readFile("hello.m4a"),
sentence: "Guten Morgen",
language: "de",
});
console.log(Math.round(r.overall * 100) + "%");Python has an async twin, AsyncLingolix, with the same methods. Leave out sentence and the audio is transcribed first and scored against what was said, which suits free speaking practice.
What comes back
overall | The score to show, 0–1. Accuracy and fluency combined. |
accuracy | How well the sounds were pronounced, 0–1. |
fluency | Pace and pauses, 0–1. null for very short utterances; then overall equals accuracy. |
completeness | How much of the sentence was actually said, 0–1. |
speaking_rate | Syllables per second (Japanese: morae), next to target_speaking_rate. |
words[] | Per word: accuracy, timing, hesitation_ms, and its syllables. |
syllables[] | Per syllable: expected_ipa vs detected_ipa, accuracy, pitch, timing, is_missing, is_extra. |
{
"text": "I would like a cup of coffee, please.",
"overall": 0.86, "accuracy": 0.84, "fluency": 1.0, "completeness": 1.0,
"speaking_rate": 4.1, "target_speaking_rate": 2.77,
"model_version": "2026-10",
"words": [
{ "text": "coffee,", "accuracy": 0.62, "hesitation_ms": null,
"syllables": [
{ "text": "cof", "expected_ipa": "kɔ", "detected_ipa": "kɑ", "accuracy": 0.48, "pitch": "flat" },
{ "text": "fee,", "expected_ipa": "fi", "detected_ipa": "fi", "accuracy": 1.0, "pitch": "low" }
] }
]
}Turning it into feedback
A pattern that works well with learners:
- Show
overallas the headline score. It is the number that best matches how a teacher would judge the whole sentence. - Colour each word by its
accuracy, and let learners tap a word to see its syllables. - For a weak syllable, show
expected_ipanext todetected_ipa: “you said kɑ, aim for kɔ” is more useful than a percentage. - Mark a word with
hesitation_msas a pause, and comparespeaking_ratewithtarget_speaking_ratefor a gentle “try a little faster”.
4. Record in the browser, score on your server
The usual setup: the browser records with MediaRecorder, sends the clip to your backend, and your backend calls Lingolix with the key. A Next.js route handler is all it takes:
import { Lingolix } from "@lingolix/sdk";
const client = new Lingolix(); // reads LINGOLIX_API_KEY on the server
export async function POST(req: Request) {
const form = await req.formData();
const r = await client.check({
audio: form.get("audio") as Blob,
sentence: String(form.get("sentence")),
language: "en",
});
return Response.json(r);
}const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const recorder = new MediaRecorder(stream);
const chunks: Blob[] = [];
recorder.ondataavailable = (e) => chunks.push(e.data);
recorder.onstop = async () => {
const form = new FormData();
form.append("audio", new Blob(chunks, { type: recorder.mimeType }));
form.append("sentence", "I would like a cup of coffee, please.");
const result = await (await fetch("/api/score", { method: "POST", body: form })).json();
// show result.overall, colour result.words ...
};
recorder.start();Compressed audio keeps uploads small: a sentence of WebM/Opus from MediaRecorder is a few dozen kilobytes.
5. When things go wrong
Every failure is a LingolixError with status and detail, and each cause has its own subclass, so you can handle the ones that matter to your users:
from lingolix import Lingolix, InvalidAudioError, QuotaExceededError
try:
r = Lingolix().check("clip.webm", sentence="Bonjour", language="fr")
except InvalidAudioError:
... # ask the learner to record again
except QuotaExceededError as e:
... # e.plan, e.remaining_minutes, e.upgrade_urlThe others are AuthenticationError (401), SubscriptionError (403) and ServiceUnavailableError (503, worth one retry).
6. Languages
Pass the ISO code as language: bg cs de el en es fi fr hu it ja ko nl pl pt sv uk. Japanese results come back per mora in hiragana, with pitch per mora.
Next steps
- API documentation with every field and error.
- Source, issues and changelog on GitHub.
- Curious how good the scores are? Read our open benchmark.