Skip to content

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.

Terminal window
pip install pyodio
  • Python 3.12 or newer (tested on 3.12, 3.13 and 3.14)
  • Dependencies: aiohttp 3.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.

import asyncio
import 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.

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 pointpyodio.connect(url)pyodio.OdioClient(url)
StateKept in memory, updated live from the SSE streamNone, every call is a fresh HTTP request
ReturnsLive entities (Player, AudioOutput, Service, …) carrying state and commandsPlain dataclasses (PlayerState, AudioOutputState, …)
ReconnectionAutomatic, with a full resync after every reconnectUp to you
FitsBots, dashboards, automations, integrationsOne-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.