mnemEcho Nederlands

API docs

Version 1. Send a short Dutch text and a recording of someone saying it; get back a score, a verdict, and a word by word, sound by sound breakdown.

Start

Base URLhttps://api.mnemecho.com/api/v1
AuthenticationAuthorization: Bearer <key> or X-API-Key: <key>
TransportHTTPS only
FormatRequests as multipart/form-data, answers as JSON

Keys are given by request during early access: hello@mnemecho.com. Keep your key on your server, never in a web page or a shipped app: anyone holding it can spend your quota. If it leaks, tell us and we rotate it; the old key stops at once.

What is graded

  • Graded: pronunciation, meaning whether each expected Dutch sound was said, and said right, and whether each word is the word in the text.
  • Not graded: rhythm, pitch and vowel length. These need a native recording of the same sentence to compare against, and your own text has none. Every response says so (graded_by, reference).
  • Never graded: voice quality. A deep, high, male, female or accented voice is not marked down for how it sounds, only for which sounds it makes.

POST /score

Score one recording against one Dutch text.

FieldTypeRules
textform fieldThe Dutch that should be said. 1 to 12 words. Punctuation is ignored.
audiofileOne recording: wav, flac, ogg or mp3. Any sample rate, mono or stereo. At most 15 seconds and 4 MB.

curl

curl -X POST https://api.mnemecho.com/api/v1/score \
  -H "Authorization: Bearer $MNEMECHO_KEY" \
  -F "text=In februari is het Valentijnsdag." \
  -F "audio=@attempt.wav"

Python

import os, requests

resp = requests.post(
    "https://api.mnemecho.com/api/v1/score",
    headers={"Authorization": f"Bearer {os.environ['MNEMECHO_KEY']}"},
    data={"text": "In februari is het Valentijnsdag."},
    files={"audio": open("attempt.wav", "rb")},
    timeout=30,
)
resp.raise_for_status()
result = resp.json()
print(result["score"], result["verdict"], result["worst_word"])

JavaScript (Node 18 and later)

import { readFile } from "node:fs/promises";

const form = new FormData();
form.append("text", "In februari is het Valentijnsdag.");
form.append("audio", new Blob([await readFile("attempt.wav")], { type: "audio/wav" }), "attempt.wav");

const resp = await fetch("https://api.mnemecho.com/api/v1/score", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.MNEMECHO_KEY}` },
  body: form,
});
if (!resp.ok) throw new Error(`${resp.status} ${(await resp.json()).detail}`);
const result = await resp.json();
console.log(result.score, result.verdict, result.worst_word);

Full runnable examples are in the GitHub repository.

The response

A real response for a native speaker saying "In februari is het Valentijnsdag". Two words shown in full; the others look like "In".

{
  "text": "In februari is het Valentijnsdag",
  "score": 91.6,
  "verdict": "pass",
  "graded_by": ["pronunciation"],
  "reference": false,
  "words": [
    {"word": "In", "ok": true, "wrong_word": false, "sounds": [
      {"target": "ɪ", "heard": "ɪ", "ok": true, "quality": 0.9997, "hint": null},
      {"target": "n", "heard": "n", "ok": true, "quality": 1.0, "hint": null}
    ]},
    {"word": "februari", "ok": false, "wrong_word": false, "sounds": [
      {"target": "f",  "heard": "f",  "ok": true,  "quality": 0.9993, "hint": null},
      {"target": "eː", "heard": "ɛ",  "ok": false, "quality": 0.6629,
       "hint": "the long ee in zee"},
      {"target": "b",  "heard": "b",  "ok": true,  "quality": 0.9999, "hint": null},
      ...
    ]},
    ...
  ],
  "ipa_target": "ɪ n f eː b r y aː r i ɪ s h ə t v aː l ə n t ɛɪ n z d ɑ x",
  "ipa_heard":  "ɪ n f ɛ b r y aː r i ɪ s h ə t v aː l ə t ɛɪ n z d ɑ x",
  "worst_word": "februari",
  "flags": ["unobservable_phones=1", "no_reference"]
}
FieldMeaning
textThe words that were scored, punctuation removed.
score0 to 100, one decimal. Set against native Dutch speakers, so 90 and up is native-like, not merely good.
verdictexcellent, pass, marginal_fail or hard_fail. See verdicts.
graded_byWhich parts made the score. Always ["pronunciation"] on this endpoint.
referenceAlways false: no native recording was compared.
wordsOne entry per word, in order.
words[].okfalse if any of its sounds counted against the score, or if it was heard as another word.
words[].wrong_wordtrue when a different word was said in its place (maart for februari). The attempt cannot pass.
words[].soundsOne entry per expected sound (phoneme) in the word.
sounds[].targetThe sound expected, in IPA. ː marks a long vowel.
sounds[].heardWhat was heard instead, on a wrong sound. Equal to target when ok.
sounds[].okWhether this sound counted against the score.
sounds[].quality0 to 1, how clearly the expected sound was heard (1 is clearly).
sounds[].hintOn a wrong sound, a short English tip such as "the long ee in zee". Otherwise null.
ipa_targetAll expected sounds, space separated.
ipa_heardWhat was heard, sound by sound. Useful for debugging, too raw to show players.
worst_wordThe word to work on first, or null.
flagsNotes on the attempt. See flags.

A word swapped

The same recording sent with the text "In maart is het Valentijnsdag." The speaker said februari, so maart is marked as a wrong word and the attempt does not pass.

{
  "score": 74.0,
  "verdict": "marginal_fail",
  "words": [
    ...
    {"word": "maart", "ok": false, "wrong_word": true, "sounds": [
      {"target": "m", "heard": "b", "ok": false, "quality": 0.0013, "hint": "the m in mens"},
      ...
    ]},
    ...
  ],
  "worst_word": "maart",
  "flags": ["unobservable_phones=1", "wrong_word=1", "no_reference"]
}

Verdicts

Use verdict for your logic, and worst_word plus the wrong sounds for feedback.

VerdictScoreMeaningSuggested action
excellent95 to 100Native-like.Full reward.
pass75 to 94.9Understood and correct, small slips.Accept; show the one wrong sound, if any.
marginal_fail55 to 74.9Recognisable, but a word or sounds were off.Ask for another try; show worst_word.
hard_failunder 55Mostly wrong, or a different sentence.Ask again; play your model audio first.

Flags

FlagMeaning
no_referenceAlways present: rhythm and pitch were not graded.
wrong_word=NN words were heard as other words. See words[].wrong_word.
unobservable_phones=NN expected sounds were not heard in their place at all. A few are forgiven; more count as missing.
missing_phones=NN sounds were judged left out, and were charged.
reduced_phones=NN sounds that Dutch speakers often drop were dropped, and not charged.
insertions=NN extra sounds were heard that the text does not have.
text_mismatchMost of the text was not heard: something else was said, or the wrong clip was sent.

New flags may be added within v1. Ignore flags you do not know.

Showing feedback

  1. Lead with the verdict in plain words.
  2. Highlight worst_word in the sentence. If its wrong_word is true, say which word was expected.
  3. Under it, show each sound where ok is false, with its hint. Skip the IPA unless your users know it.
  4. Show one or two sounds per try. People fix one thing at a time.

GET /health

Checks your key and shows your limits. Needs a key.

curl https://api.mnemecho.com/api/v1/health -H "Authorization: Bearer $MNEMECHO_KEY"
{"ok": true, "client": "your-name",
 "limits": {"per_minute": 20, "per_day": 500},
 "request": {"max_words": 12, "max_audio_s": 15.0,
             "formats": "wav, flac, ogg or mp3"}}

GET /status

Whether the service answers. No key, readable from any origin. The status page uses it.

curl https://api.mnemecho.com/api/v1/status
{"ok": true}

Errors

Every error is JSON with a detail message written for a developer, for example {"detail": "send the Dutch text as the form field 'text'"}.

StatusWhenWhat to do
400Empty audio, or audio with no samples.Check the recording step.
401Key missing or wrong.Check the Authorization header.
413Audio over 15 seconds, or upload over 4 MB.Trim silence; send one sentence per call.
415Audio not readable.Send wav, flac, ogg or mp3. Browser webm recordings must be converted first.
422Text missing, over 12 words, or nothing in the audio could be heard.For "could not be heard", ask for another try, louder. This is not a low score.
429Over your per minute or per day limit.Wait the seconds in the Retry-After header.
500Our fault.Retry once; if it repeats, tell us the time.

Limits

  • Each key has a per minute and a per day limit; GET /health shows yours. The day resets at 00:00 UTC.
  • Failed requests (400, 413, 415, 422) count toward the limits; 429 answers do not.
  • Our network also blocks bursts of more than 10 requests in 10 seconds.

Good practice

  • Send sentences, not single words. One word said alone is often not heard at all (422). Three words or more is reliable.
  • Record 16 kHz mono wav if you can choose. Other formats work.
  • Start recording before the speaker starts and stop shortly after. Silence within the 15 seconds is fine.
  • Background noise, music, or Bluetooth earbuds with noise suppression lower the score. A wired or built-in microphone in a quiet room works best.
  • Write numbers out (twaalf, not 12). Names and English loanwords may be expected with a pronunciation your users do not use.
  • Call from your server, not the user's device, so your key stays secret.

For machines

  • openapi.json: the OpenAPI 3.1 description.
  • llms.txt: a short guide for AI agents.
  • GitHub: examples, changelog, and issues for bugs and requests.