# Lingolix Pronunciation API (v3)

Scores how well someone pronounced a sentence, word by word and syllable by syllable, with IPA, pitch and timing. 17 languages.

- Endpoint: `POST https://api.lingolix.com/api/pronunciation/v3/check`
- OpenAPI schema: https://api.lingolix.com/openapi.json (Swagger UI: https://api.lingolix.com/docs)
- API keys: https://lingolix.com/api-portal/dashboard · Plans: https://lingolix.com/api-portal/pricing

## Authentication

Send your API key as a bearer token:

```
Authorization: Bearer lx_your_api_key_here
```

Keep the key server-side. Never ship it in browser or mobile app code. The SDKs read it from `$LINGOLIX_API_KEY`.

## Request

`multipart/form-data` with these fields:

| Field | Required | Description |
|---|---|---|
| `speechdata` | yes | Audio file: WAV, MP3, M4A, OGG, FLAC or WEBM. At most 10 MiB and 60 seconds. |
| `sentence` | no | The text the speaker was supposed to say. Omit it to have the audio transcribed first and scored against that transcript ("just talk" mode). |
| `language_code` | no | One of the codes below. Default: `ja`. |

Languages: `bg` Bulgarian, `cs` Czech, `de` German, `el` Greek, `en` English, `es` Spanish, `fi` Finnish, `fr` French, `hu` Hungarian, `it` Italian, `ja` Japanese, `ko` Korean, `nl` Dutch, `pl` Polish, `pt` Portuguese, `sv` Swedish, `uk` Ukrainian.

Numbers, dates and currency amounts in `sentence` are expanded to words automatically for `en`, `de`, `fr` and `es`; other languages score them as written.

Send compressed audio (MP3, Opus/WebM, AAC). A 10-second utterance is about 320 KB as 16 kHz mono WAV and under 40 KB as 64 kbps Opus, with the same score.

## Examples

```bash
# Known text
curl -X POST "https://api.lingolix.com/api/pronunciation/v3/check" \
  -H "Authorization: Bearer $LINGOLIX_API_KEY" \
  -F "speechdata=@audio.wav" \
  -F "sentence=Guten Morgen" \
  -F "language_code=de"

# Just talk (no sentence)
curl -X POST "https://api.lingolix.com/api/pronunciation/v3/check" \
  -H "Authorization: Bearer $LINGOLIX_API_KEY" \
  -F "speechdata=@audio.wav" \
  -F "language_code=de"
```

Python (`pip install lingolix`):

```python
from lingolix import Lingolix, QuotaExceededError

with Lingolix() as client:  # reads $LINGOLIX_API_KEY; AsyncLingolix is the async twin
    try:
        r = client.check("audio.wav", sentence="Guten Morgen", 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}")
```

TypeScript (`npm install @lingolix/sdk`):

```ts
import { readFile } from "node:fs/promises";
import { Lingolix, QuotaExceededError } from "@lingolix/sdk";

const client = new Lingolix(); // reads $LINGOLIX_API_KEY
const audio = await readFile("audio.wav"); // Blob, Uint8Array or ArrayBuffer

try {
  const r = await client.check({ audio, sentence: "Guten Morgen", language: "de" });
  console.log(r.overall, r.accuracy, r.fluency, r.speaking_rate);
} catch (e) {
  if (e instanceof QuotaExceededError) console.log(`${e.plan} plan is out of minutes`);
}
```

## Response

```json
{
  "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": "Morgen",
      "accuracy": 0.42,
      "completeness": 1,
      "start_ms": 760,
      "end_ms": 1200,
      "char_start": 6,
      "char_end": 12,
      "is_scored": true,
      "hesitation_ms": null,
      "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,
          "is_scored": true
        }
      ]
    }
  ]
}
```

Show `overall` as the score; `accuracy` and `fluency` explain it, and the words and syllables show where the problems are. All scores are numbers from 0.0 to 1.0. Fields typed `| null` are always present and may be `null`.

### Top level

| Field | Type | Description |
|---|---|---|
| `text` | string | The expected sentence, or the transcript in just-talk mode. |
| `overall` | number | The score to show: 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. |
| `fluency` | number \| null | Pace and pauses: 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, same unit: 60% of a typical native pace. |
| `model_version` | string \| null | Which model and scoring produced the numbers, e.g. `"2026-10"`. Changes when an update shifts scores; store it with results. |
| `words` | Word[] | Per-word results, below. |

### Word

| Field | Type | Description |
|---|---|---|
| `text` | string | Word text. |
| `accuracy` | number | Judged by the word's weakest sound. |
| `completeness` | number | Fraction of the word's syllables detected. |
| `start_ms`, `end_ms` | number | Offsets in the audio, milliseconds. |
| `char_start`, `char_end` | integer | Character range (end exclusive) of the word in `text`. `-1` for Japanese and for extra words not in the expected text. |
| `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 the word beyond the speaker's usual gap, ms; `null` if none. Pauses at punctuation never count. |
| `syllables` | Syllable[] | Per-syllable results, below. |

### Syllable

| Field | Type | Description |
|---|---|---|
| `text` | string | Syllable text, e.g. `"Mor"`. |
| `expected_ipa` | string | Expected IPA. |
| `detected_ipa` | string | IPA heard in the audio. |
| `accuracy` | number | Phoneme-level accuracy. |
| `completeness` | number | Fraction of expected phonemes detected. |
| `pitch` | `"high"` \| `"low"` \| `"flat"` \| `"unknown"` | `high`, `low`, `flat` or `unknown`. |
| `duration_ms`, `start_ms`, `end_ms` | number | Timing in milliseconds. |
| `is_missing` | boolean | `true` if the speaker skipped this syllable. |
| `is_extra` | boolean | `true` if the speaker said something not in the expected text. |
| `is_scored` | boolean | `false` when the syllable has no reference IPA; `accuracy` is then not a verdict (distinguish it from a real 0). |

## Errors

Errors are JSON: `{"error": {"code": "HTTP_<status>", "message": ..., "path": ..., "method": ...}}`, except 422, which uses FastAPI's `{"detail": [...]}`.

| Status | Meaning | What to do |
|---|---|---|
| 400 | Invalid audio, or longer than 60 seconds | Send WAV, MP3, M4A, OGG, FLAC or WEBM, at most 60 s. |
| 401 | Missing, malformed or invalid API key | Send `Authorization: Bearer lx_...`. |
| 403 | No active API subscription | Subscribe at https://lingolix.com/api-portal/pricing. |
| 413 | Upload over 10 MiB | Encode to MP3/Opus instead of WAV. |
| 422 | Missing `speechdata` or malformed form fields | Fix the request. |
| 429 | Monthly quota used up | `error.message` is an object with `message`, `plan`, `remaining_minutes` and `upgrade_url`; headers `X-RateLimit-Limit` (monthly minutes) and `X-RateLimit-Remaining: 0`. Upgrade or wait for the next billing period. Don't retry in a loop. |
| 5xx | Temporary failure | Retry after a short delay with backoff. |

## Quotas

Usage is metered in audio minutes per month, shared across all keys of a subscription. The free tier has 15 minutes and is a hard limit (429 when used up). Paid plans have 180 to 13,200 minutes and bill overage per minute (€0.02 to €0.05, depending on plan). Usage is shown at https://lingolix.com/api-portal/dashboard.
