Metadata-Version: 2.5
Name: roveapi
Version: 1.3.0
Summary: Browser automation SDK for AI agents — Playwright-as-a-Service with accessibility trees
Project-URL: Homepage, https://roveapi.com
Project-URL: Documentation, https://roveapi.com/docs
Project-URL: Repository, https://github.com/roveapi/roveapi-python
License-Expression: MIT
Keywords: a11y,accessibility,ai-agent,automation,browser,mcp,playwright
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Description-Content-Type: text/markdown

# roveapi

Python SDK for the [Rove](https://roveapi.com) browser automation API — Playwright-as-a-Service with accessibility trees for 77% fewer tokens.

## Install

```bash
pip install roveapi
```

## Quickstart

```python
from roveapi import BrowserClient

client = BrowserClient(api_key="rvp_live_...")

# One-shot screenshot
screenshot = client.screenshot(url="https://example.com")
print(screenshot["url"])  # signed S3 URL

# Session with context manager (auto-close)
with client.session() as session:
    session.navigate("https://example.com")
    tree = session.get_a11y_tree()
    print(tree["result"]["tree"])  # ~26K tokens vs ~114K for screenshot

    session.click(label="Get Started")
    session.fill(selector="#email", value="user@example.com")
```

## Error Handling

```python
from roveapi import BrowserClient, InsufficientCreditsError, RateLimitError

try:
    with client.session() as session:
        session.navigate("https://example.com")
except InsufficientCreditsError as e:
    # next_step: "buy_credits" (buy one of e.packs_within_limits) or "ask_owner" (give a person e.owner_action_url)
    print(e.next_step, e.packs_within_limits, e.owner_action_url)
except RateLimitError as e:
    print(f"Retry after {e.retry_after} seconds")
```

## Credits and purchases

An API key can read its balance and the catalogue, and buy credit packs when the account owner has allowed it.

```python
from roveapi import BrowserClient, format_credits

with BrowserClient(api_key="rvp_live_...") as client:
    balance = client.balance()
    print(format_credits(balance["credits_remaining_millicredits"]))  # "1.75" (integer arithmetic)

    packs = balance["purchasing"]["packs_within_limits"]
    if balance["purchasing"]["allowed"] and packs:
        purchase = client.purchase_credits(packs[0])
        if purchase["next_action"]:
            print(purchase["next_action"]["instruction"], purchase["next_action"]["url"])  # a person pays here
        # Poll every 5 s or slower until it is paid:
        latest = client.get_purchase(purchase["id"])
```

- `purchase_credits` generates an idempotency key when you pass none, reuses it on its own retries (network errors and timeouts, `409 idempotency-in-progress`, `429`, `503`; at most 3 attempts, honouring `Retry-After` up to 60 seconds), returns it as `result["idempotency_key"]`, and sets `e.idempotency_key` on every exception it raises. Retry with that key to never buy twice; use a new key when the error's `retry` is `new_key`.
- Exceptions: `PurchaseNotPermittedError` (`reason`, `suggested_payment`), `SpendLimitExceededError` (`limit`, `limit_cents`, `committed_cents`, `requested_cents`, `resets_at`, `packs_within_limits`), `PaymentFailedError` (`purchase_id`, `failure_code`), `IdempotencyKeyReusedError`, `BillingUnavailableError` (`retry_after`). Every `RoveError` carries the problem's `retry` and `owner_action_url` when the API sent them, and the raw body as `problem`. Problems are matched by the last path segment of their `type`.
- With a test key (`rvp_test_`), purchases are simulated: `client.simulate_purchase(purchase_id, "paid")` (or `"expired"`, `"failed"`, `"authentication_required"`).

## API

### `BrowserClient(api_key, base_url?, retries?, retry_delay?)`

- `client.session(**options)` → `Session` (context manager)
- `client.screenshot(url, **options)` → dict
- `client.extract(url, schema, **options)` → dict
- `client.usage()` → dict (its `api_key` field is deprecated: the masked prefix `rvp_live_…`/`rvp_test_…`, never the token)
- `client.balance()` → dict
- `client.catalogue()` → dict
- `client.purchase_credits(item_id, *, payment="auto", idempotency_key=None)` → dict (includes `idempotency_key`); `payment` is `"auto"`, `"checkout"` or `"saved_method"`
- `client.get_purchase(purchase_id)` → dict
- `client.list_purchases(*, limit=20, cursor=None)` → `{"data": [...], "next_cursor": ...}`
- `client.cancel_purchase(purchase_id)` → dict
- `client.simulate_purchase(purchase_id, outcome)` → dict (test keys only)
- `roveapi.format_credits(millicredits)` → str

### `Session`

All 12 browser actions: `navigate()`, `get_a11y_tree()`, `click()`, `fill()`, `select()`, `scroll()`, `wait_for()`, `screenshot()`, `get_text()`, `get_attribute()`, `evaluate()`, `close()`.

Retries network errors, 5xx, `429` and `409 idempotency-in-progress` with exponential backoff, honouring `Retry-After` (up to 60 seconds) on `429` and `503`. Other 4xx (including 402 and 410) are never retried.
