Metadata-Version: 2.5
Name: getmux
Version: 0.1.1
Summary: One API, one key and one bill for Exa, Parallel, Keenable and DeepSeek web search.
Project-URL: Homepage, https://getmux.io
Project-URL: Documentation, https://getmux.io/docs/sdks
Project-URL: Changelog, https://getmux.io/changelog
Author-email: Mux <support@getmux.io>
License-Expression: MIT
License-File: LICENSE
Keywords: agents,api,deepseek,exa,keenable,llm,mux,parallel,search,web search
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Description-Content-Type: text/markdown

# getmux

Python client for [Mux](https://getmux.io): one API, one key and one bill for Exa, Parallel, Keenable and DeepSeek web search, with automatic fallbacks, per-key spend limits and bring-your-own-key support.

```bash
uv add getmux
```

Requires Python 3.9+. The only dependency is [`httpx`](https://www.python-httpx.org/).

## Quickstart

Create an API key in the [dashboard](https://getmux.io/dashboard), add credits, and put the key in your environment:

```bash
export MUX_API_KEY="mux-..."
```

```python
from getmux import Mux

mux = Mux()  # reads MUX_API_KEY
res = mux.search("latest developments in fusion energy", max_results=5)

for r in res.results:
    print(r.title, r.url)
print(f"served by {res.model} for ${res.cost_usd}")
```

You can also pass the key explicitly. An explicit key always wins over the environment variable, so one process can hold several clients:

```python
mux = Mux(api_key="mux-...")
```

## Choosing models and fallbacks

With no `model`, Mux uses `auto`. Pin a model, or give an ordered list to fall back through:

```python
mux.search("solid-state batteries", model="exa/auto")

res = mux.search(
    "solid-state batteries",
    models=["parallel/fast", "exa/auto", "keenable/pro"],  # tried in order
    include_domains=["nature.com"],
    published_after="2026-01-01",
    content="full",
)
print(res.attempts)  # every model tried and how it went
```

Failed provider calls are refunded automatically, so you only pay for the answer you got. See the [model list](https://getmux.io/docs/models).

## Async

```python
import asyncio
from getmux import AsyncMux

async def main():
    mux = AsyncMux()
    res = await mux.search("open source vector databases")
    print(res.results[0].url)
    await mux.aclose()

asyncio.run(main())
```

`AsyncMux` has the same methods as `Mux`, as coroutines. Close the sync client with `mux.close()`.

## Errors and retries

Every non-2xx response raises `MuxError` with `status`, `code`, `message` and the parsed `body`:

```python
from getmux import Mux, MuxError

try:
    Mux().search("hello")
except MuxError as e:
    if e.code == "insufficient_credits":
        print("Add credits:", e.body["needed_usd"])
    else:
        raise
```

`429` and `5xx` responses (except `502`) are retried automatically, up to `max_retries` times (default 2) with exponential backoff starting at 300 ms. If the response has a `Retry-After` header, the client waits at least that long (up to 10 seconds). `4xx` errors, including `402`, are not retried. See [Errors](https://getmux.io/docs/errors) for every code.

## Other calls

```python
mux.credits()                      # balance in USD
mux.checkout(50)                   # Stripe checkout URL for a $50 top-up
mux.usage(days=7)                  # spend and request counts
mux.activity(limit=20)             # recent requests
mux.ledger(kind="usage")           # credit history
mux.key()                          # limit, remaining and usage of the key in use
```

### Managing API keys

Key management needs a **management key** (`mux-mgmt-...`), which can administer keys but can never search:

```python
import os

admin = Mux(api_key=os.environ["MUX_MGMT_KEY"])

secret, key = admin.keys.create("agent-7", limit=25, limit_reset="monthly")  # the secret is shown once
admin.keys.update(key.id, limit=50)       # change a limit
admin.keys.update(key.id, limit=None)     # remove it (omit an argument to leave it unchanged)
admin.keys.update(key.id, disabled=True)  # kill switch
admin.keys.list()
admin.keys.delete(key.id)
```

### Bring your own provider keys

```python
admin.provider_keys.set("exa", "my-exa-key")
admin.provider_keys.set("parallel", "my-key", always_use=True)  # never fall back to Mux's shared key
admin.provider_keys.list()   # only a hint of the last 4 characters is returned
admin.provider_keys.delete("exa")
```

Your keys are encrypted at rest. See [Bring your own key](https://getmux.io/docs/byok) for pricing.

## Client options

| Option | Default | Description |
|---|---|---|
| `api_key` | `MUX_API_KEY` | Your key. The constructor raises `ValueError` if neither is set. |
| `base_url` | `MUX_BASE_URL`, else `https://api.getmux.io/v1` | API base URL. |
| `timeout` | `30.0` | Per-request timeout in seconds. |
| `max_retries` | `2` | Retries for `429` and `5xx` (except `502`). |

## Links

- Documentation: <https://getmux.io/docs/sdks>
- Status: <https://status.getmux.io>
- Support: support@getmux.io

MIT licensed.
