Metadata-Version: 2.4
Name: elav8-paga
Version: 0.1.0
Summary: Python SDK for Elav8 Paga — Sign in with Elav8 (OAuth 2.1 + PKCE), offline token verification, entitlements, checkout/portal, and webhook verification.
Project-URL: Homepage, https://elav8.dev
Project-URL: Documentation, https://elav8.dev/docs
Author: Elav8
License-Expression: LicenseRef-Proprietary
Keywords: billing,elav8,entitlements,jwt,oauth,oidc,paga
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: cryptography>=42.0
Requires-Dist: httpx>=0.27
Requires-Dist: pyjwt[crypto]>=2.8
Provides-Extra: dev
Requires-Dist: fastapi>=0.110; extra == 'dev'
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.110; extra == 'fastapi'
Description-Content-Type: text/markdown

# elav8 (Python SDK)

Python SDK for **Elav8 / Paga** — "Sign in with Elav8" (OAuth 2.1 + PKCE),
offline access-token verification, entitlements, hosted checkout / customer
portal, and webhook signature verification. Mirrors the JavaScript
[`@elav8/paga`](https://www.npmjs.com/package/@elav8/paga).

- Offline JWT verification against Elav8's JWKS (no per-request network call).
- One-line FastAPI route protection.
- Small dependency surface: `PyJWT`, `cryptography`, `httpx`.

## Install

```bash
pip install elav8-paga
# with the FastAPI helper:
pip install "elav8-paga[fastapi]"
```

## Verify an access token (offline)

```python
from paga import verify_access_token, Elav8Error

try:
    claims = verify_access_token(token, issuer="https://billing.example.com/api/auth")
    user_id = claims["sub"]
except Elav8Error:
    ...  # 401
```

> Trade-off: verification is **offline**, so a token revoked before its `exp`
> (~10 min) stays accepted until it expires. For sensitive operations, re-check
> server state (e.g. entitlements) rather than trusting the token alone.

## Protect a FastAPI route (one line)

```python
from fastapi import Depends, FastAPI
from paga.fastapi import require_user

app = FastAPI()
authed = require_user(issuer="https://billing.example.com/api/auth")

@app.get("/me")
def me(user: dict = Depends(authed)):
    return {"sub": user["sub"]}
```

## Sign in with Elav8 (OAuth 2.1 + PKCE)

```python
from paga import Elav8OAuth

oauth = Elav8OAuth(
    issuer="https://billing.example.com/api/auth",
    client_id="client_...",
    redirect_uri="https://app.example.com/callback",
)

# 1. Begin sign-in — persist state + code_verifier in the session, redirect to .url
flow = oauth.start()

# 2. On your callback route:
tokens = oauth.exchange_code(code, code_verifier)
# tokens.access_token is a JWT verifiable with verify_access_token(...)
```

## Server API (secret key)

```python
import os
from paga import Elav8ServerClient, CustomerInput

elav8 = Elav8ServerClient(
    secret_key=os.environ["ELAV8_SECRET_KEY"],
    base_url="https://billing.example.com",
    webhook_secret=os.environ.get("ELAV8_WEBHOOK_SECRET"),
)

snapshot = elav8.get_entitlements("user_1")
if snapshot.entitlements.get("pro"):
    ...

checkout = elav8.create_checkout(
    plan="pro",
    customer=CustomerInput(email="a@b.com", external_user_id="user_1"),
    success_url="https://app.example.com/welcome",
    cancel_url="https://app.example.com/pricing",
)
# redirect the customer to checkout.url
```

## Verify a webhook

```python
event = elav8.construct_webhook_event(raw_body, signature_header)
```

Elav8 signs each webhook with `x-elav8-signature: t=<unix>,v1=<hmac-sha256-hex>`
over `"{t}.{raw_body}"`; the timestamp blocks replays. Always pass the **raw**
request body.

## License

Proprietary — internal to Elav8 until open-sourced.
