Metadata-Version: 2.4
Name: onstoa
Version: 0.2.0
Summary: Usage-based billing SDK for AI applications
Project-URL: Homepage, https://onstoa.com
Project-URL: Repository, https://github.com/stoa-org/stoa
Project-URL: Documentation, https://docs.onstoa.com
Author-email: Stoa <hello@stoa.dev>
License-Expression: MIT
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Requires-Dist: httpx>=0.25.0
Requires-Dist: orjson>=3.9.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: pyjwt[crypto]>=2.0.0
Requires-Dist: tenacity>=8.0.0
Requires-Dist: uuid-utils>=0.6.0
Provides-Extra: all
Requires-Dist: anthropic>=0.18.0; extra == 'all'
Requires-Dist: elevenlabs>=1.0.0; extra == 'all'
Requires-Dist: openai>=1.0.0; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.18.0; extra == 'anthropic'
Provides-Extra: dev
Requires-Dist: anthropic>=0.18.0; extra == 'dev'
Requires-Dist: elevenlabs>=1.0.0; extra == 'dev'
Requires-Dist: openai>=1.0.0; extra == 'dev'
Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
Requires-Dist: pytest-xdist>=3.5.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Requires-Dist: ty>=0.0.7; extra == 'dev'
Provides-Extra: elevenlabs
Requires-Dist: elevenlabs>=1.0.0; extra == 'elevenlabs'
Provides-Extra: openai
Requires-Dist: openai>=1.0.0; extra == 'openai'
Description-Content-Type: text/markdown

# Stoa SDK

Usage-based billing SDK for AI applications. Wrap your AI provider calls to automatically meter usage and bill your users.

## Installation

```bash
pip install stoa
```

## Quick Start

```python
from stoa import Stoa, InsufficientBalanceError

# Reads STOA_API_KEY and STOA_APP_ID from environment
stoa = Stoa()

# 1. Register or reuse the member from your backend
stoa.register_member(
    connect_secret=STOA_CONNECT_SECRET,
    user_id="user_123",
    email="ada@example.com",
)

# 2. Use your app's own user ID for billing calls
client = stoa.openai(user_id="user_123")

try:
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": "Hello!"}]
    )
except InsufficientBalanceError as e:
    print(f"Please top up: {e.topup_url}")
```

Typical flow:
1. call `stoa.register_member(...)` from your backend
2. keep using your own app `user_id` in SDK billing calls
3. send the user to `e.topup_url` if they need funds

## Onboard Users

Stoa can stay invisible to the customer while still owning the canonical user
and membership records.

### User onboards via your app

From your backend, call the SDK once with your connect secret and the user's
verified app identity:

```python
from stoa import Stoa

stoa = Stoa(api_key=STOA_API_KEY, base_url=STOA_BASE_URL, app_id=STOA_APP_ID)

result = stoa.register_member(
    connect_secret=STOA_CONNECT_SECRET,
    user_id=user.id,
    email=user.email,
    name=user.name,
    avatar_url=user.avatar_url,
)

# Optional: store the membership ID for debugging or richer account UX.
persist_membership_binding(
    user_id=user.id,
    membership_id=result.membership_id,
)
```

Stoa registers the user for billing in your app.

Your app can continue billing that user with its own `user_id`.

#### Optional: open a hosted top-up page

If you want an explicit "Add funds" action in your product:

```python
topup_url = stoa.get_topup_url(user_id=user.id)
```

This returns a hosted Stoa payment page for that app user.

### User installs via Stoa marketplace

Marketplace installs use the install flow below.

#### Install callback

Add a callback endpoint that verifies the Stoa install token and activates the
membership for your app user. Both flows should converge on the same stored
membership binding in your app.

```python
from stoa import Stoa

stoa = Stoa()


def stoa_callback(token: str):
    claims = stoa.verify_install_callback(token=token)

    user = find_or_create_user(
        email=claims["email"],
        name=claims["name"],
    )

    install = stoa.exchange_install_token(
        token=token,
        user_id=user.id,
    )

    persist_membership_binding(
        user_id=user.id,
        membership_id=install.membership_id,
    )
    return create_session_and_redirect(user)
```

The SDK fetches Stoa's public key for you and keeps the server-side activation
step in place, so the secure flow does not become app boilerplate.

## Supported Providers

### OpenAI

```python
client = stoa.openai(user_id="user_123")

# Chat completions
response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": "Explain quantum computing"}]
)

# Embeddings
embeddings = client.embeddings.create(
    model="text-embedding-3-small",
    input="Hello world"
)
```

### Anthropic

```python
client = stoa.anthropic(user_id="user_123")

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Write a haiku about Python"}]
)
```

### OpenRouter

Access 100+ models through a single API:

```python
client = stoa.openrouter(user_id="user_123")

# Use any model available on OpenRouter
response = client.chat.completions.create(
    model="meta-llama/llama-3.1-70b-instruct",
    messages=[{"role": "user", "content": "Hello!"}]
)
```

### ElevenLabs

```python
client = stoa.elevenlabs(user_id="user_123")

audio = client.text_to_speech.convert(
    voice_id="JBFqnCBsd6RMkjVDRZzb",
    text="Hello, welcome to our application!"
)
```

## Environment Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `STOA_API_KEY` | Yes | - | Your Stoa application API key |
| `STOA_APP_ID` | No | - | Your Stoa application ID (required for member registration unless passed explicitly) |
| `STOA_BASE_URL` | No | `https://api.onstoa.com` | Stoa API base URL (for self-hosted or testing) |

### Example .env file

```bash
# Required
STOA_API_KEY=stoa_app_xxx
STOA_APP_ID=app_xxx

# Optional - override API endpoint
STOA_BASE_URL=https://api.onstoa.com

# Provider keys (only needed for providers you use)
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
OPENROUTER_API_KEY=sk-or-...
ELEVENLABS_API_KEY=...
```

## Documentation

See [docs.onstoa.com](https://docs.onstoa.com) for full documentation.
