pyodio, the Python library for odio
pyodio is an async Python client library for the odio API. Everything the other clients in this section can do, your own Python code can do too: MPRIS players and their tracklists, volume and outputs, systemd services, Bluetooth, power, and upgrades.
It is the library the Home Assistant integration is built on.
Installation
Section titled “Installation”pip install pyodio- Python 3.12 or newer (tested on 3.12, 3.13 and 3.14)
- Dependencies:
aiohttp3.9 or newer, nothing else - License: MIT
pyodio is async only, built on asyncio. From synchronous code, wrap your calls in a coroutine run with asyncio.run(). There are no credentials to configure: the odio API has no authentication and is meant for trusted local networks.
pyodio talks to any node running go-odio-api, a full odio streamer or a standalone API install on a desktop or NAS. Models parse tolerantly: unknown fields are ignored and missing ones fall back to defaults, so a newer or older server never breaks parsing. Features follow the server, for example tracklists need odio-api v0.16.0 or newer.
Quick start
Section titled “Quick start”import asyncioimport pyodio
async def main(): async with pyodio.connect("http://odio.local:8018") as odio: print(f"Connected to {odio.server.hostname} (odio-api {odio.server.api_version})")
# MPRIS players are live objects with transport controls player = odio.players.find("spotifyd") if player: print(f"{player.title} - {player.artist} [{player.playback_status}]") await player.play_pause() await player.set_volume(0.5)
# Master volume and outputs await odio.audio.set_volume(0.4) for output in odio.audio.outputs.values(): print(f"{'*' if output.is_default else ' '} {output.description}")
# React to live changes pushed by the server odio.players.on_change(lambda change, p: print(f"[{change}] {p.app_name}: {p.title}")) await asyncio.sleep(60)
asyncio.run(main())The URL defaults to http://localhost:8018, the API’s default listen address, so a script running on the node itself can call pyodio.connect() without arguments. To reach a node from another machine, its API must listen on a network interface, see network binding.
Two layers
Section titled “Two layers”pyodio ships two APIs over the same HTTP connection. Pick the one that matches how your code thinks about state.
OdioHub (high level) | OdioClient (low level) | |
|---|---|---|
| Entry point | pyodio.connect(url) | pyodio.OdioClient(url) |
| State | Kept in memory, updated live from the SSE stream | None, every call is a fresh HTTP request |
| Returns | Live entities (Player, AudioOutput, Service, …) carrying state and commands | Plain dataclasses (PlayerState, AudioOutputState, …) |
| Reconnection | Automatic, with a full resync after every reconnect | Up to you |
| Fits | Bots, dashboards, automations, integrations | One-shot scripts, CLIs, tests, your own state layer |
Most code should start with the hub. The hub is built on the client, and exposes it as odio.client, so you can drop down a level at any time.
Source and support
Section titled “Source and support”- Source code and issues: github.com/b0bbywan/pyodio
- Package: pypi.org/project/pyodio
- Questions and show-and-tell: GitHub Discussions