OMNi

OmniDAW API · 8 min

Client Libraries

The Python client, and minimal working clients in plain Python, Node.js and Rust. Everything needed to drive Omni from your own code.

You do not need a library to use the OmniDAW API: it is JSON lines over a TCP socket. This page gives you a ready client for Python, and three small ones in plain Python, Node.js and Rust that you can copy and adapt. Each one is complete: run it with Omni open and it prints your session.

Download everything: omnidaw-api-examples.zip, or the single files linked below.

The Python client

omnidaw.py is a single file with no dependencies beyond the Python standard library. Download it and put it next to your script. It reads the endpoint file, says hello, loads the catalog and turns every method of the running Omni into a Python call:

python
from omnidaw import Omni

with Omni() as omni:
    print(omni.capabilities)       # ['jobs', 'analysis', 'render', 'plugins', 'audio']
    track = omni.tracks.create(kind="midi", name="Keys", instrument="builtin://piano-synth")
    omni.clips.write_progression(track=track["id"], slot=0, progression={
        "key": {"root": "F", "mode": "minor"},
        "chords": "i VI III VII",
        "perform": {"pattern": "pad"},
    })
    job = omni.analysis.capture(source="master", seconds=4)["job"]
    print(omni.wait(job)["loudness"])

The attribute path is the method name: omni.clips.write_progression(...) calls clips.write_progression. Parameters are keyword arguments and map one to one onto the reference. Because the methods come from the running Omni's catalog, a newer Omni brings new methods to your script without updating the client.

MemberDoes
Omni(client_name="omnidaw.py", timeout=600.0)Connects, says hello, loads the catalog. Raises FileNotFoundError or ConnectionRefusedError if Omni is not running with the API on
omni.domain.method(**params)Calls domain.method and returns its result
omni.call("domain.method", {...})The same with a method name and a dictionary
omni.wait(job)Waits for a job to finish and returns its result; raises OmniError if it failed
omni.methodsThe catalog: a dictionary from method name to its summary, params schema, example, effect
omni.capabilities, omni.api_versionFrom the handshake
OmniErrorRaised when Omni answers with an error. Has .code (the JSON-RPC code) and .data

Things worth knowing:

  • A method named like a Python keyword takes a trailing underscore: omni.audio.import_(...) calls audio.import.
  • omni.domain.method.__doc__ is the method's description from the catalog, so help() and your editor show it.
  • One request is on the wire at a time, guarded by a lock, so the client is safe to call from several threads.
  • To skip the endpoint file, set OMNI_API_ENDPOINT=host:port and OMNI_API_TOKEN.
  • wait(job) loops over jobs.wait (9 seconds per round) until the job is done.

Errors read naturally:

python
from omnidaw import Omni, OmniError

with Omni() as omni:
    try:
        omni.tracks.update(track=0, volumee=3)
    except OmniError as e:
        print(e.code, e)   # -32602 tracks.update: -32602 unknown field `volumee`, expected one of ...

A minimal client in plain Python

If you would rather not copy a file, this is the whole thing in about 30 lines (minimal_client.py). It finds Omni, shakes hands and makes calls:

python
# SPDX-License-Identifier: MIT
# Copyright (c) 2026 Tomasz Głuc
"""Smallest useful OmniDAW API client: no dependencies, just sockets and JSON."""
import json, os, socket, sys
from pathlib import Path

if sys.platform == "win32":
    config = Path(os.environ["APPDATA"]) / "omni"
elif sys.platform == "darwin":
    config = Path.home() / "Library" / "Application Support" / "omni"
else:
    config = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) / "omni"

endpoint = json.loads((config / "api" / "endpoint.json").read_text())
sock = socket.create_connection((endpoint["host"], endpoint["port"]))
stream = sock.makefile("rw", encoding="utf-8", newline="\n")
next_id = 0


def call(method, params=None):
    global next_id
    next_id += 1
    stream.write(json.dumps({"jsonrpc": "2.0", "id": next_id, "method": method, "params": params or {}}) + "\n")
    stream.flush()
    reply = json.loads(stream.readline())
    if "error" in reply:
        raise RuntimeError(f"{method}: {reply['error']['code']} {reply['error']['message']}")
    return reply["result"]


call("api.hello", {"token": endpoint["token"], "api_version": "0.21.0",
                   "client": {"name": "raw-example", "version": "1.0"}})
print(call("session.info"))
print([t["name"] for t in call("tracks.list")])

A minimal client in Node.js

The same in JavaScript, as an ES module (minimal_client.mjs, tested on Node.js 24), using only built-in modules:

javascript
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Tomasz Głuc
// Smallest useful OmniDAW API client for Node.js: built-in modules only.
import fs from 'node:fs';
import net from 'node:net';
import os from 'node:os';
import path from 'node:path';
import readline from 'node:readline';

function configDir() {
  if (process.platform === 'win32') return path.join(process.env.APPDATA, 'omni');
  if (process.platform === 'darwin') return path.join(os.homedir(), 'Library', 'Application Support', 'omni');
  return path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config'), 'omni');
}

const endpoint = JSON.parse(fs.readFileSync(path.join(configDir(), 'api', 'endpoint.json'), 'utf8'));
const socket = net.createConnection(endpoint.port, endpoint.host);
const pending = new Map();
let nextId = 0;

readline.createInterface({ input: socket }).on('line', (line) => {
  const reply = JSON.parse(line);
  const { resolve, reject } = pending.get(reply.id);
  pending.delete(reply.id);
  if (reply.error) reject(new Error(`${reply.error.code} ${reply.error.message}`));
  else resolve(reply.result);
});

const call = (method, params = {}) =>
  new Promise((resolve, reject) => {
    const id = ++nextId;
    pending.set(id, { resolve, reject });
    socket.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
  });

await call('api.hello', {
  token: endpoint.token,
  api_version: '0.21.0',
  client: { name: 'node-example', version: '1.0' },
});
console.log(await call('session.info'));
console.log((await call('tracks.list')).map((t) => t.name));
socket.end();

Replies arrive in the order of the requests, but this client matches them by id, which also makes it safe to have several calls in flight.

A minimal client in Rust

This client needs only the serde_json crate (cargo add serde_json). It is the same as session_info.rs in the download:

rust
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Tomasz Głuc
//
// A complete OmniDAW API client in Rust. It needs only `serde_json`
// (`cargo add serde_json`): find Omni, one TCP connection, one JSON line per
// call. Omni must be running with the API on (Settings, AI Create).

use serde_json::{json, Value};
use std::error::Error;
use std::io::{BufRead, BufReader, Write};
use std::net::TcpStream;
use std::path::PathBuf;

/// The API version this client was written against.
const API_VERSION: &str = "0.21.0";

/// Omni's settings folder.
fn config_dir() -> Option<PathBuf> {
    let var = |name: &str| std::env::var_os(name).map(PathBuf::from);
    if cfg!(windows) {
        var("APPDATA").map(|p| p.join("omni"))
    } else if cfg!(target_os = "macos") {
        var("HOME").map(|h| h.join("Library/Application Support/omni"))
    } else {
        var("XDG_CONFIG_HOME")
            .or_else(|| var("HOME").map(|h| h.join(".config")))
            .map(|p| p.join("omni"))
    }
}

struct Omni {
    reader: BufReader<TcpStream>,
    writer: TcpStream,
    next_id: u64,
}

impl Omni {
    /// Find Omni, connect and shake hands; returns the connection and Omni's welcome.
    fn connect() -> Result<(Self, Value), Box<dyn Error>> {
        let file = config_dir()
            .ok_or("cannot find Omni's settings folder")?
            .join("api")
            .join("endpoint.json");
        let text = std::fs::read_to_string(&file).map_err(|e| {
            format!(
                "Omni is not running with the API on ({}: {e})",
                file.display()
            )
        })?;
        let endpoint: Value = serde_json::from_str(&text)?;
        let host = endpoint["host"]
            .as_str()
            .ok_or("endpoint.json has no host")?;
        let port = endpoint["port"]
            .as_u64()
            .ok_or("endpoint.json has no port")?;

        let stream = TcpStream::connect((host, u16::try_from(port)?))?;
        let mut omni = Omni {
            writer: stream.try_clone()?,
            reader: BufReader::new(stream),
            next_id: 0,
        };
        let welcome = omni.call(
            "api.hello",
            json!({
                "token": endpoint["token"],
                "api_version": API_VERSION,
                "client": { "name": "session_info example", "version": "1.0" },
            }),
        )?;
        Ok((omni, welcome))
    }

    /// Send one request and read its response.
    fn call(&mut self, method: &str, params: Value) -> Result<Value, Box<dyn Error>> {
        self.next_id += 1;
        let request =
            json!({ "jsonrpc": "2.0", "id": self.next_id, "method": method, "params": params });
        self.writer.write_all(format!("{request}\n").as_bytes())?;
        let mut line = String::new();
        if self.reader.read_line(&mut line)? == 0 {
            return Err("Omni closed the connection".into());
        }
        let mut reply: Value = serde_json::from_str(&line)?;
        if let Some(error) = reply.get("error") {
            return Err(format!("{method}: {} {}", error["code"], error["message"]).into());
        }
        Ok(reply["result"].take())
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let (mut omni, welcome) = Omni::connect()?;
    println!(
        "Omni speaks OmniDAW API {} ({} methods, capabilities {})",
        welcome["api_version"], welcome["method_count"], welcome["capabilities"]
    );
    let info = omni.call("session.info", json!({}))?;
    println!("{}", serde_json::to_string_pretty(&info)?);
    Ok(())
}

Put it in src/main.rs of a new Cargo project and run it with cargo run.

Any other language

The recipe is the same everywhere:

  1. Work out the settings folder (table on the protocol page) and read api/endpoint.json.
  2. Open a TCP connection to host:port.
  3. Send api.hello with the token as the first line.
  4. For each call write one JSON line and read one JSON line back.
  5. Treat an error reply as an error and ignore result fields you do not know.

If you want types for your language, generate them from the catalog: api.methods (or omni-mcp --catalog when Omni is not running) gives you a JSON Schema for the parameters of every method.