Metadata-Version: 2.4
Name: vellumcharter
Version: 0.1.0
Summary: Thin, dependency-free server-side client for the Vellum entitlements + billing API
License-Expression: MIT
Project-URL: Homepage, https://vellumcharter.com
Project-URL: Repository, https://github.com/tdesposito/Vellum
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# vellumcharter (Python SDK)

A thin, **dependency-free** (stdlib `urllib` + `hmac`) server-side client for the
Vellum entitlements + billing API. The Python counterpart to `sdks/js/` (TypeScript);
built for Python consumers such as SAM2-SalesImport. **Server-side only** — it
holds a secret API key.

```python
from vellumcharter import VellumClient, verify_push_signature

vellum = VellumClient(
    api_key="vlm_sam.xxxxx",   # from SSM, never shipped to a browser
    tenant_id="sam",
    # base_url defaults to https://api.vellumcharter.com; override for tests/staging.
)

# Gate a feature (pull-first; cached with a short TTL):
if vellum.can("unit_1", "imports"):
    ...

ent = vellum.get_entitlements("unit_1")
ent.is_active()            # True for active/trialing
ent.has("imports")
ent.config("trial_days")
ent.found                  # False if the customer has no record (no exception)
```

## Async

`AsyncVellumClient` is the awaitable counterpart — same constructor and methods,
each `await`-ed. It wraps the sync client on a worker thread (`asyncio.to_thread`),
so the SDK stays dependency-free. Use it from async frameworks (e.g. FastAPI):

```python
from vellumcharter import AsyncVellumClient

vellum = AsyncVellumClient(api_key="vlm_sam.xxxxx", tenant_id="sam")

if await vellum.can("unit_1", "imports"):
    ...

ent = await vellum.get_entitlements("unit_1")
vellum.invalidate("unit_1")   # cache op is synchronous (no await)
```

It is "async-compatible" (each call runs in a worker thread), not single-socket
async IO — the right trade-off for server-side entitlement checks while keeping
zero dependencies.

## What it covers

- **Entitlements:** `get_entitlements`, `can`, opt-in TTL cache + `invalidate`.
  A 404 returns an empty `EntitlementSet` (no exception), so gating never needs a
  null check.
- **Checkout / subscriptions:** `create_checkout`, `create_setup_checkout`
  (payment method, no charge), `create_subscription` (server-side trial),
  `cancel_subscription`, `get_subscription`.
- **Provisioning:** `create_account` (adopt or create the Stripe customer),
  `create_customer`, `set_account_status`, `set_customer_status`.
- **Billing facade:** `list_payment_methods`, `set_default_payment_method`,
  `remove_payment_method`, `set_subscription_payment_method`, `list_invoices`,
  `invoice_pdf_url` (returns Stripe's hosted PDF URL).
- **Push verification:** `verify_push_signature(raw_body, header, secret)` for the
  consumer's `/webhooks/vellum` endpoint (mirrors `api/src/lib/notify.ts`).

`VellumAuthError` is raised on 401/403; `VellumApiError` (with `.status`/`.body`)
on other non-2xx; `VellumSignatureError` on a bad push signature.

## Auth & errors

The API key is sent as `x-api-key` on every request. Keep it server-side.

## Requirements & tooling

Python **3.11+** (3.9/3.10 are end-of-life). Managed with [uv](https://docs.astral.sh/uv/):

```sh
uv sync                                   # create the venv (pinned to 3.11 via .python-version)
uv run python -m unittest discover -s tests
```

The SDK itself has no third-party dependencies — `uv` is just for the dev/test
environment and interpreter pinning.
