Metadata-Version: 2.4
Name: velerian-sdk
Version: 0.1.0
Summary: Official Python SDK for the Velerian governed-run + evidence APIs.
Project-URL: Homepage, https://velerian.ai
Project-URL: API Reference, https://github.com/Velerian-Inc/velerian-platform/blob/main/docs/api/getting-started.md
Project-URL: Source, https://github.com/Velerian-Inc/velerian-platform/tree/main/sdks/python
Author: Velerian, Inc.
License: Proprietary
Keywords: agentic,compliance,governance,sdk,velerian
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Provides-Extra: dev
Requires-Dist: pytest<9,>=8; extra == 'dev'
Description-Content-Type: text/markdown

# Velerian Python SDK

Official Python client for the Velerian **governed-run + evidence** APIs. A
thin, fully typed wrapper over [`httpx`](https://www.python-httpx.org/) that
bakes in the three things every integration re-implements:

- **Auth** — your API key is attached as a Bearer token on every request.
- **Pagination** — generators walk the API's `limit`/`offset` pages for you.
- **Error handling** — HTTP 4xx/5xx map to typed exceptions, with first-class
  `429` / `Retry-After` handling and optional automatic backoff.

> The SDK is generated *from* the checked-in OpenAPI contract
> ([`docs/api/openapi.json`](../../docs/api/openapi.json)) — see
> [Versioning](#versioning) and [`scripts/gen_sdks.py`](../../scripts/gen_sdks.py).
> The Python client is hand-written (not codegen) because the contract uses
> FastAPI's module-namespaced model names, which most Python OpenAPI generators
> reject as "duplicate models"; a small typed client is more robust and has no
> codegen-version churn.

## Install

From this repo (until published to a registry — see [Publishing](#publishing)):

```bash
pip install ./sdks/python
# or, for development:
pip install -e ./sdks/python
```

Requires Python 3.10+. The only runtime dependency is `httpx`.

## Authenticate

The platform issues tenant-scoped API keys of the form `vk_live_...`
(VPLAT-529 / ADR-025). A tenant **admin** (or owner) mints one via
`POST /api/v2/api-keys` from an interactive session — the full secret is
returned **exactly once** at mint (only its SHA-256 hash is stored). Keys
carry **scopes** (`runs:read`, `runs:write`, `evidence:read`, `audit:read`,
plus coarse `read`/`write`) and act with the minting user's current role.
Revoke any time with `DELETE /api/v2/api-keys/{id}`.

Mint a key and pass it to the client. **Read the key from the environment —
never hard-code it.**

```python
import os
from velerian_sdk import VelerianClient

client = VelerianClient(
    api_key=os.environ["VELERIAN_API_KEY"],   # vk_live_...
    base_url=os.environ.get("VELERIAN_BASE_URL", "https://api.velerian.ai"),
)
```

The key is sent as `Authorization: Bearer <key>`. A missing/invalid/expired key
raises `VelerianAuthError` (401); a key lacking a required scope raises
`VelerianForbiddenError` (403).

## Quickstart — run a governed pack + fetch its evidence

```python
import os
from velerian_sdk import VelerianClient, VelerianError

with VelerianClient(
    api_key=os.environ["VELERIAN_API_KEY"],
    base_url=os.environ.get("VELERIAN_BASE_URL", "https://api.velerian.ai"),
) as client:
    # 1. Run a governed invocation of a DEPLOYED ABP (needs runs:write).
    result = client.invoke_abp(
        os.environ["VELERIAN_ABP_ID"],
        payload={"prompt": "Summarize the attached filing."},
    )
    print("outcome:", result.outcome, "allowed:", result.is_allowed)
    if result.requires_human:
        print("held for human approval:", result.approval_id)

    # 2. Fetch a presigned evidence export for that invocation (needs evidence:read).
    if result.evidence_pack_id:
        export = client.invocation_evidence(
            os.environ["VELERIAN_ABP_ID"], result.invocation_id
        )
        print("evidence download URL (expires", export.expires_at, "):")
        print(export.download_url)
```

See [`examples/run_governed_pack.py`](./examples/run_governed_pack.py) for a
runnable version with error handling.

## Pagination

List endpoints use `limit`/`offset`. The SDK gives you both a single-page
accessor and an auto-iterating generator:

```python
# One page:
page = client.list_audit_log(action="abp.invoke", limit=50)
print(len(page), "of", page.total or "?", "entries; next offset", page.offset + page.limit)

# Every entry, transparently across pages:
for entry in client.iter_audit_log(action="abp.invoke"):
    print(entry["created_at"], entry["action"])
```

For any list route the SDK does not wrap, use the generic paginator:

```python
for row in client.iter_paginated("approvals", page_size=100):
    ...
# Prefixed limit/offset params (e.g. the usage summary's evidence list):
for pack in client.iter_paginated(
    "usage/summary",
    limit_param="evidence_limit", offset_param="evidence_offset",
    items_key="evidence_packs",
):
    ...
```

## Error handling

Every non-2xx response raises a typed exception (all subclasses of
`VelerianError`):

| Exception                  | HTTP | Meaning                                    |
| -------------------------- | ---- | ------------------------------------------ |
| `VelerianAuthError`        | 401  | Missing/invalid/expired/revoked key        |
| `VelerianForbiddenError`   | 403  | Authenticated but missing a scope          |
| `VelerianNotFoundError`    | 404  | Resource not found / not visible           |
| `VelerianConflictError`    | 409  | State conflict (e.g. ABP not `deployed`)   |
| `VelerianValidationError`  | 422  | Request failed schema validation           |
| `VelerianRateLimitError`   | 429  | Rate limited (`.retry_after` in seconds)   |
| `VelerianServerError`      | 5xx  | Server-side error                          |
| `VelerianAPIError`         | other| Any other non-2xx (base of the above)      |

```python
from velerian_sdk import VelerianRateLimitError, VelerianForbiddenError

try:
    client.invoke_abp(abp_id, payload={...})
except VelerianForbiddenError as e:
    print("missing scope:", e.detail)
except VelerianRateLimitError as e:
    print("retry after", e.retry_after, "seconds")
```

### Automatic retries

By default the client retries `429` and `5xx` up to **3 times**, honoring the
server's `Retry-After` on `429` and using full-jitter exponential backoff
otherwise. Tune or disable it:

```python
client = VelerianClient(api_key=..., base_url=..., max_retries=0)  # never retry
```

When retries are exhausted the final typed exception is raised.

## Escape hatch

The typed methods cover the governed-run + evidence + usage + audit surface. For
any other route, `client.get(path)` / `client.post(path, json=...)` /
`client.request(...)` issue an authenticated, error-mapped, retried request and
return the raw `httpx.Response`:

```python
plans = client.get("plans").json()
```

## Versioning

The SDK version tracks the API contract it was built against. The API is
versioned by URL path (`/api/v2`, see
[`docs/api/versioning-and-deprecation.md`](../../docs/api/versioning-and-deprecation.md));
the SDK targets `v2`. Regenerate after a contract change with:

```bash
python scripts/gen_sdks.py
```

## Publishing

Building a wheel:

```bash
pip install build && python -m build ./sdks/python
```

Publishing to PyPI is a follow-on that requires registry credentials (gated on
account setup); the package metadata (`name = velerian-sdk`) is ready.
