Metadata-Version: 2.4
Name: iceberg-subzero
Version: 0.1.0
Summary: Python client for the Subzero tokenization vault and LLM proxy
Requires-Python: >=3.11
Requires-Dist: httpx>=0.28.0
Provides-Extra: auth
Requires-Dist: pyotp>=2.9.0; extra == 'auth'
Provides-Extra: dev
Requires-Dist: openai>=1.0.0; extra == 'dev'
Requires-Dist: pyotp>=2.9.0; extra == 'dev'
Requires-Dist: pytest>=8.3.0; extra == 'dev'
Requires-Dist: respx>=0.22.0; extra == 'dev'
Provides-Extra: openai
Requires-Dist: openai>=1.0.0; extra == 'openai'
Description-Content-Type: text/markdown

# Subzero Python SDK

Thin Python client for the [Subzero](../api/) tokenization vault and LLM proxy.

## Install

```bash
cd python-sdk
pip install -e ".[dev,openai,auth]"
```

The optional `auth` extra adds `pyotp` for TOTP code generation during dashboard login.

## Two auth planes

| Plane | Credential | Use for |
|-------|------------|---------|
| **Server integration** | API key (`sz_live_...`) | `tokenize`, `search`, `reveal`, `proxy`, tenant admin after bootstrap |
| **Dashboard / human** | JWT access token | `auth.*`, `members.*`, `admin.create_tenant`, `admin.get_tenant` |

Pass an API key, an access token, or both when constructing the client:

```python
from subzero import SubzeroClient

# Vault + proxy (server-side)
vault = SubzeroClient(api_key="sz_live_...", base_url="http://127.0.0.1:8000")

# Dashboard session (platform or tenant admin JWT)
dashboard = SubzeroClient(access_token="eyJ...", base_url="http://127.0.0.1:8000")
```

## Quick start (vault)

```python
from subzero import SubzeroClient

client = SubzeroClient(
    api_key="sz_live_...",
    base_url="http://127.0.0.1:8000",
)
client.ready()

token = client.tokenize("SSN", "123-45-6789").token
value = client.reveal(token).value
```

## Platform admin login (tenant provisioning)

`POST /v1/tenants` requires a platform-admin JWT. Log in with MFA, then create tenants:

```python
from subzero import SubzeroClient

client = SubzeroClient(base_url="http://127.0.0.1:8000")
client.ready()

client.auth.login_platform(
    email="admin@iceberg.local",
    password="...",
    totp_code="123456",       # or totp_secret="BASE32..." with pip install 'subzero[auth]'
)

tenant = client.admin.create_tenant(name="Acme", slug="acme")
admin_key = tenant.bootstrap_api_key

# Switch to tenant admin API key for day-to-day vault/proxy setup
admin = SubzeroClient(api_key=admin_key, base_url="http://127.0.0.1:8000")
admin.admin.create_entity_type(tenant.id, name="SSN", deterministic=True)
```

Tenant member invites and management use `client.members` with a tenant-admin or platform-admin JWT.

## API key scopes

| Scope | Vault methods | Notes |
|-------|---------------|-------|
| `tokenize` | `tokenize`, `search` | No plaintext return |
| `reveal` | `reveal` | Server-side plaintext reveal; requires matching policy rule |
| `reveal_grant` | `create_reveal_grant` | Mint browser reveal grants only; requires matching policy rule |
| `proxy` | `proxy.chat.completions` | In-flight tokenization |
| `admin` | All of the above + `admin.*` + `delete_token` | Bypasses reveal policy |

**Browser reveal:** the iframe calls `POST /v1/browser/reveal` with a server-minted grant. Your BFF uses a **`reveal_grant`** key to call `create_reveal_grant(token, client_public_key_jwk=..., allowed_origin=...)`. Keep **`reveal`** keys for server pipelines that need `client.reveal(token).value`.

```python
grant = client.create_reveal_grant(
    token,
    client_public_key_jwk=jwk_from_iframe,
    allowed_origin="https://app.yourcompany.com",
)
```

**Delete requires admin.** There is no delegatable delete-scoped API key at the HTTP layer. `delete_token()` needs an admin key even though policy rules support a `delete` action at the service layer.

## Proxy vs reveal vs detokenize

- **Vault reveal:** `client.reveal(token)` — `POST /v1/reveal`, reveal-scoped key + policy
- **Proxy chat:** `client.proxy.chat.completions(...)` — tokenizes declared patterns in-flight
- **Proxy detokenize:** pass `detokenize=True` or use OpenAI helper below — governed by reveal policy for the proxy key, not the reveal endpoint

```python
from subzero import create_openai_client

client = create_openai_client(
    api_key="sz_live_...",          # Subzero proxy key
    base_url="http://127.0.0.1:8000/v1",
    detokenize=True,                # X-Subzero-Detokenize: true via default_headers
)
client.chat.completions.create(model="gpt-4o", stream=False, messages=[...])
```

## Examples

With the API running (`docker compose up` or `uvicorn`):

```bash
export SUBZERO_PLATFORM_EMAIL=admin@iceberg.local
export SUBZERO_PLATFORM_PASSWORD=...
export SUBZERO_TOTP_CODE=123456   # or SUBZERO_TOTP_SECRET / SUBZERO_ACCESS_TOKEN

python examples/hero_demo.py
python examples/vault_loop.py
```

Set `SUBZERO_BASE_URL` and `OPENAI_API_KEY` as needed.

## Tests

```bash
pytest
```

## PyPI

Local editable install only for now. `# TODO: twine upload` when ready to publish.
