Metadata-Version: 2.4
Name: axilio
Version: 0.1.1
Summary: Axilio Python SDK — manage workflows, runs, devices, usage, and control devices remotely
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: black==25.1.0; extra == 'dev'
Requires-Dist: mypy==1.16.0; extra == 'dev'
Requires-Dist: openapi-python-client>=0.21; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: ruff==0.12.1; extra == 'dev'
Description-Content-Type: text/markdown

# Axilio Python SDK

The official Python SDK for [Axilio](https://axilio.ai). One pip package covers account management, device discovery, allocation, and the full remote control surface (tap, swipe, OCR, screenshot, ...).

## Installation

```bash
pip install axilio
```

## Quick start

```python
from axilio import Client

# Reads AXILIO_API_KEY from the environment.
client = Client()

# One-shot device session — allocate, connect, control, clean up.
with client.devices.session(platform="android") as device:
    device.tap(540, 1200)
    device.type("hello world")
    device.screenshot("after.png")
```

The same code runs unmodified inside an Axilio sandbox VM — when you schedule a workflow in the dashboard, Hephaestus injects scoped auth and the paired Atlas endpoint at boot, and the Client auto-detects sandbox mode. No `if local: ... else: ...` branches in your code.

## Authentication

```bash
export AXILIO_API_KEY=ax_live_...
```

Or pass explicitly:

```python
client = Client(api_key="ax_...")
client = Client(api_key="ax_...", base_url="https://staging-api.axilio.ai")
```

Generate keys from the [Axilio dashboard](https://app.axilio.ai/settings/api-keys). API keys are scoped to one organization — multi-org users mint one key per org.

## Resources

| Resource | Methods | Notes |
|---|---|---|
| `client.billing` | `balance()`, `subscription()`, `invoices()`, `upgrade()`, `downgrade()` | Reads everywhere; upgrade/downgrade local-only |
| `client.usage` | `metrics()`, `sessions()` | Both modes |
| `client.api_keys` | `list()`, `create()`, `revoke()` | Local-only |
| `client.org` | `current()`, `members()`, `invite()`, `remove_member()` | Reads everywhere; member writes local-only |
| `client.devices` | `available()`, `locations()`, `supported_apps()`, `allocate()`, `connect()`, `deallocate()`, `session()` | Sandbox mode short-circuits `allocate()` |
| `client.workflows` | `list()`, `get()`, `create()`, `update()`, `delete()` | Both modes |
| `client.runs` | `list()`, `get()`, `create()`, `cancel()` | Both modes |

For the full method surface, type signatures, and the device control vocabulary, see the [SDK design doc](https://github.com/axilioai/axilio/blob/main/.docs/sdk-design.md) in the monorepo.

## Errors

All errors raised by the SDK subclass `axilio.AxilioError`:

```python
from axilio import Client, NotFoundError, RateLimitError, SandboxPermissionError

client = Client()

try:
    run = client.runs.get("run_does_not_exist")
except NotFoundError:
    print("run not found")
except RateLimitError:
    # backoff + retry
    ...
except SandboxPermissionError:
    # tried a local-only write from inside a sandbox VM
    ...
```

| Exception | HTTP | Notes |
|---|---|---|
| `UnauthorizedError` | 401 | API key missing, malformed, or rejected |
| `NotFoundError` | 404 | Resource doesn't exist or isn't visible |
| `RateLimitError` | 429 | Retry after backoff |
| `ServerError` | 5xx | Transient; safe to retry |
| `SandboxPermissionError` | — | Local-only operation attempted from sandbox |
| `AllocationMismatchError` | — | Sandbox kwargs don't match the bound allocation |
| `AxilioError` | other | Catch-all |

## Configuration

| Env var | Description |
|---|---|
| `AXILIO_API_KEY` | API key for authentication. Required in local mode. |
| `AXILIO_BASE_URL` | Override the API host (defaults to `https://api.axilio.ai`). |
| `AXILIO_SANDBOX_TOKEN` | Scoped sandbox token. Injected by Hephaestus inside a sandbox VM; not set in local mode. |
| `AXILIO_ATLAS_ENDPOINT` | Paired Atlas endpoint inside a sandbox VM. Injected by Hephaestus. |

| Constructor kwarg | Default | Description |
|---|---|---|
| `api_key` | `AXILIO_API_KEY` env, then `AXILIO_SANDBOX_TOKEN` (sandbox mode) | Bearer token |
| `base_url` | `AXILIO_BASE_URL` env, then `https://api.axilio.ai` | API host |
| `timeout` | `30.0` | Request timeout in seconds |
| `max_retries` | `3` | Retry budget for 429 + 5xx (TODO: wire) |
| `retry_base_delay` | `0.5` | Exponential backoff base |

## Status

This is the v0 scaffold. The public surface (Client + resource classes) matches the design doc; method bodies are stubs (`NotImplementedError`) until codegen lands the typed wire calls. The codegen pipeline is driven by `repository_dispatch` from the monorepo's `publish-sdk-spec` job on each backend deploy — see `.github/workflows/sdk-regenerate.yml` (landing next).

## What's not here

- **Async client** — sync only for v0.
- **In-sandbox device drivers** (the implementation that actually drives a phone screen) — those live inside Atlas + the runner image, not in this SDK.
- **SSE / WebSocket subscriptions** for live run events — REST only; SSE lands when a consumer needs it.
