Metadata-Version: 2.5
Name: sub2api
Version: 0.1.1
Summary: Python client for the user-facing API of Sub2API instances
Author: Eight Labs
License-Expression: MIT
License-File: LICENSE
Keywords: ai-gateway,api,sdk,sub2api
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
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: curl-cffi<1,>=0.10
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: httpx<1,>=0.27; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest-cov>=5; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.7; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Description-Content-Type: text/markdown

# sub2api

`sub2api` is a Python client for the shared user-facing panel API exposed by Sub2API instances. One client object represents one user's in-memory dashboard session. Requests use `curl_cffi` with Chrome browser impersonation by default.

The library targets operations present on standard Sub2API deployments: account balance, platform quotas, usage history and statistics, API keys, groups, subscriptions, announcements, and redemption.

## Install

```bash
pip install sub2api
```

Python 3.10 or newer is required.

## Authenticate with an existing session

The dashboard's access token is different from an `sk-...` gateway API key. Browser deployments normally store the panel tokens under `auth_token` and `refresh_token` in local storage.

```python
import os

from sub2api import Sub2API

client = Sub2API(
    "https://sub2api.example.com",
    access_token=os.environ["SUB2API_ACCESS_TOKEN"],
    refresh_token=os.environ.get("SUB2API_REFRESH_TOKEN"),
)

print(client.me().email)
print(client.balance().balance)
```

Pass either the instance origin or its full `/api/v1` URL. Tokens are retained only in memory. If a refresh token is supplied, the client rotates the token pair after an authenticated `401`. Supplying `expires_at` as a Unix timestamp also enables proactive refresh.

The default browser fingerprint is Chrome. Choose another `curl_cffi` fingerprint or configure proxies by supplying your own `curl_cffi.requests.Session`:

```python
from curl_cffi import requests

session = requests.Session(impersonate="safari")
client = Sub2API("https://sub2api.example.com", session=session)
```

## Log in with email and password

```python
from sub2api import Sub2API

with Sub2API("https://sub2api.example.com") as client:
    user = client.login("person@example.com", "password")
    print(user.username)
    print(client.is_authenticated)
```

An instance with CAPTCHA enabled requires the corresponding proof:

```python
client.login(
    "person@example.com",
    "password",
    turnstile_token="captcha-proof",
)
```

For a TOTP-enabled account, `login()` raises `TwoFactorRequired` and retains the temporary challenge in memory:

```python
from sub2api import Sub2API, TwoFactorRequired

client = Sub2API("https://sub2api.example.com")

try:
    client.login("person@example.com", "password")
except TwoFactorRequired:
    client.complete_2fa("123456")
```

## Common operations

Resources are callable for their common list operation and also expose explicit methods.

```python
balance = client.balance()
quotas = client.account.platform_quotas()

groups = client.groups()
group_rates = client.groups.rates()

first_page = client.keys(page_size=50, status="active")
for api_key in first_page:
    print(api_key.id, api_key.name, api_key.group.name)

all_keys = client.keys.all()
resolved = client.keys.with_group_multipliers()
for item in resolved:
    print(
        item.api_key.key,
        item.group_id,
        item.base_multiplier,
        item.custom_multiplier,
        item.effective_multiplier,
    )

multiplier_by_key = client.keys.multiplier_map(key_by="key")
multiplier_by_id = client.keys.multiplier_map(key_by="id")

created = client.keys.create("automation", group_id=groups[0].id)
client.keys.update(created.id, name="nightly automation")
client.keys.set_status(created.id, active=False)
client.keys.delete(created.id)
```

`all()` follows pagination until every key has been fetched. `with_group_multipliers()` joins each key to its group and reports the base, user-specific, and effective rate; the user-specific rate from `/groups/rates` takes precedence. `multiplier_map()` returns the effective rate keyed by the API key value, key ID, or name. Name collisions raise an error instead of silently overwriting an entry.

API key values are available through `api_key.key`, but object representations redact fields that commonly contain credentials.

## Usage history

`history` and `usage` refer to the same resource.

```python
from datetime import date, timedelta

end = date.today()
start = end - timedelta(days=7)

page = client.history(
    start_date=start,
    end_date=end,
    page_size=100,
    sort_by="created_at",
    sort_order="desc",
)

for record in page:
    print(record.created_at, record.model, record.actual_cost)

for record in client.history.iter(page_size=100):
    process(record)

stats = client.usage.stats(start_date=start, end_date=end)
dashboard = client.usage.dashboard()
trend = client.usage.trend(start_date=start, end_date=end, granularity="day")
models = client.usage.models(start_date=start, end_date=end)
snapshot = client.usage.snapshot(start_date=start, end_date=end)
```

## Other shared resources

```python
active_subscriptions = client.subscriptions(active=True)
announcements = client.announcements()
client.announcements.mark_read(announcements[0].id)

result = client.redeem("REDEMPTION-CODE")
redemption_history = client.redeem.history()
```

## Fork-specific endpoints

`request()` provides the same authentication, envelope handling, timezone parameter, refresh behavior, and error mapping for relative endpoints that are not part of the stable resource API.

```python
result = client.request("GET", "some-fork-specific-endpoint")
```

Absolute URLs and parent-path traversal are rejected so a session token cannot be redirected outside the configured API root.

## Errors

HTTP and Sub2API envelope failures use typed exceptions:

```python
from sub2api import AuthenticationError, RateLimitError, Sub2APIError

try:
    client.keys.create("automation")
except RateLimitError as error:
    print(error.retry_after)
except AuthenticationError:
    client.login("person@example.com", "password")
except Sub2APIError as error:
    print(error)
```

Remote plaintext HTTP is rejected by default because it exposes login credentials and tokens. Localhost HTTP is allowed for development; other HTTP instances require `allow_insecure=True`.

