Quickstart
POST /v1/tutor with Content-Type: application/json. An IPA request does not need the recognizer. ipa wins if you also send audio.
Audio is JSON. audio_base64 is a WAV of at most 256 KiB decoded. A larger clip calls the MCP tool create_upload, PUTs the wav, and passes audio_url. Client setup is on Install.
The path is /v1. The success field api is tutor-v1. Package 0.2.0 names the build, not the score contract. New fields may appear without a new path. A break in the score contract would be /v2.
curl
curl -sS https://ipa.raymati.com/v1/tutor \
-H 'content-type: application/json' \
-d '{"target":"λόγος","ipa":"l o g o s","scheme":"koine-greek","different_word":false,"calibrate":true}'
Python
The standard library is enough.
import json
from urllib.request import Request, urlopen
body = json.loads(r"""{
"target": "λόγος",
"ipa": "l o g o s",
"scheme": "koine-greek",
"different_word": false,
"calibrate": true
}""")
request = Request(
"https://ipa.raymati.com/v1/tutor",
data=json.dumps(body).encode(),
headers={"content-type": "application/json"},
)
with urlopen(request) as response:
score = json.load(response)
print(score["proximity"])
JavaScript
const body = {
"target": "λόγος",
"ipa": "l o g o s",
"scheme": "koine-greek",
"different_word": false,
"calibrate": true
};
const response = await fetch("https://ipa.raymati.com/v1/tutor", {
method: "POST",
headers: {"content-type": "application/json"},
body: JSON.stringify(body),
});
const score = await response.json();
console.log(score.proximity);
Rust
No extra crate. The snippet shells out to curl.
fn main() {
let body = r#"{"target":"λόγος","ipa":"l o g o s","scheme":"koine-greek","different_word":false,"calibrate":true}"#;
let output = std::process::Command::new("curl")
.args([
"-sS",
"https://ipa.raymati.com/v1/tutor",
"-H",
"content-type: application/json",
"-d",
body,
])
.output()
.expect("curl");
println!("{}", String::from_utf8(output.stdout).expect("utf-8"));
}
What you get back
λόγος / l o g o s (example exactLogos):
proximity1.0real_edits0phones[].statuscorrect,correct,correct,correct,correctcoaching.sounds[].statusexact,exact,exact,exact,exactstress.penalty0.0heard_as.candidates[0]λόγος (lexicon), confidence 0.715876
The full object is the exactLogos response example in openapi.json. Other public calls:
| Example | IPA | proximity | real_edits | first learner status |
|---|---|---|---|---|
exactLogos | l o g o s | 1.0 | 0 | exact |
stressShift | l o g ˈo s | 0.9 | 0 | exact |
thetaAsS | s e o s | 0.75 | 1 | close |
acceptableTheta | tʰ e o s | 0.988187 | 0 | acceptable_variant |
verseArche | e n a r x i | 1.0 | 0 | exact |
Local replacement for the host: http://127.0.0.1:43117. Set IPA_TUTOR_BIND to move it.
MCP
POST /mcp speaks Streamable HTTP as one JSON object, not an SSE stream.
scoretakes the same IPA fields and returnstutor-v1instructuredContent.score_attemptis the same tool.heard_astakestargetandipaand returns only theheard_asobject. The ranking does not changeproximity.recognizetakesaudio_base64of a WAV. It returns phones. It does not score. No sample wav is published.
curl -sS https://ipa.raymati.com/mcp \
-H 'content-type: application/json' \
-H 'MCP-Protocol-Version: 2025-03-26' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"score","arguments":{"target":"λόγος","ipa":"l o g o s","scheme":"koine-greek","different_word":false,"calibrate":true}}}'
A missing MCP-Protocol-Version header is treated as 2025-03-26. 2026-07-28 is also accepted. GET /mcp is 405.
HTML guides honor Accept: text/markdown (draft-consolidated-content-01). curl -H 'Accept: text/markdown' https://ipa.raymati.com/docs/quickstart returns this page. The .md URL does the same without negotiation.