Auditing Claude Enterprise: Shipping the Compliance API into Your SIEM

Auditing Claude Enterprise: Shipping the Compliance API into Your SIEM
In this post I show how to use Anthropic's Compliance API to stream Claude Enterprise audit events into your SIEM, and introduce claude-compliance-sdk, a Python SDK I built to make interacting with the API easier.

Why bother?

You don't need me to tell you that AI tools and agents have arrived in the enterprise in force. Security teams have so far been playing catch-up in companies where the edict from up high has become "AI is the prescribed remedy, regardless of the ailment". AI providers are also playing catch-up, adding security and compliance monitoring tools to their own products. Claude Enterprise is a platform where real work now gets done; code, internal documents, customer data are all handled (and, uniquely, created) in Claude. Security teams need the same audit trail for it that they already have for every other SaaS in the estate.

Anthropic's Compliance API closes that gap. It exposes a feed of audit events plus on-demand access to chats, messages, files and the static org data sat alongside them, enough to wire Claude into the same monitoring pipeline you already run for other SaaS platforms.

An SDK makes accessing the API easier. The full specification is in a PDF that appears after the Primary Owner enables the API in Organization settings β†’ Data and privacy, so without a wrapper the first step is reading the PDF and writing your own client. The SDK skips that, and the same calls drop straight into a SIEM-ready export script (examples later).

What you get

The Compliance API exposes data in four categories from Claude.ai and the Claude API.

The Activity Feed. A stream of audit events covering user actions: entries like api_key_created, compliance_api_accessed, claude_chat_created. Each has an id, a created_at timestamp, a type, the organization_id, and an actor block describing who did the thing. Anything outside the documented field set is preserved in an extra dict, so any future additions won't silently get dropped.

Chats and messages. The chats endpoint contains the prompts and responses from users' interactions with Claude. You list chats by user and date range, and for each chat you walk the messages cursor to pull the actual content.

Files and artefacts. Split into user-uploaded files (project_file) and assistant-generated artefacts. Both download through the same interface, with streaming variants for anything you don't want to load into memory. Useful for downstream DLP scanning, or for reconstructing what files users have attached to chats.

Org data. Organisations, users, groups, roles, permissions. Useful for enriching the activity feed at SIEM query time, e.g. "show me admin actions by users added to the admins group in the last thirty days".

What you don't get

The Compliance API does not cover everything in the Claude estate. Before we get into it, it's worth reviewing the parts that make up Claude Enterprise.

  • Claude.ai. The web, desktop and mobile chat interfaces. Where most users have their day-to-day Claude conversations.
  • Claude API. The programmatic interface, used by software engineers building Claude into their own products and backends.
  • Claude Code. A CLI tool for agentic coding, run from a developer's terminal with access to the local repo and shell.
  • Claude Cowork. A desktop application aimed at non-developers, automating work across local files and connected SaaS apps. Conversation history and connected-folder state live on the user's machine. This includes MCP integrations and the Tasks functionality.
  • Claude in Chrome. A browser agent that drives a Chrome session on the user's behalf. Beta.
  • Claude in Excel. A spreadsheet-side agent operating inside Excel workbooks. Beta.

Coverage matrix

The Compliance API provides full coverage of Claude.ai activities, and partial coverage of Claude API and Claude Code activities.

The table below has been reproduced from General Analysis' Claude Compliance API Guide, which I would recommend reading for a more in-depth explanation of telemetry gaps.

Layer Claude.ai Chat Claude API Claude Code Cowork Files Cowork Chrome Cowork MCP Cowork Tasks
Audit log export βœ… 🟑 🟑 ❌ ❌ ❌ ❌
Compliance API Β· events βœ… 🟑 🟑 ❌ ❌ ❌ ❌
Compliance API Β· content βœ… 🟑 ❌ ❌ ❌ ❌ ❌
OpenTelemetry Β· Code ❌ ❌ βœ… ❌ ❌ ❌ ❌
OpenTelemetry Β· Cowork ❌ ❌ ❌ βœ… 🟑 βœ… 🟑
On-device proxy / LLM gateway 🟑 🟑 βœ… ❌ βœ… 🟑 🟑

Legend: βœ… Covered Β· 🟑 Partial Β· ❌ Not covered

Cowork is outside the audit feed entirely. Conversations are stored on the user's machine. File reads, browser actions, MCP tool calls and scheduled tasks generate no centralised record.

Inference detail is absent. The feed records that a chat happened, and the chat endpoint gives you the message content, but there's no record of which model ran, which tools Claude called, or what any MCP server returned.

The SDK covers what the API covers. Everything outside that needs other tools. Filling this telemetry gap is out of scope here; I may revisit it in a future post.

claude-compliance-sdk

I've wrapped the Compliance API into a small Python library: claude-compliance-sdk. The SDK gives you typed dataclasses, handles cursor and offset pagination, and respects the server's rate limit with a client-side sliding-window limiter so you don't have to. Sync and async clients are available with the same method names.

You can install the library from PyPI:

pip install claude-compliance-sdk

The API uses two key types, and they're not interchangeable.

Key type Prefix Endpoints
Admin key sk-ant-admin01-... Activity Feed only
Compliance Access Key sk-ant-api01-... Chats, messages, files, projects, groups, users, roles, org data

Compliance Access Keys are created by the Primary Owner under Settings β†’ Data Management β†’ Compliance access keys, and the scopes assigned at creation time are fixed for the lifetime of the key. Available scopes:

Scope Unlocks
read:compliance_activities The Activity Feed. Every audit event in the organisation.
read:compliance_user_data Chats, messages, file metadata, project data, group members. The on-demand content side.
delete:compliance_user_data Deleting chats and user-uploaded files.
read:compliance_org_data Organisations, users, roles, permissions, groups.

For the walkthrough that follows, you'll want read:compliance_activities on the Admin key, and read:compliance_user_data and read:compliance_org_data on the Compliance Access Key.

Key issues will raise either InvalidAPIKeyError (key is wrong) or InsufficientScopeError (key is right but missing the scope the endpoint needs).

Authenticate the client by passing the key directly:

import os
from claude_compliance_sdk import ComplianceClient
 
client = ComplianceClient(api_key=os.environ["ANTHROPIC_COMPLIANCE_API_KEY"])

…or set ANTHROPIC_COMPLIANCE_API_KEY in the environment and let the client read it:

client = ComplianceClient()
Never store API keys in plaintext; fetch them from a secrets manager or encrypted local vault.

How it works

Here are some examples of how to use the SDK. Throughout these examples we'll use Westeros Inc, with jon.snow as the user and House Stark as the org.

Pulling activity events

The minimal "give me a window of audit events".

Scenario:

"Give me all events for chats created and API tokens generated on the 3rd June, return a full JSON object"
with ComplianceClient() as client:
    for activity in client.activities.iter(
        activity_types=["claude_chat_created", "api_key_created"],
        created_at_gte="2026-06-03T00:00:00Z",
        created_at_lt="2026-06-04T00:00:00Z",
    ):
        print(json.dumps(asdict(activity)))

Response (pretty-printed for readability):

{
    "id": "activity_01HQK2yYsT5Z3wVf8XbCn9pEz",
    "created_at": "2026-06-03T05:51:04.949824Z",
    "type": "claude_chat_created",
    "organization_id": "org_01HouseStark7p8m4nVjL2sR",
    "organization_uuid": "5a4f9c2d-7b91-4c30-9e87-3f6a2b8d4e15",
    "actor": {
        "email_address": "jon.snow@westeros.inc",
        "ip_address": "2001:db8:36fc:ec8::179:1da",
        "type": "user_actor",
        "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Claude/1.9659.2 Chrome/146.0.7680.216 Electron/41.6.1 Safari/537.36",
        "user_id": "user_01JonSnow8nXyR7Mh3KpQvLa"
    },
    "extra": {
        "claude_chat_id": "claude_chat_01WinterFell9KpM4nVjLqSx",
        "claude_project_id": null
    }
}

Activities serialise cleanly to one JSON object per line, the shape every SIEM HTTP collector wants. The extra dict holds any fields that differ by event type, or have been added in API responses.

Activity events with message content

Scenario:

"Find me the content in chats created by jon.snow on the 3rd June"
with ComplianceClient() as client:
    for chat in client.chats.iter(
        user_ids=["user_01JonSnow8nXyR7Mh3KpQvLa"],
        created_at_gte="2026-06-03T00:00:00Z",
        created_at_lt="2026-06-04T00:00:00Z",
    ):
        result = client.chats.get(chat.id)
        print(f"Chat: {chat.id}")
        print("---")
        for message in result.messages.data:
            print(f"{message.role}: {message.content}")

Response:

Chat: claude_chat_01WinterFell9KpM4nVjLqSx
---
user: Can you help me draft a phishing-awareness playbook for House Stark staff?
assistant: Happy to. Is the playbook for technical responders or general staff, and do you have a recent lure I should anchor the examples on?
user: General staff. The lure that keeps landing is "the Wall has been breached, click here to deploy reinforcements". Urgency plus a fake action button.
...
Chat: claude_chat_02WinterFell9KpM4nVjLqfx
---
user: What are the rules in Westeros about aunt marriage?
assistant: In Westeros there isn't a neatly codified marriage statute, but the customs are fairly consistent...

Oh Jon... A reminder that users may use AI chats for personal queries. Treat them with caution.

The chats endpoint requires a list of user_ids (a max of 10). The call count adds up fast. Listing a user's chats is one call; for each chat returned, you make another call to pull its messages. Long chats may need multiple cursor pages to walk through all their messages. This is reasonable for a targeted investigation, but painful as a permanent SIEM cadence pulling content for every chat every minute. In the repo, examples/ediscovery_export.py shows how to handle the messages cursor properly.

Downloading files and artefacts

Files live under projects. The projects resource exposes an attachments iterator that returns user uploads and assistant-generated artefacts side by side:

with ComplianceClient() as client:
    for attachment in client.projects.iter_attachments("claude_proj_winterfell"):
        if attachment.type != "project_file":
            continue  # skip assistant-generated artefacts and project docs
        client.files.download_to_file(attachment.id, f"./artefacts/{attachment.id}")

download_to_file streams to disk, ignoring the in-memory max_download_bytes cap, so a house-stark-payroll.xlsx upload comes down regardless of size. There's also download_stream for chunks and a plain download that returns bytes if you want them in memory.

Users, org, and membership

A static snapshot, useful as a join table on the SIEM.

Scenario:

Get me all organisation data, including users, roles, permissions and group membership.
with ComplianceClient() as client:
    for organisation in client.organizations.list():
        print("Organisation:")
        print(json.dumps(asdict(organisation), indent=4))
        print("Users:")
        for user in client.organizations.iter_users(org_uuid=organisation.uuid):
            print(json.dumps(asdict(user), indent=4))
        for role in client.roles.iter(org_uuid=organisation.uuid):
            print("Role:")
            print(json.dumps(asdict(role), indent=4))
            for permission in client.roles.iter_permissions(org_uuid=organisation.uuid, role_id=role.id):
                print("Permission:")
                print(json.dumps(asdict(permission), indent=4))
    for group in client.groups.iter():
        print("Group:")
        print(json.dumps(asdict(group), indent=4))
        for member in client.groups.iter_members(group_id=group.id):
            print("Group user:")
            print(json.dumps(asdict(member), indent=4))

Each iterator yields plain dataclasses you can json.dumps straight into NDJSON. I recommend exporting these daily rather than the per-minute cadence the activity feed runs on, as they change rarely.

Ingesting into a SIEM

The real value comes from feeding these into your SIEM. The dataclasses serialise to JSON, and every mainstream collector (Splunk HEC, Sentinel via Log Analytics, Elastic via Filebeat, Cribl, Sumo) will accept newline-delimited JSON without a wrapper. An example minimal puller that writes NDJSON to a file your collector tails:

import json
from pathlib import Path
from dataclasses import asdict
 
from claude_compliance_sdk import ComplianceClient
 
with ComplianceClient() as client, Path("claude-activities.ndjson").open("a") as out:
    for activity in client.activities.iter(
        created_at_gte="2026-06-03T00:00:00Z",
        created_at_lt="2026-06-04T00:00:00Z",
    ):
        out.write(json.dumps(asdict(activity)) + "\n")

The repo's examples/activity_audit.py is an expanded version of the above with error handling, actor and type filters.

Scheduling it

Cron, Lambda or a Cloud Function all work for running export scripts regularly. Pull a window with created_at_gte / created_at_lt, write the results to the collector, and persist the last successful created_at somewhere so the next run picks up cleanly.

Now what?

You can now ingest Claude Enterprise activity, chats with full message content, project files and membership snapshots into your SIEM as JSON. The interesting work starts on the SIEM side: detection rules, alerts on the obvious admin events, joins against the identity data you already index. I'll cover that in the next post in this series.

Repo: github.com/PaperMtn/claude-compliance-sdk
PyPI: pip install claude-compliance-sdk

Share this article