The OmniDAW API is JSON-RPC 2.0 over a local TCP socket, one JSON object per line. Anything that can open a socket and parse JSON can drive Omni. This page is the complete specification; the method reference lists what you can call.
Transport
- Address: TCP on
127.0.0.1only. Connections from other addresses are dropped. - Framing: one JSON-RPC 2.0 object per line, UTF-8, each line ending in
\n. Replies come back in order, one per request. - Limits: a line longer than 8 MiB closes the connection. Up to 8 connections are served at once.
- Notifications: a request without an
idis still executed, but gets no reply.
Finding Omni
While the API is on, Omni writes api/endpoint.json in its settings folder and deletes it on exit:
{
"protocol": "omnidaw-api",
"api_version": "0.21.0",
"host": "127.0.0.1",
"port": 50123,
"token": "9f2c...64 hex characters...",
"pid": 4242
}
| System | Settings folder |
|---|---|
| Windows | %APPDATA%\omni |
| macOS | ~/Library/Application Support/omni |
| Linux | $XDG_CONFIG_HOME/omni, else ~/.config/omni |
The port is random unless you set the OMNI_API_PORT environment variable before starting Omni. A client can skip the file by setting OMNI_API_ENDPOINT=host:port together with OMNI_API_TOKEN, for example in tests. The token is new on every run and the file is readable only by the user who started Omni.
Handshake
The first call on every connection must be api.hello with the token. Until it succeeds, everything except api.ping is answered with unauthorized.
{"jsonrpc":"2.0","id":1,"method":"api.hello","params":{"token":"9f2c...","api_version":"0.21.0","client":{"name":"my-tool","version":"1.0"}}}
{"jsonrpc":"2.0","id":1,"result":{"protocol":"omnidaw-api","api_version":"0.21.0","app":{"name":"Omni","version":"0.1.0"},"method_count":102,"capabilities":["jobs","analysis","render","plugins","audio"]}}
api_version in the request is the version your client was built against. If it is not compatible, you get incompatible (see Versioning). client is free text shown in Omni's log.
Capabilities
capabilities tells a client what this host offers beyond the method list, so it can adapt without trial and error:
| Capability | Meaning |
|---|---|
jobs | Long work runs as jobs (jobs.get, jobs.wait) |
analysis | Capture and analyse the master or a track |
render | Offline export and stems, faster than real time |
plugins | External VST3 and CLAP instruments and effects |
audio | Audio clips: sampler, slicing, transcription, stem splitting |
files | Present only when the user allowed clients to read audio files (see Security) |
Methods outside the catalog
Three methods belong to the connection itself and do not touch the project:
| Method | Does |
|---|---|
api.hello | The handshake above |
api.methods | The complete catalog document |
api.ping | Answers "pong"; allowed before the handshake. Use it to check an idle connection |
The catalog document
api.methods returns everything a client needs to present the API without hard-coding it:
{
"protocol": "omnidaw-api",
"api_version": "0.21.0",
"units": { "time": "beats (quarter notes)", "level": "dB / dBFS, null = silence", "...": "..." },
"errors": [ { "code": -32602, "name": "invalid_params" }, "..." ],
"methods": [ { "name": "tracks.update", "...": "..." } ]
}
Each entry of methods has:
| Field | Meaning |
|---|---|
name | domain.verb, snake_case, for example clips.write_midi |
domain | The part before the dot |
title | A short human title |
summary | A description written for a language model: what it does, what to pass, what to expect |
effect | read, control, edit, destructive or history (see below) |
idempotent | Whether repeating the call with the same parameters changes nothing more |
since | The API version that introduced it |
params | A JSON Schema of the parameters (closed objects, ranges, enums, nested items) |
example | A parameters object that is accepted as is |
An MCP server turns each entry into a tool; Building an MCP server shows how.
Requests, results and errors
A request:
{"jsonrpc":"2.0","id":7,"method":"tracks.update","params":{"track":0,"volume_db":-6}}
A successful reply carries result, a failed one carries error:
{"jsonrpc":"2.0","id":7,"error":{"code":-32003,"message":"slot 0 holds no audio (audio.import a file first)"}}
error.data is optional. Codes are the JSON-RPC ones plus Omni's:
| Code | Name | When |
|---|---|---|
| -32700 | parse_error | The line is not JSON |
| -32600 | invalid_request | Not a JSON-RPC 2.0 request |
| -32601 | method_not_found | Unknown method |
| -32602 | invalid_params | The parameters do not match the schema or a range; nothing was changed |
| -32603 | internal | A bug in Omni |
| -32001 | unauthorized | No successful api.hello on this connection |
| -32002 | not_found | A track, slot, clip or device does not exist |
| -32003 | invalid_state | It exists but cannot do this (notes on an audio track, no audio loaded) |
| -32004 | busy | Omni did not answer in time, or an export or capture is already running |
| -32005 | incompatible | The API versions do not match |
| -32006 | unavailable | The audio engine is not running |
Error messages are written to be read by a model: they say what was wrong and, where it helps, which method to use instead. Pass them on unchanged.
Conventions
| Thing | Rule |
|---|---|
| Method names | domain.verb, snake_case. The MCP tool name replaces the dot with an underscore |
| Time | Always beats (quarter notes). A 4/4 bar is 4 beats. Seconds appear only where noted (capture length, positions in an audio file) |
| Levels | dB or dBFS. Silence or "not measured" is null, never minus infinity |
| Loudness | LUFS (EBU R128), true peak in dBTP, loudness range in LU |
| Pan | -1 (left) to +1 (right) |
| Pitch | MIDI note number, 60 is middle C |
| Parameters | Closed objects. An unknown or misspelled field is invalid_params, never ignored. Everything is checked before anything changes |
| Results | Open. Clients must ignore fields they do not know; new versions add fields |
References
- Track: the
id(a UUID string fromtracks.list, stable) or the 0-basedindex(it shifts when tracks are added or moved). Prefer ids. - Slot: the 0-based clip slot on a track, which is the scene row of the session grid.
- Effect chain:
{"track": <id or index>}for a track's inserts,{"return_bus": <index>}, or the string"master". A slot in the chain is the 0-based position in signal order. - Parameter key:
instrument:<id>for a parameter of a track's instrument andfx:<slot>:<index>for an insert's, as listed byparams.list.automation.writetakes the same keys, plusvolumeandpan.
Effects and undo
Every method declares an effect. A client decides permissions, confirmations and MCP annotations from it alone.
| Effect | Meaning | Undo |
|---|---|---|
read | Reads state, changes nothing | none needed |
control | Changes live state that is not project data: transport, clip launch, captures, exports | not recorded |
edit | Changes the project | one step in Omni's Undo History, labelled API: <title> |
destructive | Removes content | one step |
history | edit.undo and edit.redo themselves | none |
Because every edit is an ordinary history step, a person can take back what an assistant did with Ctrl+Z, and the assistant can do the same with edit.undo. A call that edits several things at once, for example arrangement.build_song, is still one step.
Jobs
Work that takes seconds (a capture, an export, a transcription, a stem split) does not block the connection. The call returns at once:
{"jsonrpc":"2.0","id":9,"result":{"job":3}}
Then ask for the outcome:
{"jsonrpc":"2.0","id":10,"method":"jobs.wait","params":{"job":3,"timeout_seconds":9}}
jobs.wait returns when the job finishes or after at most 9 seconds, whichever is first; call it again while state is running. jobs.get returns the current state immediately. Both are answered without involving Omni's UI thread, so waiting never slows the application. A job has:
| Field | Meaning |
|---|---|
job | The job id |
kind | The method that started it, for example analysis.capture |
state | running, done or failed |
progress | 0 to 1 |
result | The outcome once done: for a capture, the analysis; for an export, the file path and an analysis of the written file |
error | The reason once failed, otherwise null |
Only one capture and one export can run at a time; a second one is answered with busy.
Timeouts and retries
- A call waits at most 10 seconds for Omni's UI thread. If Omni is busy (for example a modal dialog is open), the answer is
busy; try again once it is free. - If a connection drops mid-call you cannot know whether an
editran. Do not resend it blindly: read the state first (tracks.list,clips.list,arrangement.get) and decide. Reads and idempotent edits are safe to repeat. - Reconnect when Omni restarts: read
endpoint.jsonagain, because the port and token change. - Send
api.pingbefore reusing a connection that has been idle.
Versioning
The version is major.minor.patch. The rule is simple:
- A client built against version A works with a host of version B when the major numbers are equal and B is not older than A.
- Minor versions only add methods and fields. Nothing is removed or changed in a way that breaks an older client within a major version.
- Therefore: ignore unknown result fields, and build your tool list from
api.methodsat start-up rather than hard-coding it, and you get every new method for free.
since on each catalog entry tells you in which version a method appeared.
Trying it by hand
You need nothing but a TCP tool. In PowerShell, read the token:
$ep = Get-Content "$env:APPDATA\omni\api\endpoint.json" | ConvertFrom-Json
$ep.port; $ep.token
Connect with any line-based TCP client (ncat 127.0.0.1 <port>, or telnet with local echo), paste the api.hello line with your token, then:
{"jsonrpc":"2.0","id":2,"method":"theory.scale","params":{"root":"A","mode":"minor"}}
