Skip to content

Getting started

roon-internal-api is the TypeScript side of this experiment. It lives in the roon-internal-api/ directory of the repo. It’s not published to npm — use it from a clone. Expect rough edges; this is a proof-of-concept.

The latest tagged version is v0.1.1. See Releases for changes, compatibility notes, and the validation performed on Roon 2.73 build 1696.

Terminal window
git clone https://github.com/arthursoares/roon-api-reverse-engineering
cd roon-api-reverse-engineering/roon-internal-api
npm install
npm run build
npm test -- --runInBand

Two values identify your Core. They aren’t secrets (there’s no auth here), but they’re specific to your install, so supply your own rather than copying mine:

ValueWhat it isHow to find it
hostCore IP / hostnameRoon → Settings → About, or your router
serverBrokerId16-byte Core id (hex)read it off a capture of the handshake — see Contributing

The RoonClient facade is the front door. A minimal, read-only program:

import { RoonClient } from './src';
async function main(): Promise<void> {
const host = process.env.ROON_HOST;
const brokerId = process.env.ROON_SERVER_BROKER_ID;
if (!host || !brokerId || !/^[0-9a-f]{32}$/i.test(brokerId)) {
throw new Error('Set ROON_HOST and ROON_SERVER_BROKER_ID (32 hex characters)');
}
const roon = new RoonClient({
host,
serverBrokerId: Buffer.from(brokerId, 'hex'),
});
try {
await roon.connect();
console.log('Library oid:', roon.serviceOid('Library').toString());
console.log('Zone oid:', roon.zoneByName('Living Room')?.toString());
const album = roon.findByTitle('AlbumLite', 'Kind of Blue');
console.log(album ? `found, oid=${album.oid}` : 'not loaded');
} finally {
roon.close();
}
}
main().catch(console.error);

Run the included version with both variables set:

Terminal window
ROON_HOST=192.168.1.50 ROON_SERVER_BROKER_ID=0123456789abcdef0123456789abcdef \
npx ts-node examples/demo.ts

On connect() the client does the handshake, resolves the root service, and starts ingesting the streaming object graph — so zones, devices, now-playing, and loaded library content are queryable via the helpers below. How reliable this is beyond my own setup, I genuinely don’t know.

MethodPurpose
connect() / close()open / close the session
serviceOid(name)object id of a singleton service ('Library', 'Transport'…)
zoneByName(name) / endpointByName(name)resolve a zone / endpoint oid
findByTitle(type, title)find a loaded object by title (e.g. 'AlbumLite')
titleOf(obj)best-effort display title of an object
call(service, method, params, args, oid?)low-level escape hatch to any method
structArg(typeName, fields)build a by-val struct argument

For doing things — favorites, playback, edits, search — see Recipes. For the full generated method surface, see the generated API.