Docs · Errors
Errors
Every error is a JSON object with a stable code and a human message. Branch on the code; show the message.
Updated 2026-10-02
error shape
{
"error": "invalid_chord",
"message": "Could not parse: Xyz9"
} | Code | Status | When | What to do |
|---|---|---|---|
invalid_json | 400 | The body is not valid JSON. | Send Content-Type: application/json and a JSON object. |
invalid_input | 400 | A required field is missing or the wrong shape (no chord, progression not an array, previousVoicing without notes or midi, unrecognised key). | The message names the field. See the field tables on the API page. |
invalid_chord | 400 / 404 | The chord symbol could not be parsed. Reharmonize reports 400 with the bad symbols; analyze, resolve and voicing report 404. | Use standard symbols: Cmaj7, Dm7b5, G7#9, C/E. Flats are b, sharps #. |
unauthorized | 401 | Missing, revoked, expired or mistyped key. | Check the Authorization header. Create or rotate a key at /keys. |
forbidden | 403 | An admin-only route. | Not something an API key can reach. |
not_found | 404 | No such route. | All theory endpoints are POST /v2/{analyze,resolve,voicing,reharmonize,conduct}. |
method_not_allowed | 405 | GET on a POST endpoint. | Use POST. |
payload_too_large | 413 | Body over the size cap. | Send one chord or one progression per call. |
rate_limited | 429 | Per-minute limit (60 free, 120 Theory Pro, 300 Developer, 1,000 Licence) or the IP throttle. | Back off and retry after a few seconds. Batch client-side. |
quota_exceeded | 429 | Monthly quota spent on any tier. | Wait for the first of the month or pick a larger plan on /keys. |
server_error | 500 | Something broke on our side. | Retry once; if it persists, email the request body and the time. |
Retrying
Only 429 and 5xx are worth retrying. 4xx means the request itself needs to change.
backoff
async function thiri(path, body, tries = 3) {
for (let i = 0; i < tries; i++) {
const res = await fetch("https://chords.thiri.ai" + path, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.THIRI_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (res.status !== 429 && res.status < 500) return res.json();
await new Promise((r) => setTimeout(r, 500 * 2 ** i));
}
throw new Error("THIRI unavailable after retries");
} Inside an MCP client
The MCP server surfaces the same codes as tool errors. Claude will usually read the message and correct the chord symbol on its own; if it keeps retrying the same bad input, tell it which field to change.
Next
Changelog →
What changed in the engine, the MCP server and this site.