Skip to main content

Compliance API Documentation

Welcome to the Compliance API documentation. These endpoints connect a ChatGPT Enterprise workspace to Enkrypt, then keep reading its compliance logs and scanning every message through one of your saved guardrails.

Purpose

Your workspace already records what people send to and receive from the assistant. A compliance scan turns that record into something you can audit: it polls the provider’s compliance export on a fixed interval, runs every message through the guardrail you name, and files the result — so you can answer “was anything sensitive pasted into ChatGPT last month” without asking anyone to forward a screenshot. A scan is a long-lived object, not a one-off job. You create it once, point it at a workspace and a start time, and it keeps up from there. cursor_end_time is where the provider’s own clock got to, and it moves forward on its own as the scan reads. Message text is never stored. The findings, the counts and the coverage record are; the text of a message is fetched live from the provider when you deliberately ask for it with Get Compliance Message, and only while the provider still holds it.

Who can call these APIs

Every endpoint on this page — reads as well as writes — requires the governance officer role.
  • The role is granted per project, inside an organization, by an organization administrator.
  • The organization owner is not exempt. An owner who has not been granted the role gets 403 from every endpoint here, exactly like any other member. Compliance is the one part of the platform where owning the account does not by itself let you read it.
  • Individual accounts cannot hold the role, because there is no organization to grant it in. An individual account gets 403 with a message saying so. Compliance is therefore an organization-only feature today.
If you get a 403, ask an administrator of your organization to grant you the governance officer role on the project your API key belongs to — the role is the only thing that changes the answer.

Projects

A scan lives in a project, and the project comes from your API key, not from the request: a project key files its scans in that project, any other key uses default. Scan names are unique per project, so the same name may be used once in each.

Offered APIs

  • test-connection: Probe a compliance key and a workspace before any scan exists. Says whether the key works, whether the workspace was found, which event types that workspace can actually serve, and — if you propose a start time — how much history a backfill from there would pull. Stores nothing.
  • add-scan: Create a scan. Starts polling immediately unless you create it with status: "paused".
  • get-scan: One scan’s record, plus lag_s — how far behind the provider’s clock the scan currently is.
  • modify-scan: Change a scan’s name, guardrail, event types, cursor, cadence, status or stored key.
  • delete-scan: Stop the scan and delete its record, along with its log-file history.
  • list-scans: Every scan in your project, paginated, optionally filtered by status.
  • start-scan / pause-scan: Resume or suspend polling without editing anything else.
  • list-log-files: The coverage record — which provider export files this scan opened and closed, how many messages each held and how many were flagged.
  • get-message: One message’s text, fetched live from the provider.

Connecting a workspace

Two values identify the source, and one of them is easy to get wrong.
  • compliance_api_key is the compliance API key issued by the provider for that workspace. Enkrypt stores it encrypted, and no endpoint ever returns it — not in plaintext and not masked. Replace it by sending a new one to Modify Compliance Scan.
  • workspace_id is the ChatGPT workspace UUID. It is not an OpenAI organization id. An organization id has the right shape, so it is accepted, and then every poll comes back empty forever with nothing to explain why. If a scan is finding nothing at all, check this value first.
Run Test Compliance Connection before creating anything:
A rejected key is not an HTTP error: the call returns 200 with key_valid: false. Read the fields, not the status code. It also answers the two questions that are expensive to get wrong later:
  • Which event types this workspace serves. Each entry in event_types comes back with supported: true or false. Selecting a service the workspace will never serve produces a scan that stays empty with no explanation.
  • How big the backfill is. proposed_cursor makes the response carry a backfill estimate — files and bytes since that time. Volume varies enormously between workspaces, so the same 30-day start that seeds one workspace in minutes pulls millions of messages in another. Quote the estimate before committing to a start time.
The probe stores nothing, so Add Compliance Scan has to carry the key again.

Creating a scan

  • guardrails_name is one of your saved guardrails — the ones Add Guardrail creates. Every message the scan reads is evaluated against it, so it decides what “flagged” means for this scan. The name is not checked against your saved guardrails when the scan is created, so a typo shows up later as a scan that runs and flags nothing.
  • cursor_end_time is where reading starts. It is the most consequential field on the request: it sets how much history gets pulled.
  • event_types must be non-empty, and every value must be one the workspace serves.
  • provider and workspace_id are immutable. Sending either to Modify Compliance Scan is a 400 — a scan whose workspace changed would carry a cursor and a history belonging to something else. Delete it and create a new one instead.
Everything else has a default: status is active, list_interval_s is 60 seconds and pending_cap is 5000 queued events before the poller backs off.

Addressing a scan

Every endpoint except Add Compliance Scan, List Compliance Scans and Test Compliance Connection addresses one scan by name, in the X-Enkrypt-Scan header:
Omitting the header is a 400; a name that is not yours is a 404. The internal scan_id is returned on the record, but it is never something you have to send. Names may contain letters, numbers, spaces and _ & - . /, must start and end with a letter or a number, and are at most 64 characters. Renaming is allowed — send scan_name to Modify Compliance Scan — and the new name is what X-Enkrypt-Scan must carry afterwards.

Status, and the one value you cannot set

needs_reconnect is not a value you may send — Add and Modify accept only the first three. It is how a scan tells you its credential stopped working. Recover by sending a fresh compliance_api_key to Modify Compliance Scan, or by calling Start Compliance Scan once the key is replaced; either one re-resolves the key. You can list exactly the scans in that state:

Is it keeping up?

Get Compliance Scan answers this with three fields:
  • cursor_end_time — how far through the provider’s timeline the scan has read.
  • lag_s — seconds between that cursor and now. A backfill starts with a very large value and works it down; a caught-up scan sits near its polling interval.
  • last_polled_at — set on every successful poll, whether or not anything new came back. This is what separates a quiet workspace (polling, nothing to read) from a stopped scan (not polling at all). A lag_s that keeps growing while last_polled_at keeps moving means the workspace is busier than the scan can drain; a last_polled_at that has stopped moving means something else.
List Compliance Log Files is the coverage record behind those numbers: one row per provider export file the scan opened, with messages_count and flagged_count once the file is closed. A file marked skipped aged out of the provider’s 30-day retention window before it could be downloaded.

Reading a message

This is the one endpoint that goes back to the provider for content, because the text was never stored. Serving it means re-downloading the entire export file the message came from — up to 15 MB for one message — out of the same rate-limited budget the scan itself uses. Put it behind a deliberate click. Never call it from a list view, a hover card or a prefetch: a burst of calls starves the scan that is reading new messages. 410 is not a failure. It means the file has aged out of the provider’s 30-day retention window, which is the normal steady state for anything older than a month. The record’s content_expires_at says when that will happen.

Deleting a scan

Delete Compliance Scan stops the scan first and deletes the record only if the stop succeeded — if the scanner cannot be reached, the call fails and the scan is left intact rather than being orphaned into a workspace it keeps polling. The scan’s log-file history is deleted with it, so export the coverage record first if you need to keep it.

Errors you will actually see

A write that succeeded in the database but could not reach the scanning service comes back as a normal success carrying a warning field — the record is saved and the scanner picks it up on its next reconcile. Check for warning rather than assuming a 200 means the timer reloaded.

Python SDK

The Python SDK gains a ComplianceClient in version 1.0.42, with one method per endpoint on this page: test_connection, add_scan, get_scan, modify_scan, delete_scan, list_scans, start_scan, pause_scan, list_log_files and get_message.