Metadata-Version: 2.3
Name: claude-compliance-sdk
Version: 0.3.0
Summary: Community Python SDK for the Anthropic Compliance API
License: GPL-3.0-or-later
Keywords: anthropic,claude,compliance,sdk,ediscovery,audit,dlp
Author: PaperMtn
Author-email: me@papermtn.co.uk
Requires-Python: >=3.11,<4.0
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Legal Industry
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Dist: httpx (>=0.27,<1.0)
Project-URL: Changelog, https://github.com/PaperMtn/claude-compliance-sdk/blob/main/CHANGELOG.md
Project-URL: Homepage, https://github.com/PaperMtn/claude-compliance-sdk
Project-URL: Issues, https://github.com/PaperMtn/claude-compliance-sdk/issues
Project-URL: Repository, https://github.com/PaperMtn/claude-compliance-sdk
Description-Content-Type: text/markdown

# claude-compliance-sdk

This is a community Python SDK for the **Anthropic Compliance API** — the API that lets you access Claude activity logs, chat data, and file content programmatically.

The Compliance API requires an Enterprise plan, and primary owners can enable it using the guide [here](https://support.claude.com/en/articles/13015708-access-the-compliance-api).

> **Unofficial.** This is a community-maintained project. It is not
> produced, endorsed, or supported by Anthropic.

📚 **[Read the documentation](https://papermtn.github.io/claude-compliance-sdk/)** — full API reference, generated from the source.

## Features

- Complete coverage of all Compliance API endpoints, including the Activity Feed, Chats, Messages, Files, Projects, Groups, Users, Roles, Permissions, Organisations, and session transcripts from Cowork and Claude Code.
- Full sync + async parity. Every resource method is available on both `ComplianceClient` and `AsyncComplianceClient` under the same name.
- Typed responses as plain dataclasses. Unknown response fields are preserved in an `extra: dict` so a future API revision adding a field cannot break the SDK.
- Built-in retry with exponential backoff that treats `Retry-After` as a floor, plus rate limiting driven by the server's own `anthropic-ratelimit-*` headers — the client waits for the stated reset instead of spending a request to discover a 429.
- Streamed downloads with a configurable memory ceiling — switch from eager bytes to `download_to_file()` or `download_stream()` for anything larger.
- Typed exception hierarchy. Every API error maps to a catchable class — `InvalidAPIKeyError`, `InsufficientScopeError`, `NotFoundError`, `ConflictError`, `RateLimitError`, and the rest.
- Tracks the hosted [Anthropic Compliance API docs](https://platform.claude.com/docs/en/manage-claude/compliance-api), snapshotted under `spec-snapshots/` so upstream changes are visible as a diff.

## Requirements
Python 3.11+.

## Install

Install from PyPI with pip:
```bash
pip install claude-compliance-sdk
```

Or install from source:
```bash
git clone https://github.com/PaperMtn/claude-compliance-sdk.git
cd claude-compliance-sdk
python -m pip install .
```

## Documentation

Full API reference docs are available at [papermtn.github.io/claude-compliance-sdk](https://papermtn.github.io/claude-compliance-sdk/).

## Quickstart

### Sync

```python
from claude_compliance_sdk import ComplianceClient

with ComplianceClient(api_key="sk-ant-api01-...") as client:
    for activity in client.activities.iter(
        activity_types=["claude_chat_created", "api_key_created"],
        limit=100,
    ):
        print(activity.created_at, activity.type, activity.id)
```

### Async

```python
import asyncio

from claude_compliance_sdk import AsyncComplianceClient


async def main() -> None:
    async with AsyncComplianceClient(api_key="sk-ant-api01-...") as client:
        async for activity in client.activities.iter(limit=100):
            print(activity.created_at, activity.type)


asyncio.run(main())
```

Every resource group on both clients exposes the same method names —
swap `ComplianceClient` for `AsyncComplianceClient`, sprinkle `await`,
done.

## Authentication

Two key types reach the Compliance API, and which you need depends on
what you are querying.

| Key type | Created in | Reaches |
| --- | --- | --- |
| **Compliance Access Key** (`sk-ant-api01-...`) | claude.ai → Organization settings → API | Every endpoint |
| **Admin API key** (`sk-ant-admin01-...`) | Claude Console → Settings → Admin keys | The Activity Feed **only** — everything else returns 403 |

A Compliance Access Key is created by a primary owner or organisation
owner. A primary owner's key can cover every organisation under the
parent; an organisation owner's key covers their own organisation only.
Admin API keys carry `read:compliance_activities` only if the
Compliance API was already enabled for the organisation when the key
was created, and cannot be granted any other Compliance scope.

Scopes are chosen at creation and are **immutable** — to change them,
create a new key and delete the old one.

| Scope | Unlocks |
| --- | --- |
| `read:compliance_activities` | Activity Feed (`activities`) |
| `read:compliance_user_data` | Chats, messages, files, projects, **session transcripts**, organisation users, group members |
| `delete:compliance_user_data` | Deleting chats, files, and projects |
| `read:compliance_org_data` | Organisations, roles, permissions, groups, and effective organisation settings |

Pick the smallest set that works. An audit pipeline that only reads the
feed needs `read:compliance_activities`. If your workflow both reads
and deletes, use **two keys** so a leaked read key cannot delete data.

> A key with `read:compliance_user_data` can read every chat, file,
> project, and session transcript in every linked organisation.
> Treat these keys like production database credentials.

The separate `read:compliance_org_settings` scope was **retired on
2026-06-30**. A key carrying only that scope now returns 403 from the
settings endpoint; `read:compliance_org_data` replaces it.

Authentication and authorisation failures surface as typed exceptions: a
`401` (invalid or revoked key) becomes `InvalidAPIKeyError`, and a `403`
becomes `PermissionDeniedError` — refined to `InsufficientScopeError`
when the key is valid but missing the scope the endpoint needs. The
403 message names both what the key carries and what the endpoint
wanted, and is available on `error_message`.

Pass the key when constructing the client:

```python
import os

client = ComplianceClient(api_key=os.environ["ANTHROPIC_COMPLIANCE_ACCESS_KEY"])
```

Or set the environment variable and let the client read it:

```bash
export ANTHROPIC_COMPLIANCE_ACCESS_KEY=sk-ant-api01-...
```

```python
client = ComplianceClient()
```

The legacy `ANTHROPIC_COMPLIANCE_API_KEY` name this SDK shipped with is
still read as a fallback, so existing deployments keep working.

## Pagination

Two types of pagination are used:

- **Cursor-paginated** — Activity Feed, Chats, Messages. Pages carry
  `first_id` / `last_id` / `has_more`.
- **Offset-paginated** — everything else. Pages carry `has_more` and
  an opaque `next_page` token.

Every paginated resource exposes both `.list()` (one page at a time) and `.iter()` (auto-paginate — yields items one
at a time across all pages) functions.

```python
# .list() — explicit page boundaries
page = client.projects.list(limit=20)
for project in page.data:
    print(project.id)
if page.has_more:
    next_page = client.projects.list(limit=20, page=page.next_page)

# .iter() — auto-paginate
for project in client.projects.iter(organization_ids=["org_abc123"]):
    print(project.id)
```

Cursor resources are identical in shape; the page contains `last_id`
and you pass it back as `after_id`.

## Session transcripts

Transcripts of the sessions your users run in Claude apps — Cowork,
Claude Code, Claude Science, and Claude for Microsoft 365 — come from
two resource groups, split by **where the session ran**:

| Resource group | Covers | ID prefix |
| --- | --- | --- |
| `client.local_sessions` | Cowork in Claude Desktop, Claude Code (terminal, desktop, IDE), Claude Science, Claude for Microsoft 365 — all on the user's own machine | `clls_` |
| `client.remote_sessions` | Cowork started on claude.ai web or mobile, running in Anthropic-managed cloud environments | `cse_` |

If you are looking for Claude Code usage, it is `local_sessions`.

```python
with ComplianceClient() as client:
    for session in client.local_sessions.iter(created_at_gte="2026-07-01T00:00:00Z"):
        if session.product_surface != "claude_code":
            continue
        for message in client.local_sessions.iter_messages(session.id):
            print(session.id, message.role, message.content)
```

Both groups are read-only — sessions cannot be deleted through the
Compliance API. Transcript content blocks (`text`, `tool_use`,
`tool_result`) are returned as plain dicts so block types that have not
shipped yet pass through rather than breaking parsing. Note that a
`tool_use` block's `input` is a JSON-encoded *string*, and a truncated
one is not valid JSON — raise `tool_use_input_max_bytes` (or pass `-1`
for the server maximum) if you need to parse it.

Two errors are worth catching by name. `LocalSessionsUnavailableError`
is a 404 meaning the endpoints are off for your parent organisation, not
that a session is gone — keep your queued IDs and retry later.
`LocalSessionsRetentionUnavailableError` is a 503 that is *not*
transient; skip that session and come back to it on a later run.

## Downloads

Three resource groups expose binary content — user files, assistant-
generated files, and artifacts. Each provides the same three download
methods:

```python
# Into memory, bounded by max_download_bytes (default 100 MiB).
data: bytes = client.files.download("claude_file_xyz789")

# Streamed to disk — unbounded.
client.files.download_to_file("claude_file_xyz789", "/tmp/report.pdf")

# Caller-managed streaming — yields bytes; connection closes when the
# iterator is exhausted or garbage-collected.
for chunk in client.files.download_stream("claude_file_xyz789"):
    handle(chunk)
```

The `max_download_bytes` cap protects memory on the memory path only.
`download_to_file` and `download_stream` ignore the cap and always stream, so you can use them for anything larger than the cap.

```python
client = ComplianceClient(max_download_bytes=10 * 1024 * 1024)  # 10 MiB cap

try:
    data = client.files.download("claude_file_big")
except FileTooLargeError as exc:
    print(f"{exc.size_bytes} bytes > {exc.max_bytes} cap — switching to stream")
    client.files.download_to_file("claude_file_big", "big.bin")
```

User files are deletable (`.delete()`). Generated files and artifacts
are not.

## Rate limits

The API allows 600 requests per minute per **parent organisation** —
one budget shared across every key beneath it and every
`/v1/compliance/*` endpoint. The SDK reads the server's
`anthropic-ratelimit-*` headers on every response and waits for the
stated reset once the budget is spent.

```python
client.activities.list(limit=100)

status = client.rate_limit_status  # None until the first response
if status and status.remaining is not None and status.remaining < 50:
    ...  # Slow your workers: the budget is shared with other consumers.
```

`rate_limit_rpm` caps how fast this client issues requests. `0`
disables that local window; the server-reported budget is still
honoured, because it is not something a caller can opt out of.

The remote session endpoints carry a second budget on top of the shared
one, so a 429 there can arrive well below 600 rpm.

## Configuration

`ComplianceClient` and `AsyncComplianceClient` accept the same kwargs:

| Kwarg | Default | What it does |
| --- | --- | --- |
| `api_key` | env `ANTHROPIC_COMPLIANCE_ACCESS_KEY`, then `ANTHROPIC_COMPLIANCE_API_KEY` | Compliance Access Key or Admin API key. |
| `base_url` | `https://api.anthropic.com` | Override for testing. |
| `timeout` | `30.0` | Per-request timeout, seconds. |
| `max_download_bytes` | `100 * 1024 * 1024` | Eager-download cap. |
| `max_retries` | `3` | Retry attempts on 429/5xx and connect errors. `0` disables. |
| `rate_limit_rpm` | `600` | Local burst smoothing for this client. `0` disables the local window. See the note below. |
| `anthropic_version` | `"2023-06-01"` | Sent as the `anthropic-version` header on every request. `None` suppresses it. |

> **On `rate_limit_rpm`:** this caps how fast a single client issues
> requests, which matters for a cold burst before the first response
> arrives. Once responses start coming back, the SDK throttles on the
> server's own `anthropic-ratelimit-*` headers instead, so you no
> longer need to divide 600 by your worker count. Setting `0` disables
> the local window only — the shared server budget is still honoured.
> See [Rate limits](#rate-limits).

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for the dev setup, branch model,
coding conventions, and PR checklist. Architecture decisions worth
preserving live as numbered ADRs under [`adr/`](adr/).

## License

GPL-3.0-or-later. See [LICENSE](LICENSE).

