Metadata-Version: 2.4
Name: keel-sdk
Version: 0.1.0
Summary: Minimal Python SDK for Keel
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0.0; extra == "dev"
Dynamic: license-file

# keel-sdk

Python SDK for the [Keel](https://keel.so) AI governance API.

Keel lets you issue permits before AI calls, enforce policies, track usage, and audit decisions — across any provider.

## Install

```bash
pip install keel-sdk
```

## Setup

```python
from keel_sdk import KeelClient

client = KeelClient(
    base_url="https://api.keel.so",
    api_key="keel_sk_...",
)
```

## Permits

Request a permit before making an AI call:

```python
import uuid
from keel_sdk import KeelClient

client = KeelClient(base_url="https://api.keel.so", api_key="keel_sk_...")

permit = client.permits.create({
    "project_id": "proj_123",
    "idempotency_key": str(uuid.uuid4()),
    "subject": {"type": "user", "id": "usr_123"},
    "action": {"name": "ai.generate"},
    "resource": {
        "type": "request",
        "id": "req_123",
        "attributes": {
            "provider": "openai",
            "model": "gpt-4o-mini",
            "estimated_input_tokens": 200,
            "estimated_output_tokens": 500,
        },
    },
})

if permit["decision"] == "allow":
    # proceed with AI call
    pass
```

Async variant:

```python
permit = await client.permits.create_async({...})
```

### Dry run

```python
result = client.permits.dry_run(permit_request)
```

### List and get

```python
permits = client.permits.list(project_id="proj_123", limit=50)
permit = client.permits.get("permit_id")
```

### Report usage

```python
client.permits.report_usage("permit_id", {
    "input_tokens": 180,
    "output_tokens": 420,
})
```

### Attestation, evidence, lineage

```python
client.permits.attest("permit_id", {"outcome": "success"})
client.permits.add_evidence("permit_id", {"label": "response_hash", "value": "abc123"})
evidence = client.permits.list_evidence("permit_id")
lineage = client.permits.lineage("permit_id")
bundle = client.permits.bundle("permit_id")
```

## Executions

Run a model synchronously:

```python
result = client.executions.create({
    "provider": "openai",
    "model": "gpt-4o-mini",
    "messages": [{"role": "user", "content": "Summarize this document."}],
    "permit_id": permit["id"],
})
```

Stream tokens as they arrive:

```python
for event in client.executions.stream({
    "provider": "openai",
    "model": "gpt-4o-mini",
    "messages": [{"role": "user", "content": "Write a poem."}],
    "permit_id": permit["id"],
}):
    if event.get("type") == "content_delta":
        print(event["delta"], end="", flush=True)
    if event.get("type") == "done":
        print()
```

Async streaming:

```python
async for event in client.executions.stream_async({...}):
    ...
```

## Execute (unified)

```python
result = client.execute.run({
    "model": "gpt-4o-mini",
    "input": "Translate to Spanish: Hello world",
    "provider": "openai",
})
```

## Proxy

Pass requests through to providers with Keel governance applied:

```python
response = client.proxy.openai({
    "model": "gpt-4o-mini",
    "messages": [{"role": "user", "content": "Hello"}],
})

# Also: client.proxy.anthropic(), .google(), .xai(), .meta()
```

## Jobs

Submit async jobs and poll for results:

```python
job = client.jobs.create({
    "provider": "openai",
    "model": "gpt-4o-mini",
    "messages": [{"role": "user", "content": "Analyze this dataset."}],
})

status = client.jobs.get(job["job_id"])
# status["status"]: "pending" | "running" | "completed" | "failed"
```

## API Keys

```python
key = client.api_keys.create()
keys = client.api_keys.list()
single = client.api_keys.get(key["id"])
client.api_keys.revoke(key["id"])
```

## Request Timeline

```python
timeline = client.requests.timeline("request_id")
```

## Error Handling

```python
from keel_sdk import KeelClient, KeelError

try:
    client.permits.create(request)
except KeelError as e:
    print(e.status)   # HTTP status code
    print(e.code)     # e.g. "permit_denied"
    print(e.message)  # human-readable message
    print(e.field)    # field that caused the error, if any
```

## Context Manager

```python
with KeelClient(base_url="...", api_key="...") as client:
    permit = client.permits.create({...})

# Async
async with KeelClient(base_url="...", api_key="...") as client:
    permit = await client.permits.create_async({...})
```

## Freshness Headers

For replay-protected endpoints:

```python
client = KeelClient(
    base_url="https://api.keel.so",
    api_key="keel_sk_...",
    request_freshness=True,
)
```

This adds `X-Keel-Timestamp` and `X-Keel-Nonce` to every request.

## License

MIT
