Skip to content
THIRI logo
build.thiri.ai
Music theory for agents

Docs · REST API

REST API reference

Five POST endpoints on one base URL. Every body is JSON, every call carries your key as a bearer token, and every response is deterministic: the same input returns the same output, byte for byte.

Updated 2026-10-02

Basics

Base URLhttps://chords.thiri.ai
AuthAuthorization: Bearer sk_live_… on every request. Get a key.
Content typeContent-Type: application/json. Bodies over 64 KB are rejected with 413.
MethodAll theory endpoints are POST. GET /v2/account reads your usage.
Chord symbolsStandard jazz notation: Cmaj7, Dm7b5, G7#9, F#m11, Bb13, slash chords like C/E. Flats are b, sharps #.
KeysA tonic such as C, Bb, F#, or a minor key like Em or E minor. Modes are expressed through their parent key: for B♭ Lydian pass F.
CORSAn allowlist of THIRI origins. A browser app on another domain is blocked by CORS, and a key in client-side code is visible to everyone anyway: put a small server-side proxy in front of the API (that is what this site's instruments do).

Quota headers

Every /v2 response reports your monthly position. A successful call counts; a 4xx does not.

response headers
HTTP/2 200
content-type: application/json
x-quota-limit: 1000
x-quota-used: 139

POST /v2/analyze

Parse a chord symbol into its parts and, with a key, its harmonic function. This is the call behind "what is this chord doing?"

FieldTypeNotes
chordstring, requiredAny chord symbol.
keystringAdds degree, numeral, function, diatonic and borrowed.
request
{"chord": "Dm7b5", "key": "C"}

POST /v2/resolve

Spell a chord into pitches, intervals, MIDI numbers and frequencies, with the scales that fit it. This is what a synth or a notation tool needs.

FieldTypeNotes
chordstring, requiredAny chord symbol. Extensions above the octave come back as MIDI numbers above the root's octave.
request
{"chord": "C13"}

POST /v2/voicing

An instrument-ready voicing. Pass the previous chord's notes and THIRI minimises motion between them and reports a voice-leading score (lower is smoother).

FieldTypeNotes
chordstring, required
stylestringrootless (default), bill_evans, shell, triad, pad, guide-tones, guide-tone-1, guide-tone-2, both-guide-tones, drop-2, drop-3 (drop2 and drop3 are accepted too). An unknown style falls back to the default without an error, so check the style echoed in the response.
keyContextstringKey for colour choices and avoid notes.
octaveintegerRegister of the lowest voice. Default 3.
previousVoicingobject{ "notes": ["G3","Bb3","D4","F4"] } or { "midi": [55,58,62,65] }. Anything else is a 400.
previousNotesstring[]Shorthand for previousVoicing.notes.
colorPreferencesstring[]Tensions to prefer, for example ["9","13"].
request
{
  "chord": "C13",
  "keyContext": "F",
  "style": "rootless",
  "previousVoicing": { "notes": ["G3", "Bb3", "D4", "F4"] }
}

POST /v2/reharmonize

Alternatives for a whole progression. Each alternative names its technique, lists exactly what changed, and explains why it works, so an agent can show its reasoning.

FieldTypeNotes
progressionstring[], requiredChord symbols in order. bars is accepted as an alias.
keystringStrongly recommended; borrowed chords and secondary dominants need it.
techniquestringRestrict to one: tritone_sub, ii_v_insertion, modal_interchange, secondary_dominant, chain_of_dominants, diminished_passing, backdoor, coltrane_changes. Omit for all eight.
request
{"progression": ["Gm7", "C7", "Fmaj7"], "key": "F"}

POST /v2/conduct

A plain-English direction becomes a four-lane band arrangement (harmony, pattern, bass, drums; ticks at 480 ppq) and a multi-track MIDI file. The engine is pure JavaScript; your side plays or renders it (the Conductor instrument uses Csound in the browser).

FieldTypeNotes
promptstring, requiredKey, feel, tempo words, instruments and length are all read from the text.
durationSecnumberOverride the interpreted length.
request
{"prompt": "4-bar swung loop in F for piano, bass and drums"}
save the MIDI
// Save the MIDI that /v2/conduct returns
import { writeFileSync } from "node:fs";
const { midiBase64 } = await res.json();
writeFileSync("loop.mid", Buffer.from(midiBase64, "base64"));

GET /v2/account

Your key's tier, limits and the last seven days of calls. The usage dashboard is a view of this response.

response
{
  "keyPrefix": "sk_live_c22ca25c",
  "owner": "you@example.com",
  "tier": "free",
  "status": "active",
  "createdAt": "2026-06-20 21:38:05",
  "lastUsedAt": "2026-10-02 14:35:50",
  "rpmLimit": 30,
  "quota": { "limit": 1000, "used": 142, "remaining": 858 },
  "last7Days": [ { "date": "2026-09-27", "calls": 362 }, { "date": "2026-10-02", "calls": 66 } ]
}

Determinism, in practice

THIRI computes from pitch-class sets and a fixed grid of voicing and reharmonization rules. There is no sampling and no model in the loop, so a request is reproducible and cacheable. If you build an evaluation around THIRI, you can snapshot responses as golden files; they will not drift between calls or between versions unless the changelog says the grid changed.

Also on the worker

POST /v2/compose and POST /v2/render (Csound rendering through a container service) and the /v2/video/* jobs exist for the instruments and the score-to-picture work. They are not yet stable for third parties; ask before building on them.

Errors are listed on the errors page. Quotas and rate limits are on keys, tiers and limits.

Next

MCP server →

The same five tools, exposed to Claude, Cursor and any MCP client.