Metadata-Version: 2.4
Name: orion-sleep-api
Version: 0.1.0
Summary: Async client for the Orion Sleep smart mattress topper API
Project-URL: Homepage, https://github.com/borski/orion-sleep-api
Project-URL: Issues, https://github.com/borski/orion-sleep-api/issues
Project-URL: Home Assistant integration, https://github.com/borski/home-assistant-orion-integration
Author: Michael Borohovski
License-Expression: MIT
License-File: LICENSE
Keywords: home-assistant,iot,mattress,orion,sleep
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Home Automation
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: aiohttp>=3.9
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Description-Content-Type: text/markdown

# orion-sleep-api

Async Python client for the [Orion Sleep](https://orionbed.com) smart
mattress topper.

Orion publishes no API documentation. This contract was rebuilt from
captured traffic and a decompile of the Android app, then verified against
a real bed one endpoint at a time.

## Every endpoint says how much it is trusted

That verification is the point of this library, so it is written into the
code rather than a wiki nobody reads.

| Label | Meaning |
|---|---|
| Measured | A real request went to a real bed and the result was recorded. |
| App-derived | Read out of the decompiled Android client. Plausible, never executed. |
| Speculative | Guessed. Nothing speculative ships here. |

Every method's docstring carries one, along with what the label rests on:
a dated request and its response, or the part of the app the contract was
read out of.

The labels exist because a reverse-engineered contract has no other way to
tell a route that was proven from one that was only inferred. Both look
equally confident in code, and only one of them will work.

## Install

```
pip install orion-sleep-api
```

## Quick start

```python
import aiohttp
from orion_sleep_api import OrionApiClient

async with aiohttp.ClientSession() as session:
    client = OrionApiClient(session)

    # Passwordless, same as the app. A code arrives by SMS or email.
    await client.request_auth_code(phone="15555550100")
    tokens = await client.verify_auth_code(code="123456", phone="15555550100")

    client = OrionApiClient(session, **tokens)
    devices = await client.list_devices()
```

Tokens refresh themselves. Register a callback to persist the new ones:

```python
client.set_token_refresh_callback(
    lambda access, refresh, expires_at: save_somewhere(access, refresh, expires_at)
)
```

## Two identifiers, and they are not interchangeable

This is the single easiest way to get a confusing 403 or 404 out of this
API.

| Routes | Identifier |
|---|---|
| `/live`, `/live/zones/{id}`, `/action`, `/activate`, `/deactivate`, `/update`, the WebSocket | `serial_number` |
| `PUT /v1/devices/{id}`, the only metadata write (name, orientation, timezone) | device UUID |

Sending the wrong one fails with no useful message. Three of these routes
were originally wired up with the UUID and returned an error on every
call, which is how the rule was found.

Nearly everything takes the serial. Reach for the UUID only on that one
metadata write, and when a route is undocumented, guess serial.

## What it covers

Devices and live state, per-zone temperature and power, LED brightness and
quiet mode, rapid cooling, per-user sleep schedules including single-day
overrides, sleep insights with stages and apnea, session management, user
access and invitations, and the live WebSocket.

Full endpoint list with confidence labels: read the docstrings. The
evidence behind every "measured" label is under "What is known about the
API" in [`AGENTS.md`](AGENTS.md), organised by route family, recording
what was sent and what came back.
There is also an
[`openapi.yaml`](https://github.com/borski/home-assistant-orion-integration/blob/main/openapi.yaml)
in the Home Assistant integration repository.

## Live data

```python
from orion_sleep_api import OrionWebSocketManager

def on_message(serial, event_type, payload):
    print(serial, event_type, payload.get("zones"))

manager = OrionWebSocketManager(session, client, on_message=on_message)
manager.sync_to_serials(["ABCDEF123456"])
```

The socket pushes roughly every two seconds. It needs ALPN forced to
`http/1.1` and an okhttp user agent, both of which this library handles.
Thirty seconds of silence means the stream is dead even though the socket
is still open, so the client reconnects with backoff.

## Home Assistant

The [Orion Sleep integration](https://github.com/borski/home-assistant-orion-integration)
is built on this library and is the reason it exists. If you want a bed in
Home Assistant, install that. If you want to write your own tooling, this
is the piece you want.

## Contributing

One rule: do not add a method for a route you have not verified. If you
cannot demonstrate a request against a live bed, label it app-derived and
say so in the docstring. A confident wrapper around a guessed endpoint is
worse than no wrapper.

[`AGENTS.md`](AGENTS.md) has the rest: how to verify a write against a bed
somebody may be asleep on, what is known about each route family, the
traps that have cost people hours, the layout, and the release process.

## License

MIT.

Not affiliated with or endorsed by Orion Longevity Inc.
