1. Get an API key

Create an account, open API keys in the dashboard and create a key. It starts with tp_live_ and is shown once. Your account starts with 50 free credits, so you can run this whole quickstart without a card; after that, the Starter plan is $10 for 2,500 credits. One credit covers a recording up to 15 seconds. Failed and unassessable requests are free. Then export the key in your shell:

Shell
export TONEPERFECT_API_KEY="tp_live_YOUR_SECRET_KEY"

Keep the key on a server. Browser and mobile apps should call your own backend, which calls the API.

2. Make a test recording

No microphone handy? Download a hosted sample: a synthetic voice saying 你好, made with the open-source eSpeak NG synthesizer (more samples, with expected outcomes, in the evaluation kit). Then use hello.ogg in the commands below.

Shell
curl -o hello.ogg https://api.toneperfect.app/capsule/speech-zh-nihao.ogg

Or record yourself saying 你好 (nǐ hǎo) for a second or two. A phone voice memo works: the API accepts WAV, MP3, M4A, Ogg and WebM up to 30 seconds and 10 MB, so you can send the file as it is and change hello.wav in the commands below to its name. On a Mac you can also synthesise one:

Shell · macOS
say -v Tingting -o hello.aiff "你好"
afconvert -f WAVE -d LEI16@16000 hello.aiff hello.wav

Or convert any recording with ffmpeg: ffmpeg -i memo.m4a -ac 1 -ar 16000 hello.wav. The server converts to 16 kHz mono itself, so this step is optional.

3. Send it

Pick one. Each is a complete, copy-paste program.

cURL

cURL
curl https://api.toneperfect.app/v1/assess \
  -H "Authorization: Bearer $TONEPERFECT_API_KEY" \
  -H "Idempotency-Key: quickstart-hello-0001" \
  -F "audio=@hello.wav" \
  -F "text=你好" \
  -F "language=zh-CN"

Change the Idempotency-Key for every new recording; repeating a request with the same key and the same audio returns the stored result without charging again.

Python

Python 3 · requests
import os
import uuid

import requests  # pip install requests

with open("hello.wav", "rb") as audio:
    response = requests.post(
        "https://api.toneperfect.app/v1/assess",
        headers={
            "Authorization": f"Bearer {os.environ['TONEPERFECT_API_KEY']}",
            # One key per recording attempt; reuse it only to retry the same attempt.
            "Idempotency-Key": f"quickstart-{uuid.uuid4()}",
        },
        files={"audio": ("hello.wav", audio, "audio/wav")},
        data={"text": "你好", "language": "zh-CN"},
        timeout=90,
    )
result = response.json()
if not response.ok:
    raise SystemExit(f"{response.status_code} {result['error']['code']}: {result['error']['message']}")
print(result["scores"]["overall"])
for word in result["words"]:
    print(word["word"], word["pinyin"], word["score"], [(p["dimension"], p["expected"], p["heard"], p["decision"]) for p in word["phones"]])

Node.js

Node.js 18+ · no dependencies
// Node 18+ (global fetch, FormData and Blob). Save as assess.mjs, run: node assess.mjs
import { readFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';

const form = new FormData();
form.append('audio', new Blob([await readFile('hello.wav')], { type: 'audio/wav' }), 'hello.wav');
form.append('text', '你好');
form.append('language', 'zh-CN');

const response = await fetch('https://api.toneperfect.app/v1/assess', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.TONEPERFECT_API_KEY}`,
    // One key per recording attempt; reuse it only to retry the same attempt.
    'Idempotency-Key': `quickstart-${randomUUID()}`,
  },
  body: form,
});
const result = await response.json();
if (!response.ok) throw new Error(`${response.status} ${result.error.code}: ${result.error.message}`);
console.log(result.scores.overall);
for (const word of result.words) {
  console.log(word.word, word.pinyin, word.score, word.phones.map(p => [p.dimension, p.expected, p.heard, p.decision]));
}

Prefer a client library? The Python and Node.js SDKs are single files that wrap the same call and turn errors into typed exceptions.

4. Read the response

This is a real response for 你好, trimmed to the documented fields; the full body also carries engine diagnostics. Your numbers will differ.

JSON · 200 OK
{
  "id": "req_example",
  "language": "zh",
  "locale": "zh-CN",
  "model_version": "mandarin-human-v2-85741cdf3967-fbf73d85ee88",
  "task": "general",
  "status": "completed",
  "assessable": true,
  "reason": null,
  "scores": {
    "overall": 99.6,
    "pronunciation": 97.8,
    "initial": 98.2,
    "final": 97.3,
    "tone": 98,
    "fluency": null,
    "rhythm": null,
    "integrity": null
  },
  "words": [
    {
      "word": "你",
      "pinyin": "ni3",
      "score": 99.3,
      "start_ms": 0,
      "end_ms": 155,
      "phones": [
        {
          "dimension": "initial",
          "expected": "n",
          "heard": "n",
          "score": 96.6,
          "decision": "no_correction",
          "flagged": false
        },
        {
          "dimension": "final",
          "expected": "i",
          "heard": "i",
          "score": 96.1,
          "decision": "no_correction",
          "flagged": false
        },
        {
          "dimension": "tone",
          "expected": 3,
          "heard": 2,
          "score": 99.7,
          "decision": "no_correction",
          "flagged": false
        }
      ]
    },
    {
      "word": "好",
      "pinyin": "hao3",
      "score": 99.5,
      "start_ms": 155,
      "end_ms": 424,
      "phones": [
        {
          "dimension": "initial",
          "expected": "h",
          "heard": "h",
          "score": 99.8,
          "decision": "no_correction",
          "flagged": false
        },
        {
          "dimension": "final",
          "expected": "ao",
          "heard": "ao",
          "score": 98.5,
          "decision": "no_correction",
          "flagged": false
        },
        {
          "dimension": "tone",
          "expected": 3,
          "heard": 3,
          "score": 96.3,
          "decision": "no_correction",
          "flagged": false
        }
      ]
    }
  ],
  "syllables": [
    {
      "index": 0,
      "expected": {
        "pinyin": "ni3",
        "initial": "n",
        "final": "i",
        "tone": 3
      },
      "span_ms": [
        0,
        155
      ],
      "score": 99.3,
      "components": {
        "initial": {
          "expected": "n",
          "heard": "n",
          "agrees_with_prompt": true,
          "score": 96.6,
          "confidence": "high",
          "decision": "no_correction",
          "advice": null
        },
        "final": {
          "expected": "i",
          "heard": "i",
          "agrees_with_prompt": true,
          "score": 96.1,
          "confidence": "high",
          "decision": "no_correction",
          "advice": null
        },
        "tone": {
          "expected": 3,
          "heard": 2,
          "agrees_with_prompt": false,
          "score": 99.7,
          "confidence": "high",
          "decision": "no_correction",
          "advice": null
        }
      }
    },
    {
      "index": 1,
      "expected": {
        "pinyin": "hao3",
        "initial": "h",
        "final": "ao",
        "tone": 3
      },
      "span_ms": [
        155,
        424
      ],
      "score": 99.5,
      "components": {
        "initial": {
          "expected": "h",
          "heard": "h",
          "agrees_with_prompt": true,
          "score": 99.8,
          "confidence": "high",
          "decision": "no_correction",
          "advice": null
        },
        "final": {
          "expected": "ao",
          "heard": "ao",
          "agrees_with_prompt": true,
          "score": 98.5,
          "confidence": "high",
          "decision": "no_correction",
          "advice": null
        },
        "tone": {
          "expected": 3,
          "heard": 3,
          "agrees_with_prompt": true,
          "score": 96.3,
          "confidence": "high",
          "decision": "no_correction",
          "advice": null
        }
      }
    }
  ],
  "heard": {
    "syllables": [
      {
        "initial": "n",
        "final": "i",
        "tone": 2
      },
      {
        "initial": "h",
        "final": "ao",
        "tone": 3
      }
    ]
  },
  "duration_seconds": 0.54,
  "billing": {
    "credits": 1,
    "balance_credits": 2499
  }
}
  • scores.overall is the sentence grade (0–100). For Mandarin, initial, final and tone are averaged across syllables; fluency and rhythm are null because Mandarin does not score them. null means “not measured”, never zero.
  • words[] has one entry per character with its pinyin, a score and timestamps. Its phones[] rows give the expected and heard initial, final and tone, a score and a decision.
  • Look at 你: the expected tone is 3 and the heard tone is 2. Before another 3rd tone, a 3rd tone is said as a 2nd (tone sandhi), so the decision is no_correction. Act on decision, not on every expected/heard mismatch.
  • billing.credits is what this request cost and billing.balance_credits what is left.

5. Next steps

  • Other languages use the same call: change text and language, for example -F "text=I like sunny days" -F "language=en-US". See all 10 language packs.
  • For Mandarin sentence practice send task=connected_speech; that is the context the component correction decisions are designed for. See Mandarin tone scoring.
  • Handle errors by error.code and retry safely with the same Idempotency-Key: the error table and retry rules list every case.
  • Generate a client from the OpenAPI description, or check live languages with curl https://api.toneperfect.app/v1/capabilities (no key needed).