# Get Activity Source: https://docs.zerodrift.com/api-reference/activities/get-activity GET /api/activities/{activity_id} Get one Command activity event with timeline and rule detail. Return the full activity timeline and per-rule detail for one Command activity event. Cross-customer access returns `404`. ## Path Parameters Activity identifier from [List Activity](/api-reference/activities/list-activities) ## Response Includes all list-summary fields, plus: Originating API endpoint when available Request correlation ID when available Full rule detail including `snippet`, `suggested_text`, and `fix_note` Ordered steps with `step` and `at` (ISO 8601) ## Errors | Status | Meaning | | ------ | ------------------------------------------ | | `404` | Not found, including cross-customer access | # List Activity Source: https://docs.zerodrift.com/api-reference/activities/list-activities GET /api/activities List Command activity for your account. Return a newest-first, eventually-consistent activity feed for your account. Date ranges are capped at 30 days. ## Query Parameters Start of the window (ISO 8601 date-time) End of the window (ISO 8601 date-time) Filter by status. Repeatable. Values: `auto_fixed`, `blocked`, `warned`, `passed` Filter by agent ID. Repeatable. Filter by source. Repeatable. Filter by rule ID. Repeatable. Free-text search Opaque pagination cursor from a previous response Page size (1–100) ## Response Activity summaries Activity identifier ISO 8601 timestamp Event label `auto_fixed`, `blocked`, `warned`, or `passed` Originating source LLM provider when applicable Model name when applicable Agent `{ id, name }` Rule summaries with `id`, `name`, `severity`, `type`, and `policy` Total rules associated with the activity Number of rules omitted from the summary payload Cursor for the next page, or `null` when finished Pagination metadata Total pages for the current query Current 1-based page ## Errors | Status | Meaning | | ------ | ------------------------------------ | | `400` | Invalid date range (maximum 30 days) | | `401` | Invalid API key | # Activate Rules Source: https://docs.zerodrift.com/api-reference/custom-policies/activate-rules POST /api/policies/import/{import_id}/activate Activate extracted rules from a policy import so they run during validation Activate rules extracted from a policy import. Once activated, these rules will be enforced during document validation for your account. You can activate all rules or a specific subset. This is also the activation step for a document synced from Notion, Linear, Confluence, or Google Drive. After Command shows the import as pending review, call this endpoint with that `import_id`. Re-syncing the same source document refreshes the existing policy rather than creating a second one — see [MCP Connectors](/connecting-mcp). Activated rules apply **automatically** on every validation made with your API key — there is no per-request flag to select them, and `document_category` / `document_metadata` do not control them (those only select ZeroDrift's built-in default rules). Use [Deactivate Rules](/api-reference/custom-policies/deactivate-rules) to stop a rule from running. After activation, you can [train a policy-specific adapter](/training-studio) for this import. ## Path Parameters The import ID to activate rules from ## Request Body Optional list of specific rule IDs to activate. If omitted, all extracted rules are activated. Set to `true` to overwrite rules that have already been activated from a previous import. Defaults to `false`. When `overwrite` is `false` (default), if a rule ID already exists the request stops and returns a `409` conflict response. The 409 error body includes an error message that identifies the conflicting rule ID and indicates which rules were successfully activated before the collision. Use `overwrite: true` to replace existing rules unconditionally. ## Response Import identifier Updated import status: `activated` Number of rules successfully activated List of activated rule IDs (prefixed with `cust__`) For `409` responses, the rule ID that caused the conflict. ```json 200 Activated theme={null} { "import_id": "550e8400-e29b-41d4-a716-446655440000", "status": "activated", "activated_count": 5, "activated_rule_ids": [ "cust_abc123_no_misleading_claims", "cust_abc123_benchmark_comparison_required", "cust_abc123_risk_disclosure_present", "cust_abc123_fee_transparency", "cust_abc123_past_performance_disclaimer" ] } ``` ```json 400 Bad Request theme={null} { "error": "No matching rule_ids found in import" } ``` ```json 409 Rule ID Conflict theme={null} { "error": "Rule 'cust_abc123_no_misleading_claims' was created by a concurrent request", "conflicting_rule_id": "cust_abc123_no_misleading_claims", "activated_rule_ids": [ "cust_abc123_benchmark_comparison_required" ] } ``` ## Example Two request shapes: activate every extracted rule, or activate a subset with `rule_ids` and `overwrite`. ```bash cURL (activate all) theme={null} curl -X POST "https://api.zerodrift.ai/api/policies/import/550e8400-e29b-41d4-a716-446655440000/activate" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" ``` ```bash cURL (activate subset) theme={null} curl -X POST "https://api.zerodrift.ai/api/policies/import/550e8400-e29b-41d4-a716-446655440000/activate" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "rule_ids": ["no_misleading_claims", "risk_disclosure_present"], "overwrite": true }' ``` ```python Python theme={null} import requests API_KEY = "YOUR_API_KEY" API_BASE = "https://api.zerodrift.ai" IMPORT_ID = "550e8400-e29b-41d4-a716-446655440000" # Activate all rules response = requests.post( f"{API_BASE}/api/policies/import/{IMPORT_ID}/activate", headers={ "x-api-key": API_KEY, "Content-Type": "application/json" } ) result = response.json() print(f"Activated {result['activated_count']} rules") for rule_id in result["activated_rule_ids"]: print(f" - {rule_id}") ``` ```javascript JavaScript theme={null} const axios = require('axios'); const API_KEY = 'YOUR_API_KEY'; const API_BASE = 'https://api.zerodrift.ai'; const IMPORT_ID = '550e8400-e29b-41d4-a716-446655440000'; // Activate specific rules const response = await axios.post( `${API_BASE}/api/policies/import/${IMPORT_ID}/activate`, { rule_ids: ['no_misleading_claims', 'risk_disclosure_present'], overwrite: true }, { headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' } } ); console.log(`Activated ${response.data.activated_count} rules`); response.data.activated_rule_ids.forEach(id => console.log(` - ${id}`)); ``` # Create Custom Rule Source: https://docs.zerodrift.com/api-reference/custom-policies/create-rule POST /api/policies/rules Create a customer-owned AI_SIGNAL, REGEX, or REQUIRE_NEAR rule Create a custom rule that belongs to your account. Supports `AI_SIGNAL`, `REGEX`, and `REQUIRE_NEAR` rule types. Requires a full-access API key. Created rules start active and run on subsequent validations for your API key. ## Request Body The body is a discriminated union on `type`. Shared fields: Human-readable rule name `AI_SIGNAL`, `REGEX`, or `REQUIRE_NEAR` `fix` or `flag` `do_not_send` or `send_with_caution` Editable suggested replacement or warning guidance. Omit to allow warning fallback; `null` clears and suppresses warning fallback. Optional remediation guidance ### AI\_SIGNAL fields AI instruction for the signal Keyword gate (at least one string) `sentence`, `paragraph`, or `document` ### REGEX fields Raw Python regex (max 512 chars) ### REQUIRE\_NEAR fields Raw Python regex for the anchor pattern (max 512 chars) Raw Python regex patterns checked near the anchor Character distance around the anchor `before`, `after`, or `either` Case-insensitive matching `document` or `page` Minimum near-pattern hits required ## Response Returns the created custom rule (`CustomRuleResponse`), including `rule_id`, `source: "custom"`, `active`, timestamps, and type-specific fields. ## Errors | Status | Meaning | | ------ | ---------------------------- | | `400` | Invalid custom rule payload | | `403` | Full-access API key required | ## Example Two request bodies for the same endpoint: a `REGEX` rule and an `AI_SIGNAL` rule. ```bash cURL (REGEX) theme={null} curl -X POST "https://api.zerodrift.ai/api/policies/rules" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Block SSN patterns", "type": "REGEX", "action": "flag", "severity": "do_not_send", "regex_pattern": "\\b\\d{3}-\\d{2}-\\d{4}\\b" }' ``` ```bash cURL (AI_SIGNAL) theme={null} curl -X POST "https://api.zerodrift.ai/api/policies/rules" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "No guaranteed returns", "type": "AI_SIGNAL", "action": "fix", "severity": "do_not_send", "prompt": "Flag language that guarantees investment returns.", "keywords": ["guarantee", "guaranteed returns"], "window": "sentence" }' ``` # Deactivate Rules Source: https://docs.zerodrift.com/api-reference/custom-policies/deactivate-rules POST /api/policies/rules/deactivate Deactivate rules by rule pack, import, scenario, or specific IDs Deactivate rules so they are skipped during validation. Custom rules are soft-deactivated (preserved for audit trail). Default rules are stored as deactivated on your customer record. ## Request Body Deactivate all rules in a rule pack (e.g., `sec_finra_comms_v2`) Deactivate all custom rules from this import Deactivate all default rules in a scenario (e.g., `scenario_email_general`) Array of rule ID strings to deactivate (custom or default) At least one field is required. Multiple fields can be combined in a single request. ## Response Number of custom rules deactivated List of deactivated custom rule IDs Number of default rules deactivated List of deactivated default rule IDs List of error messages for rules that could not be deactivated. Empty array when no errors. ```json 200 OK theme={null} { "deactivated_custom_count": 2, "deactivated_custom_rule_ids": [ "cust_abc123_require_risk_disclosure", "cust_abc123_past_performance_disclaimer" ], "deactivated_default_count": 47, "deactivated_default_rule_ids": [ "signal_mnpi_leak", "signal_promissory_tone" ], "errors": [] } ``` ## Example Three ways to select what to deactivate on the same endpoint: by `rule_pack_id`, by `import_id`, or by specific `rule_ids`. ```bash cURL — by rule pack theme={null} curl -X POST "https://api.zerodrift.ai/api/policies/rules/deactivate" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"rule_pack_id": "sec_finra_comms_v2"}' ``` ```bash cURL — by import theme={null} curl -X POST "https://api.zerodrift.ai/api/policies/rules/deactivate" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"import_id": "550e8400-e29b-41d4-a716-446655440000"}' ``` ```bash cURL — by specific rules theme={null} curl -X POST "https://api.zerodrift.ai/api/policies/rules/deactivate" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"rule_ids": ["signal_mnpi_leak", "cust_abc123_rule1"]}' ``` # Edit Imported Rule Source: https://docs.zerodrift.com/api-reference/custom-policies/edit-imported-rule PATCH /api/policies/import/{import_id}/rules/{rule_id} Partially edit a pending imported rule before activation Partially edit `prompt`, remediation guidance (`fix_note`), or extraction `confidence` for one rule while its customer-owned import is pending review. Requires a full-access API key. The import must still be in a pending-review state (or equivalent pending status that allows edit); concurrent changes return `409`. ## Path Parameters Policy import identifier Extracted rule ID within the import (unprefixed) ## Request Body At least one field is required. Explicit `null` clears `prompt` or `fix_note`. `prompt` is accepted only for `AI_SIGNAL` rules. AI instruction for `AI_SIGNAL` rules. Nullable to clear. Remediation guidance. Nullable to clear. Extraction confidence from 0 through 1 ## Response Returns the updated extracted rule object (same shape as entries under [Get Import Details](/api-reference/custom-policies/get-import-details)). ## Errors | Status | Meaning | | ------ | ----------------------------------------------------- | | `400` | Invalid imported rule update | | `403` | Full-access API key required | | `404` | Import or imported rule not found | | `409` | Import is not pending review, or changed concurrently | # Edit Custom Rule Source: https://docs.zerodrift.com/api-reference/custom-policies/edit-rule PATCH /api/policies/rules/{rule_id} Partially update an existing customer-owned custom rule Partially update an existing customer-owned `AI_SIGNAL`, `REGEX`, or `REQUIRE_NEAR` rule. Rule type and ownership metadata are immutable. Active status is changed via [Activate Rules](/api-reference/custom-policies/activate-rules) / [Deactivate Rules](/api-reference/custom-policies/deactivate-rules), not this endpoint. Requires a full-access API key. Supply only fields applicable to the stored rule type. ## Path Parameters Custom rule identifier ## Request Body At least one field is required. Common editable fields: Human-readable rule name `fix` or `flag` `do_not_send` or `send_with_caution` Editable suggestion. Omit to preserve; `null` clears and suppresses warning fallback. Remediation guidance. Nullable to clear. Type-specific fields match [Create Custom Rule](/api-reference/custom-policies/create-rule) (`prompt` / `keywords` / `window` for `AI_SIGNAL`, `regex_pattern` for `REGEX`, `anchor` / `near` / distance options for `REQUIRE_NEAR`). ## Response Returns the updated custom rule definition (same shape as [Get Custom Rule](/api-reference/custom-policies/get-rule)). ## Errors | Status | Meaning | | ------ | ----------------------------- | | `400` | Invalid custom rule update | | `403` | Full-access API key required | | `404` | Custom rule not found | | `409` | Concurrent custom rule update | # Get Import Details Source: https://docs.zerodrift.com/api-reference/custom-policies/get-import-details GET /api/policies/import/{import_id} Retrieve details and extracted rules for a specific policy import Retrieve the full details of a policy import, including all extracted rules and their properties. Use this endpoint to poll for results after submitting an import, and to review rules before activation. Extraction runs in the background. Small documents are usually `pending_review` within seconds; large documents (hundreds of pages) are extracted in chunks and can stay in `processing` for several minutes. Poll until the status is no longer `processing`. ## Path Parameters The import ID returned from the import endpoint ## Response Unique import identifier Import status: `processing`, `pending_review`, `no_rules_found`, `failed`, `activated`, or `partially_activated` Number of extracted rules (present once extraction completes) Error details when status is `failed` ISO 8601 timestamp of the import Full list of extracted rules with details Rule identifier Rule type: `AI_SIGNAL`, `REGEX`, or `REQUIRE_NEAR` Human-readable rule name Suggested action: `flag` (warn only) or `fix` (suggest replacement text) Rule severity: `low`, `medium`, or `high` AI confidence score (0-1) Suggested replacement or guidance text (if applicable) ```json 200 Success theme={null} { "import_id": "550e8400-e29b-41d4-a716-446655440000", "status": "pending_review", "rule_count": 3, "created_at": "2026-01-15T10:30:00Z", "rules": [ { "id": "no_misleading_claims", "type": "AI_SIGNAL", "name": "No Misleading Claims", "action": "flag", "severity": "high", "confidence": 0.95 }, { "id": "benchmark_comparison_required", "type": "AI_SIGNAL", "name": "Benchmark Comparison Required", "action": "fix", "severity": "medium", "confidence": 0.88, "suggested_text": "Performance results should be compared to an appropriate benchmark index." }, { "id": "risk_disclosure_present", "type": "REQUIRE_NEAR", "name": "Risk Disclosure Near Performance Data", "action": "flag", "severity": "high", "confidence": 0.92 } ] } ``` ```json 404 Not Found theme={null} { "error": "Import not found" } ``` # Get Custom Rule Source: https://docs.zerodrift.com/api-reference/custom-policies/get-rule GET /api/policies/rules/{rule_id} Return the full editable definition for a customer-owned custom rule Return the full editable definition for a customer-owned custom rule. ## Path Parameters Custom rule identifier (typically prefixed with `cust_`) ## Response Rule identifier Human-readable rule name `AI_SIGNAL`, `REGEX`, or `REQUIRE_NEAR` `fix` or `flag` `do_not_send` or `send_with_caution` Stored suggested replacement or warning guidance when present Always `custom` Whether the rule currently runs during validation Remediation guidance when present AI instruction (`AI_SIGNAL` only) Keyword gate (`AI_SIGNAL` only) `sentence`, `paragraph`, or `document` (`AI_SIGNAL` only) Regex pattern (`REGEX` only) Anchor regex (`REQUIRE_NEAR` only) Near-pattern regexes (`REQUIRE_NEAR` only) Character distance (`REQUIRE_NEAR`) `before`, `after`, or `either` (`REQUIRE_NEAR`) Case-insensitive matching (`REQUIRE_NEAR`) `document` or `page` (`REQUIRE_NEAR`) Minimum near hits (`REQUIRE_NEAR`) Source import ID when the rule was created via policy import ISO 8601 creation timestamp ISO 8601 last-update timestamp when present ## Errors | Status | Meaning | | ------ | --------------------- | | `403` | Forbidden | | `404` | Custom rule not found | # Import Policy Source: https://docs.zerodrift.com/api-reference/custom-policies/import-policy POST /api/policies/import Upload a policy and extract enforceable rules. Upload a policy and extract enforceable rules. The endpoint returns immediately with a `processing` status while rule extraction runs in the background. Poll [Get Import Details](/api-reference/custom-policies/get-import-details) to check when extraction is complete. Documents you pick from a Notion, Linear, Confluence, or Google Drive connection go through this same import pipeline. Connect the source in Command, then treat the resulting `import_id` like any other import — see [MCP Connectors](/connecting-mcp). This endpoint sends the document inline in the request body (base64-encoded when `source` is `file`), which is capped by API Gateway / Lambda payload limits (a few MB after encoding). For large policy files, use the presigned upload flow instead: [Get Import Presigned URL](/api-reference/custom-policies/import-presigned-url) → upload to S3 → [Start Import](/api-reference/custom-policies/start-import). ## Request Body Content type indicator: `file` for base64-encoded documents or `text` for plain text. The policy content. Base64-encoded document bytes when `source` is `file`, or plain text when `source` is `text`. Original filename for reference (optional, used with `file` source). ## Response Unique identifier for the import. Use this to poll for status, view details, or activate rules. Initial status: `processing`. Poll the [Get Import Details](/api-reference/custom-policies/get-import-details) endpoint until status transitions to `pending_review`, `no_rules_found`, or `failed`. Human-readable message with next steps. ```json 202 Import Accepted theme={null} { "import_id": "550e8400-e29b-41d4-a716-446655440000", "status": "processing", "message": "Import accepted. Poll GET /api/policies/import/{import_id} for results." } ``` ```json 400 Bad Request theme={null} { "error": "Missing required field: source" } ``` ```json 422 Unprocessable Entity theme={null} { "error": "Cannot extract text from document" } ``` ## Status Lifecycle | Status | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------ | | `processing` | Import accepted, AI extraction in progress | | `pending_review` | Extraction complete, rules ready for review and activation | | `no_rules_found` | Extraction complete but no compliance rules were identified | | `failed` | Extraction failed (see `error_message` in [Get Import Details](/api-reference/custom-policies/get-import-details)) | | `activated` | All extracted rules have been activated | | `partially_activated` | Some rules activated, others still pending | ## Example Two request shapes (`source: "file"` with a base64 PDF, and `source: "text"`), then poll [Get Import Details](/api-reference/custom-policies/get-import-details) until extraction finishes. ```bash cURL (file) theme={null} DOC_BASE64=$(base64 -i compliance-policy.pdf) # Submit the import curl -X POST "https://api.zerodrift.ai/api/policies/import" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"source\": \"file\", \"content\": \"$DOC_BASE64\", \"filename\": \"compliance-policy.pdf\" }" # Poll for results (returns 'processing' until extraction completes) curl -X GET "https://api.zerodrift.ai/api/policies/import/IMPORT_ID" \ -H "x-api-key: YOUR_API_KEY" ``` ```bash cURL (text) theme={null} curl -X POST "https://api.zerodrift.ai/api/policies/import" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "source": "text", "content": "All marketing materials must include a risk disclosure. Performance claims require a 1-year, 5-year, and since-inception comparison to the benchmark." }' ``` ```python Python theme={null} import requests import base64 import time API_KEY = "YOUR_API_KEY" API_BASE = "https://api.zerodrift.ai" # Submit file import with open("compliance-policy.pdf", "rb") as f: doc_bytes = base64.b64encode(f.read()).decode() response = requests.post( f"{API_BASE}/api/policies/import", headers={ "x-api-key": API_KEY, "Content-Type": "application/json" }, json={ "source": "file", "content": doc_bytes, "filename": "compliance-policy.pdf" } ) import_id = response.json()["import_id"] print(f"Import ID: {import_id} — polling for results...") # Poll until extraction completes while True: details = requests.get( f"{API_BASE}/api/policies/import/{import_id}", headers={"x-api-key": API_KEY} ).json() if details["status"] != "processing": break time.sleep(3) print(f"Status: {details['status']}") if details.get("rule_count") is not None: print(f"Rules extracted: {details['rule_count']}") ``` ```javascript JavaScript theme={null} const fs = require('fs'); const axios = require('axios'); const API_KEY = 'YOUR_API_KEY'; const API_BASE = 'https://api.zerodrift.ai'; const docBytes = fs.readFileSync('compliance-policy.pdf').toString('base64'); // Submit import const { data } = await axios.post( `${API_BASE}/api/policies/import`, { source: 'file', content: docBytes, filename: 'compliance-policy.pdf' }, { headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' } } ); console.log(`Import ID: ${data.import_id} — polling for results...`); // Poll until extraction completes let details; do { await new Promise(r => setTimeout(r, 3000)); const res = await axios.get( `${API_BASE}/api/policies/import/${data.import_id}`, { headers: { 'x-api-key': API_KEY } } ); details = res.data; } while (details.status === 'processing'); console.log(`Status: ${details.status}`); if (details.rule_count != null) { console.log(`Rules extracted: ${details.rule_count}`); } ``` # Get Import Presigned URL Source: https://docs.zerodrift.com/api-reference/custom-policies/import-presigned-url POST /api/policies/import/presigned_url Get an S3 presigned URL for importing large policy files Step 1 of the large-file custom policy import flow. Returns an `import_id` and an S3 presigned `upload_url` so you can upload large policy documents directly to S3, bypassing the API Gateway and Lambda payload limits that cap the inline [Import Policy](/api-reference/custom-policies/import-policy) endpoint. After uploading, call [Start Import](/api-reference/custom-policies/start-import) with the `import_id` to begin rule extraction. Use the inline [Import Policy](/api-reference/custom-policies/import-policy) endpoint for plain text and small files. Use this presigned flow for large files (above a few MB). Maximum upload size is **1 GB**. Large documents (e.g. a multi-hundred-page regulatory handbook) are fully extracted — rule extraction runs in the background in chunks, so the entire document is analyzed, not just the first pages. The trade-off is time: a very large document can stay in `processing` for several minutes. Keep polling [Get Import Details](/api-reference/custom-policies/get-import-details) until the status leaves `processing`. ## Request Body Original filename, for reference/audit. MIME type of the file you will upload. Must match the `Content-Type` header you send on the PUT. ## Response Unique identifier for the import. Pass this to [Start Import](/api-reference/custom-policies/start-import) after uploading. S3 presigned URL for the file upload (HTTP PUT). HTTP method to use for upload (always `PUT`). S3 bucket name. S3 object key the file will be uploaded to. **Important:** Headers that MUST be included on the upload PUT. The presigned URL is signed with these, so omitting any results in a `SignatureDoesNotMatch` error from S3. Seconds until the presigned URL expires (default 900). ISO 8601 timestamp when the URL expires. Maximum allowed upload size in bytes, enforced at [Start Import](/api-reference/custom-policies/start-import). Step-by-step instructions for uploading and starting the import. ```json 200 OK theme={null} { "import_id": "550e8400-e29b-41d4-a716-446655440000", "upload_url": "https://example-bucket.s3.amazonaws.com/policy-imports/raw/550e8400-e29b-41d4-a716-446655440000.bin?X-Amz-Algorithm=...", "upload_method": "PUT", "s3_bucket": "example-bucket", "s3_key": "policy-imports/raw/550e8400-e29b-41d4-a716-446655440000.bin", "required_headers": { "Content-Type": "application/pdf" }, "expires_in_seconds": 900, "expires_at": "2026-06-15T10:45:00Z", "max_upload_bytes": 1073741824, "next_steps": { "step_1": "PUT the raw file bytes to upload_url with the exact required_headers", "step_2": "POST /api/policies/import/start with { \"import_id\": \"550e8400-...\" } to begin extraction" } } ``` **Required headers:** Include **all** headers from `required_headers` exactly as provided when uploading your file. Missing or mismatched headers (including `Content-Type`) can cause S3 to reject the upload with `SignatureDoesNotMatch`. ## Example Get a presigned URL from this endpoint, then `PUT` the file directly to S3 with every header from `required_headers`. Rule extraction still needs a follow-up [Start Import](/api-reference/custom-policies/start-import) call. ```bash cURL theme={null} # Step 1: Get the presigned URL RESPONSE=$(curl -s -X POST "https://api.zerodrift.ai/api/policies/import/presigned_url" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "filename": "compliance-policy.pdf", "content_type": "application/pdf" }') IMPORT_ID=$(printf '%s' "$RESPONSE" | jq -r '.import_id') UPLOAD_URL=$(printf '%s' "$RESPONSE" | jq -r '.upload_url') # Build curl -H args from required_headers CURL_HEADERS=() while IFS=$'\t' read -r key value; do CURL_HEADERS+=(-H "$key: $value") done < <(printf '%s' "$RESPONSE" | jq -r '.required_headers | to_entries[] | "\(.key)\t\(.value)"') # Step 2: Upload the raw file directly to S3 (no API key) curl -X PUT "$UPLOAD_URL" \ "${CURL_HEADERS[@]}" \ --data-binary @compliance-policy.pdf echo "Import ID: $IMPORT_ID" # Step 3: call POST /api/policies/import/start with this import_id ``` ```python Python theme={null} import requests API_KEY = "YOUR_API_KEY" API_BASE = "https://api.zerodrift.ai" # Step 1: Get the presigned URL resp = requests.post( f"{API_BASE}/api/policies/import/presigned_url", headers={"x-api-key": API_KEY, "Content-Type": "application/json"}, json={"filename": "compliance-policy.pdf", "content_type": "application/pdf"}, ).json() import_id = resp["import_id"] upload_url = resp["upload_url"] required_headers = resp["required_headers"] # Step 2: Upload the raw file directly to S3 with open("compliance-policy.pdf", "rb") as f: requests.put(upload_url, headers=required_headers, data=f) print(f"Import ID: {import_id}") # Step 3: call POST /api/policies/import/start with this import_id ``` ```javascript JavaScript theme={null} const fs = require('fs'); const axios = require('axios'); const API_KEY = 'YOUR_API_KEY'; const API_BASE = 'https://api.zerodrift.ai'; (async () => { // Step 1: Get the presigned URL const { data } = await axios.post( `${API_BASE}/api/policies/import/presigned_url`, { filename: 'compliance-policy.pdf', content_type: 'application/pdf' }, { headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' } } ); // Step 2: Upload the raw file directly to S3 with the signed required_headers const fileStream = fs.createReadStream('compliance-policy.pdf'); await axios.put(data.upload_url, fileStream, { headers: data.required_headers, maxBodyLength: Infinity, maxContentLength: Infinity, }); console.log(`Import ID: ${data.import_id}`); // Step 3: call POST /api/policies/import/start with this import_id })(); ``` # List Policy Imports Source: https://docs.zerodrift.com/api-reference/custom-policies/list-imports GET /api/policies/import List all policy imports for your account Retrieve a list of all policy imports associated with your API key. ## Response Array of import records Unique import identifier Import status: `processing`, `pending_review`, `no_rules_found`, `failed`, `activated`, or `partially_activated` Number of rules extracted ISO 8601 timestamp of the import Total number of imports ```json 200 Success theme={null} { "imports": [ { "import_id": "550e8400-e29b-41d4-a716-446655440000", "status": "activated", "rule_count": 5, "created_at": "2026-01-15T10:30:00Z" }, { "import_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "status": "pending_review", "rule_count": 3, "created_at": "2026-01-20T14:15:00Z" } ], "count": 2 } ``` # List Rules Source: https://docs.zerodrift.com/api-reference/custom-policies/list-rules GET /api/policies/rules List all default and custom rules with their active/deactivated status Retrieve all rules for your account, including default rules (from rule packs and scenarios) and custom rules (from policy imports). Each rule includes an `active` field indicating whether it will run during validation. ## Response Your customer identifier Default rules from rule packs and scenarios Rule identifier (e.g., `signal_mnpi_leak`) `AI_SIGNAL` or `REGEX` Always `default` for built-in rules Whether this rule is currently active for your account Whether this rule can run in simulation. Non-simulatable rules are still listed so clients can render them disabled. Rule pack this rule belongs to (e.g., `sec_finra_comms_v2`) Scenarios this rule appears in Confidence threshold for AI signals Threshold comparison operator (e.g., `>=`) Custom rules from policy imports Rule identifier (prefixed with `cust_`) `AI_SIGNAL`, `REGEX`, or `REQUIRE_NEAR` Human-readable rule name Always `custom` for customer-owned rules Whether this rule is currently active Whether this rule can run in simulation Import ID this rule was extracted from (when created via policy import) Rule severity level Rule action type Text window for evaluation ISO 8601 creation timestamp Total number of default rules (active + deactivated) Number of active default rules Total number of custom rules (active + deactivated) Number of active custom rules Total rules across default + custom Total active rules across default + custom ```json 200 OK theme={null} { "customer_id": "abc-123", "default_rules": [ { "rule_id": "signal_mnpi_leak", "type": "AI_SIGNAL", "source": "default", "active": true, "rule_pack_id": "sec_finra_comms_v2", "rule_pack_scenario_ids": ["scenario_email_general", "scenario_all_general"], "threshold": 0.60, "operator": ">=" } ], "custom_rules": [ { "rule_id": "cust_abc123_require_risk_disclosure", "type": "AI_SIGNAL", "name": "Risk Disclosure Required", "severity": "high", "source": "custom", "active": true, "import_id": "550e8400-e29b-41d4-a716-446655440000" } ], "default_rules_count": 119, "default_rules_active_count": 115, "custom_rules_count": 5, "custom_rules_active_count": 5, "total_rules_count": 124, "total_active_count": 120 } ``` # Reactivate Rules Source: https://docs.zerodrift.com/api-reference/custom-policies/reactivate-rules POST /api/policies/rules/activate Reactivate previously deactivated rules Reactivate rules that were previously deactivated. Custom rules have their status set back to active. Default rules are removed from your deactivated list. This endpoint is for re-activating **previously deactivated** rules. To activate rules from a new policy import for the first time, use [POST /api/policies/import//activate](/api-reference/custom-policies/activate-rules) instead. ## Request Body Reactivate all rules in a rule pack (e.g., `sec_finra_comms_v2`) Reactivate all custom rules from this import Reactivate all default rules in a scenario Array of rule ID strings to reactivate (custom or default) At least one field is required. Multiple fields can be combined in a single request. ## Response Number of custom rules reactivated List of reactivated custom rule IDs Number of default rules reactivated List of reactivated default rule IDs List of error messages for rules that could not be activated. Empty array when no errors. ```json 200 OK theme={null} { "activated_custom_count": 3, "activated_custom_rule_ids": [ "cust_abc123_require_risk_disclosure", "cust_abc123_past_performance_disclaimer", "cust_abc123_no_specific_client_returns" ], "activated_default_count": 0, "activated_default_rule_ids": [], "errors": [] } ``` ## Example Two ways to select what to reactivate on the same endpoint: by `rule_pack_id` or by `import_id`. ```bash cURL — by rule pack theme={null} curl -X POST "https://api.zerodrift.ai/api/policies/rules/activate" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"rule_pack_id": "sec_finra_comms_v2"}' ``` ```bash cURL — by import theme={null} curl -X POST "https://api.zerodrift.ai/api/policies/rules/activate" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"import_id": "550e8400-e29b-41d4-a716-446655440000"}' ``` # Start Import Source: https://docs.zerodrift.com/api-reference/custom-policies/start-import POST /api/policies/import/start Start rule extraction after uploading a policy file via presigned URL Step 2 of the large-file custom policy import flow (after the S3 upload). Call this after uploading your file to the presigned `upload_url` from [Get Import Presigned URL](/api-reference/custom-policies/import-presigned-url). It verifies the uploaded object and enforces the size cap, then — once malware scanning has completed successfully — starts asynchronous rule extraction and returns `status: "processing"`. If the scan is still running, it returns `status: "scan_pending"` immediately instead; retry the call until extraction starts (see the note below). Poll [Get Import Details](/api-reference/custom-policies/get-import-details) until the status transitions to `pending_review`, `no_rules_found`, or `failed` — identical to the inline [Import Policy](/api-reference/custom-policies/import-policy) flow. ## Request Body The `import_id` returned by [Get Import Presigned URL](/api-reference/custom-policies/import-presigned-url). Optional filename to record on the import (overrides the value from the presigned step). ## Response Unique identifier for the import. Use it to poll for status or activate rules. `processing` once extraction has started, or `scan_pending` if the malware scan is still running (retry shortly). Human-readable message with next steps. ```json 202 Import Accepted theme={null} { "import_id": "550e8400-e29b-41d4-a716-446655440000", "status": "processing", "message": "Import accepted. Poll GET /api/policies/import/{import_id} for results." } ``` ```json 202 Scan Pending theme={null} { "import_id": "550e8400-e29b-41d4-a716-446655440000", "status": "scan_pending", "message": "File is still being scanned. Retry POST /api/policies/import/start shortly." } ``` ```json 404 Not Found theme={null} { "error": "No uploaded file found for this import_id", "import_id": "550e8400-e29b-41d4-a716-446655440000", "message": "Upload the file with the presigned URL before calling /start" } ``` ## Error Responses | Status | Description | | ------ | --------------------------------------------------------------------------- | | 400 | Missing/invalid `import_id`, or the uploaded file is empty | | 403 | Invalid API key, or the import belongs to another customer | | 404 | No uploaded file found for this `import_id` (upload step skipped or failed) | | 409 | Import already started or completed | | 413 | Uploaded file exceeds the maximum allowed size (1 GB) | | 422 | File failed malware/content scanning | If you receive a `202` with `status: "scan_pending"`, the malware scan is still running (usually a few seconds). Retry this request shortly — rule extraction does not start until the scan completes successfully. ## Example Retry this endpoint while malware scan is still `scan_pending`. The Python sample then polls [Get Import Details](/api-reference/custom-policies/get-import-details) until extraction finishes. The cURL sample is the single POST. ```bash cURL theme={null} curl -X POST "https://api.zerodrift.ai/api/policies/import/start" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "import_id": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```python Python theme={null} import requests import time API_KEY = "YOUR_API_KEY" API_BASE = "https://api.zerodrift.ai" import_id = "550e8400-e29b-41d4-a716-446655440000" # Start (retry while the malware scan is still pending) while True: resp = requests.post( f"{API_BASE}/api/policies/import/start", headers={"x-api-key": API_KEY, "Content-Type": "application/json"}, json={"import_id": import_id}, ).json() if resp.get("status") != "scan_pending": break time.sleep(3) # An error payload (e.g. 404) has no "status" — surface it instead of crashing if "status" not in resp: raise SystemExit(f"Start failed: {resp.get('error', resp)}") print(resp["status"]) # Poll for extraction results while True: details = requests.get( f"{API_BASE}/api/policies/import/{import_id}", headers={"x-api-key": API_KEY}, ).json() if details.get("status") != "processing": break time.sleep(3) print(f"Status: {details.get('status', details)}") ``` ```javascript JavaScript theme={null} const axios = require('axios'); const API_KEY = 'YOUR_API_KEY'; const API_BASE = 'https://api.zerodrift.ai'; const importId = '550e8400-e29b-41d4-a716-446655440000'; (async () => { // Start (retry while the malware scan is still pending) let started; while (true) { try { const res = await axios.post( `${API_BASE}/api/policies/import/start`, { import_id: importId }, { headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' } } ); started = res.data; } catch (err) { const payload = err.response?.data ?? err.message; throw new Error(`Start failed: ${typeof payload === 'string' ? payload : JSON.stringify(payload)}`); } if (started?.status !== 'scan_pending') break; await new Promise(r => setTimeout(r, 3000)); } if (!started?.status) { throw new Error(`Start failed: ${JSON.stringify(started)}`); } console.log(started.status); })(); ``` # Errors Source: https://docs.zerodrift.com/api-reference/errors Every HTTP status code the ZeroDrift Enforcement API returns, and what to do. HTTP errors return JSON. Responses generated by the API gateway use a top-level `message` field: ```json theme={null} { "message": "Forbidden" } ``` Validation and application errors may instead use a top-level `detail` field. Read either field when presenting an error to an operator. | Code | Meaning | What to do | | ----- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | Bad request, such as invalid parameters, training options, pagination, or an Activity range over 30 days | Correct the field named in the response. | | `401` | Invalid API key on the Activity API | Check the `x-api-key` header and the key in Command. | | `403` | Missing, invalid, inactive, or unresolved API key; or a key without permission for the operation | Check the key, or use a full-access key for write operations. | | `404` | Unknown `job_id`, `import_id`, `rule_id`, `activity_id`, or rulepack selector | Check the id. Jobs and activities age out according to [Data Retention](/data-retention). | | `409` | Resource-state conflict, such as an import that is not activated or a training run already in progress | Complete the required state transition or wait for the active operation. | | `410` | The original policy document expired | Re-import and activate the policy, then retry training. | | `413` | An uploaded policy is too large | Use a smaller document or the supported presigned-upload limits. | | `422` | Content, a policy document, or request fields could not be processed | Correct or provide fuller input. | | `429` | Rate limit exceeded | Retry with backoff. The standard limit is 10 requests per second with a burst of 50. | | `500` | Internal server error | Follow the endpoint-specific guidance. Some operations are safe to retry; `POST /api/policies/import/start` must not be retried with the same `import_id`. | | `502` | Training Studio upstream request failed | Retry with backoff. | | `503` | Training is not enabled, or the service is temporarily unavailable | For temporary failures, retry. For a disabled feature, contact support. | The exact set varies by endpoint. Each endpoint page lists the statuses that it returns, and the [OpenAPI specification](/api-reference/openapi) is the machine-readable contract. ## Retry pattern Retry only transient failures and rate limits: ```python theme={null} import time import requests def call_with_retry(send, retryable_statuses, attempts=4): response = None for attempt in range(attempts): response = send() if response.status_code not in retryable_statuses: return response if attempt == attempts - 1: break retry_after = response.headers.get("Retry-After") delay = float(retry_after) if retry_after else min(2**attempt, 8) time.sleep(delay) return response ``` Choose `retryable_statuses` from the endpoint's documented responses. For example, pass `{429, 500, 502, 503}` only when that operation is safe to repeat. For `POST /api/policies/import/start`, do not include `500`: create a new upload and `import_id` before trying again. Do not retry `400`, `401`, `403`, `404`, `409`, `410`, `413`, or `422` without changing the request or resource state. Never re-send content that received a Block verdict. ## Job failures are not HTTP errors An asynchronous enforcement job can finish with `status: "failed"`. In that case, the `error` field on the job explains the failure while the poll request itself returns HTTP `200`. ```json theme={null} { "api_version": "v3", "job_id": "a1b2c3", "status": "failed", "model_engine": "anchor_3_0", "error": "Validation engine unavailable" } ``` # Introduction Source: https://docs.zerodrift.com/api-reference/introduction ZeroDrift Enforcement API ## Overview **Preview:** The ZeroDrift API and this documentation are in preview. Endpoints and response formats may change before general availability. The ZeroDrift Enforcement API enforces regulations, company policies, and security controls on content and communications. Every result is a verdict. Pass. Rewrite. Block. Escalate. The API returns the verdict; Guard applies it inline. ## Authentication All API endpoints require authentication via API Key in the request header. | Header | Description | | ----------- | ---------------------- | | `x-api-key` | Your ZeroDrift API key | Your Enforcement API key is provisioned when your account is created. ```bash theme={null} curl "https://api.zerodrift.ai/api/rulepacks/" \ -H "x-api-key: YOUR_API_KEY" ``` ## Quick Start Enforcement runs in two steps. Send content and get a job ID. Poll for the verdict. ``` POST /api/v3/content/validate → GET /api/v3/jobs/{job_id} ``` 1. Send content with [Enforce Content](/api-reference/validate/validate-content) — the response returns a `job_id` 2. Poll [Get Verdict](/api-reference/validate/get-results) until the job is `done` or `failed` ### Policy Import a policy and ZeroDrift extracts enforceable rules. Review, activate, train an adapter in Training Studio, then enforce against it. ``` import → poll extraction → review/simulate → activate → train → poll training → enforce ``` 1. Upload a policy document (PDF, DOCX, or plain text), or sync one from a [connected MCP source](/connecting-mcp) 2. Poll until AI rule extraction completes 3. Review or simulate the rules, then activate them 4. [Start adapter training](/api-reference/training-studio/train-policy) from the activated import 5. [Poll training status](/api-reference/training-studio/get-training-status) until the adapter succeeds 6. Enforce with the import ID in `validation_scope.imports` You can also create and edit custom rules directly via `POST /api/policies/rules` and `PATCH /api/policies/rules/{rule_id}`. ### Activity ``` GET /api/activities → GET /api/activities/{activity_id} ``` Every verdict is logged in Command. The Activity endpoints read that log for the last 30 days. ## OpenAPI specification A machine-readable OpenAPI 3.0 document of the public API is available at [`/openapi.json`](/openapi.json). See [OpenAPI specification](/api-reference/openapi) for the operations it covers, and [SDKs](/generate-sdk) to produce a typed Python or TypeScript client from it. ## Rate Limits | Tier | Requests/Second | Burst | | -------- | --------------- | ----- | | Standard | 10 | 50 | See [Errors](/api-reference/errors) for `429` handling. Contact [support@zerodrift.ai](mailto:support@zerodrift.ai) for higher limits. ## Support For API support or to request higher rate limits, contact: **[support@zerodrift.ai](mailto:support@zerodrift.ai)** For data handling and retention, see [Data Retention](/data-retention). # OpenAPI specification Source: https://docs.zerodrift.com/api-reference/openapi Machine-readable OpenAPI 3.0 spec for the ZeroDrift Enforcement API The ZeroDrift Enforcement API is published as an OpenAPI 3.0 document. Use it to generate clients, import the API into Postman or similar tools, or give a language model a single file that describes every publicly documented endpoint. For a walkthrough in Python and TypeScript, see [SDKs](/generate-sdk). Open or download the OpenAPI 3.0 spec (JSON). ZeroDrift serves this file at `/openapi.json` on the docs site. After deploy, that URL is: ```text theme={null} https://docs.zerodrift.com/openapi.json ``` Fetch it with: ```bash theme={null} curl https://docs.zerodrift.com/openapi.json ``` ZeroDrift also lists the spec in the auto-generated [`llms.txt`](https://docs.zerodrift.com/llms.txt) index, which AI tools use to discover pages and API files. This spec covers enforcement, policy, activity, Training Studio, and rule packs. ## Operations | Group | Method | Path | Docs | | --------------- | ------- | -------------------------------------------------- | ------------------------------------------------------------------------------- | | Enforce | `POST` | `/api/v3/content/validate` | [Enforce Content](/api-reference/validate/validate-content) | | Enforce | `GET` | `/api/v3/jobs/{job_id}` | [Get Verdict](/api-reference/validate/get-results) | | Policy | `POST` | `/api/policies/import` | [Import Policy](/api-reference/custom-policies/import-policy) | | Policy | `GET` | `/api/policies/import` | [List Policy Imports](/api-reference/custom-policies/list-imports) | | Policy | `POST` | `/api/policies/import/presigned_url` | [Get Import Presigned URL](/api-reference/custom-policies/import-presigned-url) | | Policy | `POST` | `/api/policies/import/start` | [Start Import](/api-reference/custom-policies/start-import) | | Policy | `GET` | `/api/policies/import/{import_id}` | [Get Import Details](/api-reference/custom-policies/get-import-details) | | Policy | `PATCH` | `/api/policies/import/{import_id}/rules/{rule_id}` | [Edit Imported Rule](/api-reference/custom-policies/edit-imported-rule) | | Policy | `POST` | `/api/policies/import/{import_id}/activate` | [Activate Rules](/api-reference/custom-policies/activate-rules) | | Policy | `GET` | `/api/policies/rules` | [List Rules](/api-reference/custom-policies/list-rules) | | Policy | `POST` | `/api/policies/rules` | [Create Custom Rule](/api-reference/custom-policies/create-rule) | | Policy | `GET` | `/api/policies/rules/{rule_id}` | [Get Custom Rule](/api-reference/custom-policies/get-rule) | | Policy | `PATCH` | `/api/policies/rules/{rule_id}` | [Edit Custom Rule](/api-reference/custom-policies/edit-rule) | | Policy | `POST` | `/api/policies/rules/deactivate` | [Deactivate Rules](/api-reference/custom-policies/deactivate-rules) | | Policy | `POST` | `/api/policies/rules/activate` | [Reactivate Rules](/api-reference/custom-policies/reactivate-rules) | | Training Studio | `POST` | `/api/policies/import/{import_id}/train` | [Train Policy Adapter](/api-reference/training-studio/train-policy) | | Training Studio | `GET` | `/api/policies/import/{import_id}/train` | [Get Training Status](/api-reference/training-studio/get-training-status) | | Activity | `GET` | `/api/activities` | [List Activity](/api-reference/activities/list-activities) | | Activity | `GET` | `/api/activities/{activity_id}` | [Get Activity](/api-reference/activities/get-activity) | | Policy | `GET` | `/api/rulepacks/` | [Rule Packs](/api-reference/rulepacks) | ## Authentication Every operation uses the `x-api-key` header. See [Introduction](/api-reference/introduction) for base URL, rate limits, and workflows. # Rule Packs Source: https://docs.zerodrift.com/api-reference/rulepacks GET /api/rulepacks/ Query the normalized policy catalog of managed and custom rule packs Managed packs cover regulations. Import Policy and Training Studio make any rulebook enforceable, in any industry. Returns the normalized policy catalog for the active authenticated customer. Managed entries reflect the Anchor v3 runtime vocabulary. Custom entries are policy imports that have been activated at least once, including entries that are currently inactive. Supplying `id` selects detail and requires `type`. ## Query Parameters Rulepack ID for detail lookup; requires `type` `managed` or `custom`. Filter list results or disambiguate a detail ID One-based result page Results per page (1–100) Case-insensitive match against ID, name, or description Filter by current runtime active state: `'true'` or `'false'` ## Response - List When called without `id`, returns paginated summaries under `data`. Rulepack summaries Rulepack identifier (e.g., `zd-us-securities`) Human-readable rulepack name Rulepack description (nullable) `managed` or `custom` Supported runtime engine vocabulary: `anchor_3_0` Whether the rulepack is currently active for this customer Total rules in the rulepack Currently active rules Currently inactive rules Publisher (nullable) Rulepack version (nullable) Compliance domain (nullable) Applicable jurisdictions Last update date (nullable) Pagination metadata (nullable) Current one-based page Results per page Total matching rulepacks Last available page ```json 200 OK - List theme={null} { "data": [ { "id": "zd-us-securities", "name": "US Securities and Investment Communications", "description": "SEC and FINRA communication compliance", "type": "managed", "engine": "anchor_3_0", "active": true, "total_rules": 72, "active_rules": 72, "inactive_rules": 0, "publisher": "ZeroDrift", "version": "3.0.0", "domain": "securities", "jurisdictions": ["US"], "last_updated": "2026-07-28" } ], "meta": { "current_page": 1, "per_page": 15, "total": 13, "last_page": 1 } } ``` ## Response - Detail When called with `id` and `type`, returns one detailed rulepack under `data`, including its `rules`. All summary fields, plus: Rules in the rulepack Rule identifier Human-readable rule name Rule description (nullable) `AI_SIGNAL`, `REGEX`, `COMPOSITE`, `LEXICON`, `REQUIRE_NEAR`, or `CUSTOM` Rule severity (nullable) `do_not_send` or `send_with_caution` Whether the rule is currently active Lifecycle detail (nullable): `status`, `effective_from`, `last_reviewed` Regulatory citations with `framework`, `cite`, and `jurisdictions` Remediation guidance with `kind` and `note` ```json 200 OK - Detail theme={null} { "data": { "id": "zd-us-securities", "name": "US Securities and Investment Communications", "type": "managed", "engine": "anchor_3_0", "active": true, "total_rules": 72, "active_rules": 72, "inactive_rules": 0, "rules": [ { "id": "guarantee_of_returns", "name": "Promissory Tone", "description": "Promissory words are risky", "type": "AI_SIGNAL", "severity": "major", "verdict": "do_not_send", "active": true, "lifecycle": { "status": "in_force", "last_reviewed": "2026-07-28" }, "citations": [ { "framework": "FINRA Rule 2210", "jurisdictions": ["US"] } ], "remediation": { "kind": "rewrite", "note": "Review with compliance" } } ] } } ``` ## Errors | Status | Meaning | | ------ | ------------------------------------------------------------------ | | `400` | Invalid pagination, filter, or detail selector | | `403` | Forbidden — missing, invalid, inactive, or unresolved API key | | `404` | Rulepack not found or not accessible to this customer | | `500` | Catalog backend read failed or stored catalog data is inconsistent | ## Example Same endpoint in two modes: a paginated list (`per_page`, `active`) and one pack's detail (`id` plus required `type`). ```bash cURL - List theme={null} curl -X GET "https://api.zerodrift.ai/api/rulepacks/?per_page=15&active=true" \ -H "x-api-key: YOUR_API_KEY" ``` ```bash cURL - Detail theme={null} curl -X GET "https://api.zerodrift.ai/api/rulepacks/?id=zd-us-securities&type=managed" \ -H "x-api-key: YOUR_API_KEY" ``` ```python Python theme={null} import requests API_KEY = "YOUR_API_KEY" API_BASE = "https://api.zerodrift.ai" headers = {"x-api-key": API_KEY} # List rulepacks response = requests.get(f"{API_BASE}/api/rulepacks/", headers=headers) catalog = response.json() print(catalog["data"]) # Get one rulepack's detail (id requires type) response = requests.get( f"{API_BASE}/api/rulepacks/", headers=headers, params={"id": "zd-us-securities", "type": "managed"} ) rulepack = response.json() print(rulepack["data"]["rules"]) ``` ```javascript JavaScript theme={null} const axios = require('axios'); const API_KEY = 'YOUR_API_KEY'; const API_BASE = 'https://api.zerodrift.ai'; const headers = { 'x-api-key': API_KEY }; // List rulepacks const listResponse = await axios.get(`${API_BASE}/api/rulepacks/`, { headers }); console.log(listResponse.data.data); // Get one rulepack's detail (id requires type) const detailResponse = await axios.get(`${API_BASE}/api/rulepacks/`, { headers, params: { id: 'zd-us-securities', type: 'managed' } }); console.log(detailResponse.data.data.rules); ``` # Get Training Status Source: https://docs.zerodrift.com/api-reference/training-studio/get-training-status GET /api/policies/import/{import_id}/train Poll a policy import until its Training Studio adapter is ready Return the current training run for a policy import. Poll after [Train Policy Adapter](/api-reference/training-studio/train-policy) until `status` is `succeeded` or `failed`. The successful poll is part of activation: when Training Studio reports `succeeded`, ZeroDrift records the run as the import's active adapter. A failed retraining attempt does not replace an earlier working adapter. ## Path Parameters The policy import whose training run you want to inspect. ## Response Policy import associated with the run. Training Studio run identifier. Current run status. `succeeded` and `failed` are terminal. Current training pipeline stage, when reported. Training progress details, when reported. Failure detail when `status` is `failed`. ```json 200 Training Succeeded theme={null} { "import_id": "550e8400-e29b-41d4-a716-446655440000", "training_run_id": "run-808961af", "status": "succeeded", "stage": "complete", "progress": { "completed": 4, "total": 4 }, "error": null } ``` ## Error Responses | Status | Description | | ------ | ---------------------------------------------------------- | | 403 | Invalid API key, or the import belongs to another customer | | 404 | Import not found, or no training run has been started | | 500 | Import state could not be read or updated | | 502 | Training Studio status request failed | | 503 | Training is not configured in this environment | ## Complete flow Training extends the custom-policy flow: 1. [Import Policy](/api-reference/custom-policies/import-policy). 2. Poll [Get Import Details](/api-reference/custom-policies/get-import-details) until extraction reaches `pending_review`. 3. Review or simulate the extracted rules, then [Activate Rules](/api-reference/custom-policies/activate-rules). 4. [Train Policy Adapter](/api-reference/training-studio/train-policy). 5. Poll this endpoint until `status` is `succeeded` or `failed`. Stop on either terminal status; do not keep polling a failed run. 6. If the run `succeeded`, enforce content with [`validation_scope.imports`](/api-reference/validate/validate-content) containing the import ID. Do not submit content for enforcement to a still-training run. ZeroDrift only promotes `training_run_id` after a successful status poll; until then, scoped enforcement follows its configured fallback path. ## Example This sample starts training and polls to a terminal state. ```python Python theme={null} import time import requests API_BASE = "https://api.zerodrift.ai" API_KEY = "YOUR_API_KEY" IMPORT_ID = "550e8400-e29b-41d4-a716-446655440000" URL = f"{API_BASE}/api/policies/import/{IMPORT_ID}/train" HEADERS = {"x-api-key": API_KEY} started = requests.post(URL, headers=HEADERS, json={}) started.raise_for_status() print(started.json()) while True: response = requests.get(URL, headers=HEADERS) response.raise_for_status() training = response.json() print(training["status"]) if training["status"] in ("succeeded", "failed"): break time.sleep(10) if training["status"] == "failed": raise SystemExit(training.get("error", "Training failed")) ``` # Train Policy Adapter Source: https://docs.zerodrift.com/api-reference/training-studio/train-policy POST /api/policies/import/{import_id}/train Start a Training Studio adapter run from an activated policy import Start asynchronous training for an activated policy import. Training Studio re-reads the import's original document, generates and judges examples, and trains a policy-specific LoRA adapter. Call this after [Import Policy](/api-reference/custom-policies/import-policy) and [Activate Rules](/api-reference/custom-policies/activate-rules). Import extracts candidate rules; training is only accepted once those rules are activated (`409` if the import is still pending review). Then poll [Get Training Status](/api-reference/training-studio/get-training-status) until training succeeds or fails. Training uses the original imported document, not only the extracted rule definitions. If that document has expired under your retention policy, re-import and activate the policy before training. ## Path Parameters The activated policy import to train. ## Request Body The body is optional. Omit it to use Training Studio defaults. Optional maximum generation spend in US dollars for this run. Optional number of generated training examples per extracted rule. ## Response Returns `202 Accepted` after the training run has been created. Policy import being trained. Training Studio run identifier. Initial status reported for the training run. Human-readable next step, when present. Endpoint used to monitor this run. Always `GET`. `/api/policies/import/{import_id}/train` ```json 202 Training Accepted theme={null} { "import_id": "550e8400-e29b-41d4-a716-446655440000", "training_run_id": "run-808961af", "status": "queued", "message": "Training started. Poll the training status endpoint for progress.", "poll": { "method": "GET", "url": "/api/policies/import/550e8400-e29b-41d4-a716-446655440000/train" } } ``` ## Error Responses | Status | Description | | ------ | ---------------------------------------------------------------------------- | | 400 | Invalid training options | | 403 | A full-access API key is required, or the import belongs to another customer | | 404 | Import not found | | 409 | Import is not activated, or another training run is already in progress | | 410 | Original document expired; re-import the policy | | 422 | Document is too short to train, or Training Studio rejected it | | 500 | Import state could not be read or updated | | 502 | Training Studio request failed | | 503 | Training is not configured in this environment | ## Example ```bash cURL theme={null} curl -X POST \ "https://api.zerodrift.ai/api/policies/import/550e8400-e29b-41d4-a716-446655440000/train" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{}' ``` # Get Verdict Source: https://docs.zerodrift.com/api-reference/validate/get-results GET /api/v3/jobs/{job_id} Retrieve the verdict for an enforcement job. Retrieve the verdict for an enforcement job started via [Enforce Content](/api-reference/validate/validate-content). Poll this endpoint until `status` is `done` or `failed`. ## Path Parameters The `job_id` returned by [Enforce Content](/api-reference/validate/validate-content). ## Response Always `v3`. Unique identifier for the enforcement job. Job status: `started`, `in_progress`, `done`, or `failed`. Supported engine value: `anchor_3_0`. Time when the enforcement job started. Time when this result snapshot was generated. Error details (present only when `status` is `failed`). Overall compliance assessment: `approved`, `needs_changes`, or `do not send`. Note: `do not send` contains spaces — this matches the API response exactly. This is the verdict: `approved` = Pass, `needs_changes` = Rewrite, and `do not send` = Block. Violation counts by severity. Count of blocking severity violations Count of caution severity violations Number of pages in the document Number of lines with at least one compliance violation. Absent for older jobs or when only grouped fixes exist (per-line positions unavailable). Number of lines with no violations. Absent for older jobs or when only grouped fixes exist. Total number of lines in the document. Absent for older jobs or when only grouped fixes exist. True when more than one fix is found, suggesting a re-scan after applying fixes. Rule IDs the Anchor 3.0 SLM identified as relevant to the content (the per-request evaluated rule set). Input (prompt) tokens the Anchor 3.0 SLM consumed for this enforcement job. Output (generated) tokens the Anchor 3.0 SLM produced for this enforcement job. One entry per violating line, with all rules violated and the best fix. Absent for older jobs or when only grouped fixes exist. Present but empty for fully compliant documents. 1-indexed line number in the document The text content of the violating line (trimmed) Character offset of the best fix's original text start Character offset of the best fix's original text end Page number where the violation appears Number of distinct rules violated on this line All rules violated on this line, sorted by severity then confidence. Each entry carries `rule_id` (canonical rule identifier — rule-pack `id`, or `cust_{customer_id}_{id}` for activated custom rules), `rule_name`, `rule_ref` (regulatory citation, e.g. `FINRA 2210(d)(1)(B)`), `severity` (`do_not_send` or `send_with_caution`), `confidence` (0–1), and `action` (`replace`, `insert_after`, `remove`, or `warning`). The recommended fix for this line (from the highest-severity, highest-confidence rule): `severity`, `rule_id`, `rule_name`, `rule_ref`, `action`, `suggested_text` (null when `action` is `remove`/`warning`), `human_action`, and `confidence`. Individual fix entries (rule-centric, one per violation per quote). Each entry carries `rule_id` — the canonical rule identifier (rule-pack `id`, or `cust_{customer_id}_{id}` for activated custom rules). Grouped fixes for rules that aggregate across pages. Each value additionally carries `rule_id` (the canonical rule identifier — also the key under which the group is stored in this object). Document metadata used for scenario matching. ## Status Values | Status | Description | | ------------- | ---------------------------------- | | `started` | Job created and started processing | | `in_progress` | Enforcement in progress | | `done` | Enforcement completed successfully | | `failed` | Enforcement failed with error | ## Error Responses | Status | Description | | ------ | ------------------------------------------------------------- | | 403 | Forbidden — missing, invalid, inactive, or unresolved API key | | 404 | Job Not Found | ```json In Progress theme={null} { "api_version": "v3", "job_id": "a1b2c3", "status": "in_progress", "model_engine": "anchor_3_0", "started": "2026-06-25T12:00:00.123456+00:00", "timestamp": "2026-06-25T12:00:02.654321+00:00" } ``` ```json Done theme={null} { "api_version": "v3", "job_id": "a1b2c3", "status": "done", "model_engine": "anchor_3_0", "overall_status": "do_not_send", "summary": { "do_not_send": 1, "send_with_caution": 0, "document_pages": 1 }, "violating_line_count": 2, "compliant_line_count": 3, "total_line_count": 5, "revalidation_recommended": true, "relevant_rule_ids": [ "guarantee_of_returns", "missing_risk_disclosure" ], "input_tokens": 119, "output_tokens": 1920, "violations_by_line": [ { "line_number": 2, "line_text": "Our fund guarantees 18% returns with no downside risk.", "char_from": 45, "char_to": 98, "page": 1, "rule_count": 1, "rules_violated": [ { "rule_name": "Prohibited Promissory Language (Keywords)", "rule_ref": "FINRA 2210(d)(1)(B)", "severity": "do_not_send", "confidence": 0.92, "action": "replace" } ], "best_fix": { "severity": "do_not_send", "rule_name": "Prohibited Promissory Language (Keywords)", "rule_ref": "FINRA 2210(d)(1)(B)", "action": "replace", "suggested_text": "Our fund has historically delivered returns, though past performance does not guarantee future results.", "confidence": 0.92 } } ], "fixes": [], "fixes_group": {}, "metadata": {} } ``` ```json Failed theme={null} { "api_version": "v3", "job_id": "a1b2c3", "status": "failed", "model_engine": "anchor_3_0", "error": "Validation engine unavailable" } ``` ## Example Poll until the job is `done` or `failed`. The Python and JavaScript samples wait 2 seconds between requests; the cURL sample is a single GET you repeat yourself. ```bash cURL theme={null} curl "https://api.zerodrift.ai/api/v3/jobs/a1b2c3" \ -H "x-api-key: YOUR_API_KEY" ``` ```python Python theme={null} import requests import time API_KEY = "YOUR_API_KEY" API_BASE = "https://api.zerodrift.ai" def get_results(job_id): while True: response = requests.get( f"{API_BASE}/api/v3/jobs/{job_id}", headers={"x-api-key": API_KEY} ) result = response.json() if result["status"] in ("done", "failed"): return result time.sleep(2) results = get_results("a1b2c3") print(results) ``` ```javascript JavaScript theme={null} const axios = require('axios'); const API_KEY = 'YOUR_API_KEY'; const API_BASE = 'https://api.zerodrift.ai'; async function getResults(jobId) { while (true) { const response = await axios.get( `${API_BASE}/api/v3/jobs/${jobId}`, { headers: { 'x-api-key': API_KEY } } ); if (['done', 'failed'].includes(response.data.status)) { return response.data; } await new Promise(resolve => setTimeout(resolve, 2000)); } } const results = await getResults('a1b2c3'); console.log(results); ``` # Enforce Content Source: https://docs.zerodrift.com/api-reference/validate/validate-content POST /api/v3/content/validate Send content to Anchor, the compliance enforcement model, and get a verdict. Async or sync. Send content to Anchor, the compliance enforcement model, and get a verdict. Requests require `mode` and `model_engine`. With `mode: "async"`, the endpoint returns a `job_id` you poll via [Get Verdict](/api-reference/validate/get-results). Set `mode: "sync"` to run enforcement inline and receive the completed result in a single response. ## Request Body Plain text to enforce. Required unless the request sends the deprecated `email_text` instead. Deprecated alias for `content`, kept for callers written before the rename. Still accepted in place of `content`, but read only when `content` is absent from the request — send `content` instead. Enforcement model. Use `anchor_3_0` to run the Anchor 3.0 compliance SLM — carrying a sentinel-head detection adapter (per-line, per-rule calibrated probabilities over the deployed checkpoint's frozen 235-rule vocabulary) and a judge-rewarded rewrite adapter. Results include `relevant_rule_ids` and input/output token counts. Execution mode: `async` returns `202` with a `job_id` to poll; `sync` runs inline for short content and returns the completed result with `200`, falling back to async if it would exceed the inline budget. Optional metadata merged into the job. Optional explicit scenario id. The complete set of public scenario ids is: `scenario_email_general` (the default when omitted) and `scenario_retail_investor_letter`. Your account's active custom rules (from policy imports) are evaluated as well. The API currently forces `metadata.document_type` to `email`, even when a different value is submitted. Do not use that metadata field to distinguish chat, agent, or document traffic. Optional nested form to narrow live enforcement to a subset of already-active rules, rule packs, and imports. Rule IDs to evaluate. Rule pack IDs to evaluate. Import IDs to evaluate. ## Response Returns `202 Accepted` for async submissions. Poll [Get Verdict](/api-reference/validate/get-results) for the verdict. Always `v3`. Unique identifier for the enforcement job. `queued` for async submissions. For `sync` responses the completed result envelope is returned inline (`status: done`). Mode the request was actually processed in: `async` or `sync`. A `sync` request that fell back returns `async`. Supported engine value: `anchor_3_0`. Where to poll for the result (async only). Always `GET`. Poll URL, e.g. `/api/v3/jobs/{job_id}`. For the `sync` response body (HTTP 200), the envelope is identical to a **done** poll — see [Get Verdict](/api-reference/validate/get-results) — with `"mode": "sync"` added. ## Error Responses | Status | Description | | ------ | -------------------------------------------------------------- | | 400 | Bad Request — invalid `model_engine` or unsupported parameters | | 403 | Forbidden — missing, invalid, inactive, or unresolved API key | | 422 | Unprocessable Entity | ```json 202 Accepted (async) theme={null} { "api_version": "v3", "job_id": "a1b2c3", "status": "queued", "mode": "async", "model_engine": "anchor_3_0", "poll": { "method": "GET", "url": "/api/v3/jobs/a1b2c3" } } ``` # Changelog Source: https://docs.zerodrift.com/changelog What changed in the ZeroDrift API and documentation. Newest first. Breaking changes are announced here before they ship. ## 2026-09-02: Guard for Agents * The Enforcement API became available in production for agent response paths. * MCP connectors can import policies from Notion, Linear, Confluence, and Google Drive. * The Anchor 3.0 preview is available as `model_engine: "anchor_3_0"`. ## 2026-09-01: Training Studio API preview * Added `POST /api/policies/import/{import_id}/train`. * Added `GET /api/policies/import/{import_id}/train` for training status. Training Studio's broader product launch is separate from this API preview. ## 2026-08-11: Command * Command launched with API keys, rulepack activation, policy import, and live activity. ## 2026-08-04: Activity API preview * Added `GET /api/activities`. * Added `GET /api/activities/{activity_id}`. * Activity queries support a 30-day window. # MCP Connectors Source: https://docs.zerodrift.com/connecting-mcp Connect Notion, Linear, Confluence, or Google Drive through the Connections Service and turn a source document into an enforceable policy. ZeroDrift's Connections Service talks to a provider's MCP server as a read-only client. You sign in with your own Notion, Linear, Confluence, or Google Drive account, browse what that account can already see, pick a document, and ZeroDrift archives it and runs the same custom-policy import pipeline as a file upload. This page covers what the connection can access, how to set up each live connector, what happens after you pick a document, and how to diagnose a failed sync. Today, connections use the account of the person who clicks **Connect**. ZeroDrift does not write back to the provider, and it does not use a service account. ## What a connection can access A connection acts as the person who completed OAuth. It can read documents that person can already open in the provider. It does not combine content across accounts, and it only calls the read tools ZeroDrift registered for that provider. | Boundary | What it means | | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Acting user | The OAuth grant is for one human account. Switching Notion, Linear, Atlassian, or Google accounts requires disconnecting and connecting again. | | Read-only | ZeroDrift only calls read tools. For Confluence, the Atlassian grant is product-level and can include write scopes on the token; the Connections Service still refuses every write tool. | | One provider per connection | Notion, Linear, Confluence, and Google Drive are separate sources. A Linear issue is not visible through a Notion connection. | | No search language | Confluence CQL and similar query languages are not exposed. You browse the tree; you do not send an operator string. | | No scheduled re-sync | Folder watch and daily re-sync are not available, even though the Sources UI still advertises daily sync. Re-sync a document when you want to refresh the policy. | | Connector | Status | What you can browse | | ---------------------- | ------ | ------------------------------------------------------------ | | Notion | Live | Pages and databases the connected Notion account can read | | Linear | Live | Documents and issues the connected Linear account can read | | Confluence (Atlassian) | Live | Spaces and pages on sites included in the Atlassian grant | | Google Drive | Live | Folders and files under My Drive, Shared with me, and Recent | ## Set-up guide ### Prerequisites * A ZeroDrift Command workspace with access to **Settings → Sources** and **Policy Studio** * An account in Notion, Linear, Confluence, or Google Drive that can already open the documents you want to import * For Confluence: an Atlassian org that allows the classic Rovo MCP consent (see [Confluence](#confluence-atlassian) if consent is blocked) ### How to connect The flow is the same for every live connector: connect, browse, pick, sync. 1. In Command, open **Settings → Sources**. 2. Choose **Notion**, **Linear**, **Confluence**, or **Google Drive**, then **Connect**. 3. Complete the provider's OAuth screen in the new tab. Grant only the workspace, site, or pages you want ZeroDrift to read. 4. When the tab returns you to Command, the source is connected. Open it to browse the document tree. 5. Select one or more documents and sync. Each file is archived, then starts a custom policy import. 6. Open **Policy Studio**. The document appears as a custom policy import (`importing` while extraction runs, then pending review, failed with a reason, or ready to enforce). You can also start from Policy Studio: **Add Policy** → import from a connected source. ### Notion 1. On **Settings → Sources**, select **Notion** → **Connect**. Settings → Sources page listing Notion, Linear, Confluence, and Google Drive with Connect buttons Connect Notion panel listing read-only access, you pick the documents, and a kept-in-sync-daily note before authorizing The Connect panel and the Sources cards say picked documents are kept in sync daily. Scheduled re-sync is not live yet — re-sync a document yourself when you want to refresh the policy. 2. Sign in to Notion and approve access for the pages or workspace ZeroDrift should read. Notion OAuth screen granting mcp-dev.zerodrift.ai access to the ZeroDrift workspace Settings → Sources page showing Notion as Connected 3. Back in Command, open the Notion source. Browse pages and databases the connected account can see. 4. Pick the policy document and sync. Add policies from Notion panel browsing Teamspaces, Shared, and Private folders to pick documents If browse is empty, confirm you approved the Notion workspace that holds the policy, not a different workspace on the same email. ### Linear 1. On **Settings → Sources**, select **Linear** → **Connect**. Settings → Sources page with Linear not yet connected Connect Linear panel describing read-only access, document picking, and daily sync before authorizing 2. Sign in to Linear and approve access. Linear MCP authorization screen showing ZeroDrift requesting read access, with Approve and Cancel buttons 3. Browse documents and issues the connected Linear account can read. Settings → Sources page showing Linear as Connected Add policies from Linear panel with Documents, Issues, and Teams filters to pick items 4. Pick the item that holds the policy text and sync. If a document you can open in Linear does not appear, disconnect and connect again, then browse from the workspace root. Do not paste an issue id into the tree. ### Confluence (Atlassian) Confluence uses Atlassian's classic MCP facade (`/v1/mcp`), not the granular `/v1/mcp/authv2` path. Authv2 cannot finish consent against a real Atlassian org while **Permissions** on the Rovo MCP server are blocked. 1. On **Settings → Sources**, select **Confluence** → **Connect**. Settings → Sources page with Confluence not yet connected Connect Confluence panel describing read-only access, document picking, and daily sync before authorizing 2. Complete Atlassian consent. The screen is product checkboxes, not a narrowed scope list. Atlassian Rovo MCP authorization screen showing ZeroDrift requesting access to Jira, Confluence, and Compass 3. Browse spaces, then pages. Expand one level at a time; opening a space homepage does not list every page in the space. Settings → Sources page showing Confluence as Connected 4. Pick pages and sync. If consent says **Permissions are blocked for this site**, an Atlassian admin needs to unblock the Rovo MCP server: 1. Open Atlassian Administration. 2. Go to **Rovo** → **Rovo MCP server** → **Permissions**. 3. Allow the site, then retry **Connect** in Command. A connection created before Confluence browse shipped can return `409` when you try to browse. Disconnect and connect again so the grant includes the read tools. ### Google Drive 1. On **Settings → Sources**, select **Google Drive** → **Connect**. Settings → Sources page with Google Drive not yet connected Connect Google Drive panel describing read-only access, document picking, and daily sync before authorizing 2. Sign in with the Google account that can open the files you want to import, and approve access. 3. Browse from **All locations**. The first level is **My Drive**, **Shared with me**, and **Recent** — places to look, not a flat file list. Settings → Sources page showing Google Drive as Connected Add policies from Google Drive panel with All locations, My Drive, Shared with me, and Recent 4. Open a folder, pick files, and sync. Trashed files can still appear in listings; skip them. Drive file search from Command is browse-only — do not paste a Drive query string into the tree. ## After you pick a document A successful sync does not stop at archive. ZeroDrift: 1. Stores the document with provenance on the record (author, last edited time, source URL when the provider supplies one, and connection id). 2. Sends the bytes through the same extraction path as [Import Policy](/api-reference/custom-policies/import-policy). 3. Records the `import_id`. Status is `processing` until extraction finishes. 4. Surfaces the row in Policy Studio as a custom policy import. Re-syncing a document that already produced a policy **refreshes that policy** instead of creating a second one. The previous import's rules are retired before the replacement is activated, so you do not enforce both versions. Unchanged content is skipped on re-sync **only if** an `import_id` already exists. A document archived before imports existed still produces one on the next sync. If the import pipeline refuses the file, the sync fails with a reason you can read and the archived object is kept. The import is not shown as reviewable. From there the API path is unchanged: poll [Get Import Details](/api-reference/custom-policies/get-import-details), then [Activate Rules](/api-reference/custom-policies/activate-rules). To train a policy-specific adapter on that import, follow [Training Studio](/training-studio). ## Capabilities | Capability | What it does | Example | | ---------- | ------------------------------------------------------------------------------- | ------------------------------------------------------- | | Connect | User-delegated OAuth; credentials are stored per workspace | Connect Notion from **Settings → Sources** | | Browse | Read-only tree of what the connected account can see | Open Confluence, expand a space, list child pages | | Sync | Fetch selected documents, archive them, start policy import | Pick a Notion page named "Communications policy" | | Refresh | Re-sync updates the same custom policy; superseded rules are retired first | Sync the Linear doc again after the policy text changes | | Revoke | Disconnect is final; ZeroDrift cannot call the provider until you connect again | Disconnect Linear on the Sources page | ZeroDrift does not expose these as public MCP tools for Claude or ChatGPT. The Connections Service is the MCP client. The public API you call after sync is the Custom Policies API. ## Troubleshooting ### The error names the provider, not "unreachable" When a provider rejects a call, Command shows that rejection. A 401 or 403 from Notion, Linear, Atlassian, or Google Drive means the grant is dead or forbidden. Reconnect the source. ZeroDrift does not keep retrying those failures. A transport timeout or DNS failure is different: you can retry that. If the UI still says the service is unreachable after a successful connect, check network access to Command, not the provider's status page first. ### Import failed, and the reason is on the row Failed imports show why they failed and are not presented as ready for review. Fix the document or the connection, then sync again. A generic "unreachable" message is no longer the expected failure text. ### Confluence consent never finishes If **Connect** fails with **Permissions are blocked for this site**, the block is in Atlassian Administration → **Rovo** → **Rovo MCP server** → **Permissions**, not in ZeroDrift. ### Browse returns 409 after connecting Confluence Disconnect and connect again. A grant created before Confluence browse shipped can omit the read tools. Browse stays refused until you reconnect. ### Re-sync created overlapping rules It should not. A refresh retires the previous import's rules before activating the new extraction. If you activated an older import through the API while a refresh was in flight, use [Deactivate Rules](/api-reference/custom-policies/deactivate-rules) on the superseded `import_id`. ### The Sources page cannot browse a connected source The UI now says what to do when a source will not browse, instead of failing with no next step. Follow that message. Typical causes: expired OAuth, Confluence permissions, or a connection that needs a reconnect. ## FAQ No. This feature connects ZeroDrift **to** Notion, Linear, Confluence, and Google Drive MCP servers so Command can import policy documents. It is not a way to query ZeroDrift from another AI assistant. No. Connecting and browsing happens in Command with OAuth. Use an API key after the document is a policy import: poll extraction, activate rules, or validate with that import. No. Connections are read-only. Even when Atlassian issues a token that includes write scopes, ZeroDrift only permits the read tools required to list and fetch pages. The same is true for Google Drive: ZeroDrift can fetch file bytes, not create or edit files. Re-sync skips extraction when the document has not changed **and** it already has an `import_id`. If there is no import yet, ZeroDrift still creates one. Not today. Every connection is the person who clicked **Connect**. # Data Retention Source: https://docs.zerodrift.com/data-retention How long ZeroDrift retains customer content, enforcement verdicts, and activity records. # Data Retention > How ZeroDrift retains and deletes customer data processed through the platform. This page is the customer-facing data retention policy for the ZeroDrift platform. It supplements the [Privacy Statement](https://zerodrift.com/legal/privacy-statement) and [Data Processing Practices](https://zerodrift.com/legal/data-processing-practices). Contractual terms in your Customer Agreement or DPA control if they differ. ## Principles * **Default to deletion.** Customer content is kept only as long as needed to deliver the service, then deleted. * **Contracts can extend retention.** Enterprise agreements may require longer retention for exam-ready records. * **No model training.** Customer data is not used to train, tune, or improve ZeroDrift or third-party AI models. * **Deletion is automated.** Retention is enforced with scheduled deletion jobs and object-store lifecycle rules. ## What we store | Category | Examples | | --------------------------- | ----------------------------------------------------------------------------- | | Customer content | Message bodies, content, and files submitted for enforcement or policy import | | Transient uploads | Objects written to presigned S3 URLs before enforcement starts | | Enforcement jobs & verdicts | Verdicts, rules cited, and fixes returned by the API | | Activity / audit records | Compact activity events (verdict, rules cited, timeline) | | Workspace configuration | Users, API keys, rule packs, custom rules, and integration settings | ZeroDrift acts as a **processor** for customer-submitted communications content and processes it only on your documented instructions. ## Default retention schedule Unless your Customer Agreement or DPA states otherwise: | Data class | Default retention | | ----------------------------------------------- | ------------------------------------------------------------------------- | | Customer content submitted to the platform | **7 days**, then deleted | | Transient presigned upload objects (S3 staging) | **24 hours** | | Enforcement job records and verdicts | **7 days**, aligned with the underlying content | | Activity records | Available in Command and via the Activity endpoints for up to **30 days** | | Workspace configuration | Life of the customer relationship **+ 90 days** after termination | | Product audit logs (admin/user actions) | Life of the relationship **+ up to 24 months** after termination | | Aggregated / de-identified operational metrics | Retained as needed for capacity and reliability (no message content) | ### Contractual overrides If your agreement requires ZeroDrift to retain regulated communications or audit artefacts longer than the defaults (for example, to support your SEC/FINRA recordkeeping obligations), ZeroDrift retains the covered data for the longer of the contractual minimum and the default above. Shorter retention — including discard immediately after the response is returned — may be available under enterprise arrangements. Contact [support@zerodrift.ai](mailto:support@zerodrift.ai) or your ZeroDrift account team. ## Deletion and offboarding * Send deletion or return requests through the channel in your Customer Agreement, or to [support@zerodrift.ai](mailto:support@zerodrift.ai). * Verified requests are acknowledged within **5 business days** and approved deletions completed within **30 days** (backups cleared on the next backup cycle), except where a legal hold or contractual/regulatory minimum retention applies. * On termination, the data-handling clause in your Customer Agreement governs return or deletion. Where the agreement is silent, remaining customer data is returned or deleted at your election, except where retention is required by law. ## Subprocessors and AI providers Customer data may be processed by cloud infrastructure and AI model subprocessors solely to deliver the service. Current model providers include OpenAI and Anthropic. Those providers process data to deliver the inference and do not retain it for their own purposes or use it to train their models. See the current subprocessor list in the [ZeroDrift Trust Center](https://app.vanta.com/zerodrift.ai/trust/8dwp517ay48r0q3abhwy09). ## Support Questions about retention, deletion, or contractual overrides: [support@zerodrift.ai](mailto:support@zerodrift.ai) # SDKs Source: https://docs.zerodrift.com/generate-sdk Generate a typed Python or TypeScript client from ZeroDrift's public OpenAPI spec ZeroDrift publishes the public API as an OpenAPI 3.0 document. You generate a typed client from that file; ZeroDrift does not ship a first-party SDK package. The spec lives at: ```text theme={null} https://docs.zerodrift.com/openapi.json ``` See [OpenAPI specification](/api-reference/openapi) for the operations it covers. Authenticate every call with the `x-api-key` header. Base URL is `https://api.zerodrift.ai`. ## Generate with an assistant Download the spec, attach `openapi.json` to your coding assistant, and ask it to write a typed client. Name the API groups you need so the generated file covers them. Example prompt: ```text theme={null} Write a typed TypeScript client for this API — cover enforce, policy, activity, and rulepacks. ``` Use `https://docs.zerodrift.com/openapi.json` as the source, not a path on `api.zerodrift.ai`. After generation, confirm the client sends `x-api-key` and calls `https://api.zerodrift.ai`. ## TypeScript with openapi-typescript [`openapi-typescript`](https://github.com/openapi-ts/openapi-typescript) turns the spec into TypeScript types. It does not emit an HTTP client; you call `fetch` (or your own wrapper) and type the requests with the generated `paths` interface. ```bash theme={null} npx --yes openapi-typescript https://docs.zerodrift.com/openapi.json -o zerodrift.ts ``` That command writes `zerodrift.ts` with `paths` and `components`. Use it like this: ```ts theme={null} import type { paths } from "./zerodrift"; const BASE_URL = "https://api.zerodrift.ai"; type ValidateBody = paths["/api/v3/content/validate"]["post"]["requestBody"]["content"]["application/json"]; type ValidateResponse = paths["/api/v3/content/validate"]["post"]["responses"]["200"]["content"]["application/json"] | paths["/api/v3/content/validate"]["post"]["responses"]["202"]["content"]["application/json"]; export async function validateContent( apiKey: string, body: ValidateBody ): Promise { const response = await fetch(`${BASE_URL}/api/v3/content/validate`, { method: "POST", headers: { "x-api-key": apiKey, "Content-Type": "application/json", }, body: JSON.stringify(body), }); if (!response.ok) { throw new Error(`ZeroDrift API error ${response.status}: ${await response.text()}`); } return response.json() as Promise; } const result = await validateContent("YOUR_API_KEY", { content: "Our fund guarantees 20% returns with zero risk!", document_category: "scenario_retail_investor_letter", mode: "sync", model_engine: "anchor_3_0", }); ``` Regenerate `zerodrift.ts` when you pick up a new spec. ## Python with OpenAPI Generator [OpenAPI Generator](https://openapi-generator.tech/) emits a full Python client. It needs Java, or Docker. ```bash theme={null} docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate \ -i https://docs.zerodrift.com/openapi.json \ -g python \ -o /local/zerodrift-python \ --additional-properties=packageName=zerodrift ``` Follow the generated README. Set the API key on the `ApiKeyHeader` scheme (the `x-api-key` header) and use host `https://api.zerodrift.ai`. If you do not want Java or Docker, [`openapi-python-client`](https://github.com/openapi-generators/openapi-python-client) generates a typed httpx client from the same URL: ```bash theme={null} pip install openapi-python-client openapi-python-client generate --url https://docs.zerodrift.com/openapi.json ``` That writes a package named from the spec title (`zero_drift_enforcement_api_client`). Pass the API key as a header. Do not use the default `Authorization: Bearer` client unless you override the header name. ```python theme={null} from zero_drift_enforcement_api_client import Client from zero_drift_enforcement_api_client.api.validate import validate_content from zero_drift_enforcement_api_client.models import ( ItemValidateV3Email, ItemValidateV3EmailMode, ItemValidateV3EmailModelEngine, ) client = Client( base_url="https://api.zerodrift.ai", headers={"x-api-key": "YOUR_API_KEY"}, ) result = validate_content.sync( client=client, body=ItemValidateV3Email( content="Our fund guarantees 20% returns with zero risk!", document_category="scenario_retail_investor_letter", mode=ItemValidateV3EmailMode.SYNC, model_engine=ItemValidateV3EmailModelEngine.ANCHOR_3_0, ), ) ``` Install the generated package into your environment (for example `pip install ./zero-drift-public-api-client`) before you import it. ## Other languages The same spec URL works with any OpenAPI 3.0 generator (`-g typescript-fetch`, Go, and so on). After codegen, check three things: 1. Requests go to `https://api.zerodrift.ai` 2. The API key is sent as `x-api-key`, not `Authorization` 3. You regenerate the client when the spec changes ZeroDrift does not publish `pip install zerodrift` or `npm i @zerodrift/sdk`. Keep the generated client in your repo or private registry. API reference pages also show basic request examples. Use the language selector in the upper right for **cURL**, **Python**, **JavaScript**, **PHP**, **Go**, **Java**, or **Ruby**. # Introduction Source: https://docs.zerodrift.com/index ZeroDrift Enforcement API - Enforce regulations and policies on AI content and communications ## Welcome to ZeroDrift **Preview:** The ZeroDrift API and this documentation are in preview. Endpoints and response formats may change before general availability. ZeroDrift is the Enforcement Runtime for AI. The Enforcement API checks content and communications against regulations, company policies, and security controls, and returns a verdict: pass, rewrite, block, or escalate. Enforce your first piece of content in minutes. Produce a typed Python or TypeScript client from the public OpenAPI spec. Connect Notion, Linear, Confluence, or Google Drive and turn a source document into an enforceable policy. Import a custom policy, train a policy-specific adapter, and enforce against it. ## What you can enforce * [Emails and chat messages](/api-reference/validate/validate-content) * Marketing and client communications * LLM and agent outputs # Quickstart Source: https://docs.zerodrift.com/quickstart Send your first piece of content through the Enforcement API in minutes ## Get started in three steps Every result is a verdict. Pass. Rewrite. Block. Escalate. Enforce your first piece of content with the ZeroDrift Enforcement API. ### Step 1: Get your API key API keys live in ZeroDrift Command. Sign in, open **Settings**, then under **API** select **Keys**. Command Settings → API → Keys empty state with Create first key 1. Select **Create first key** (or **New API key** if you already have keys). 2. In the **Create API key** dialog, enter a **Key name**, choose **Permissions** (Full access or Read only), and set **Rulepacks**. 3. Select **Create**, then **Copy** the key immediately — Command shows the full value only once. Use the key in the `x-api-key` header for all API requests: ```bash theme={null} x-api-key: YOUR_API_KEY ``` ### Step 2: Send content for enforcement The quickest way to test the API is with a text snippet: ```bash theme={null} curl -X POST "https://api.zerodrift.ai/api/v3/content/validate" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "Our fund guarantees 20% returns with zero risk!", "document_category": "scenario_retail_investor_letter" }' ``` You'll receive a `job_id` in the response: ```json theme={null} { "api_version": "v3", "job_id": "abc123def456", "status": "queued", "mode": "async", "model_engine": "anchor_3_0", "poll": { "method": "GET", "url": "/api/v3/jobs/abc123def456" } } ``` Enforcement runs asynchronously by default on Anchor, the compliance enforcement model (`"model_engine": "anchor_3_0"`). For short content you can set `"mode": "sync"` to receive the completed result in a single response — see [Enforce Content](/api-reference/validate/validate-content). The same endpoint can enforce policies in other regulated industries: ```bash theme={null} curl -X POST "https://api.zerodrift.ai/api/v3/content/validate" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "Send Jane Doe'\''s diagnosis and treatment plan to the entire partner mailing list.", "mode": "sync", "model_engine": "anchor_3_0" }' ``` ### Step 3: Get the results Use the `job_id` to retrieve the results: ```bash theme={null} curl "https://api.zerodrift.ai/api/v3/jobs/abc123def456" \ -H "x-api-key: YOUR_API_KEY" ``` Poll until `status` is `done` or `failed`. A completed response includes: ```json theme={null} { "api_version": "v3", "job_id": "abc123def456", "status": "done", "model_engine": "anchor_3_0", "overall_status": "do not send", "summary": { "do_not_send": 1, "send_with_caution": 0, "document_pages": 1 }, "violations_by_line": [ { "line_number": 1, "line_text": "Our fund guarantees 20% returns with zero risk!", "rule_count": 1, "rules_violated": [ { "rule_name": "Prohibited Promissory Language (Keywords)", "rule_ref": "FINRA 2210(d)(1)(B)", "severity": "do_not_send", "confidence": 0.92, "action": "replace" } ], "best_fix": { "severity": "do_not_send", "rule_name": "Prohibited Promissory Language (Keywords)", "rule_ref": "FINRA 2210(d)(1)(B)", "action": "replace", "suggested_text": "Our fund has historically delivered returns, though past performance does not guarantee future results.", "confidence": 0.92 } } ] } ``` ## Next steps Explore the full API capabilities: Full request and response details for the enforcement endpoint used above. Browse the managed rule pack catalog. Import a policy, train a policy-specific adapter, and enforce against it. Produce a typed Python or TypeScript client from the OpenAPI spec. Import a policy from Notion, Linear, Confluence, or Google Drive instead of uploading a file. **Need help?** Contact us at **[support@zerodrift.ai](mailto:support@zerodrift.ai)** # Security Source: https://docs.zerodrift.com/security Certifications, deployment, data handling, and how to request reports. ## Certifications and compliance ZeroDrift is SOC 2 Type II and ISO 27001 certified, and supports customers' GDPR and HIPAA compliance requirements. ## Deployment ZeroDrift supports multi-tenant cloud and private-cloud deployments. Customer- managed VPC and on-premises deployments are not generally available. Contact [support@zerodrift.ai](mailto:support@zerodrift.ai) to discuss deployment and data-residency requirements. ## Data handling * Content and verdict retention windows are documented in [Data Retention](/data-retention). * Customer data is not used to train ZeroDrift or third-party AI models. * Connected Notion, Linear, Confluence, and Google Drive sources are read-only. ## Access * API keys are shown once when created. * Keys can have read-only or full-access permissions. * Rulepacks can be scoped per key. ## Reports and subprocessors Review ZeroDrift's security posture, request SOC 2 and ISO 27001 reports, and see the current subprocessor list in the [ZeroDrift Trust Center](https://app.vanta.com/zerodrift.ai/trust/8dwp517ay48r0q3abhwy09). # Training Studio Source: https://docs.zerodrift.com/training-studio Train a policy-specific adapter from an imported custom policy, then enforce against it. Training Studio is where you train your own enforcement model on ZeroDrift's base. Anchor handles regulations. Your adapter handles your policies. Procedure: [Train an Adapter](/api-reference/training-studio/train-policy). Training Studio turns an imported custom policy into a **policy-specific adapter**. After the adapter is ready, enforcement scoped to that import uses the trained adapter instead of the base Anchor path. By default, activated custom rules run automatically during enforcement for your account; supplying `validation_scope` narrows evaluation to the selected active rules, rule packs, and imports. Training is the extra adapter step: it teaches the engine from the original policy document, not only from the extracted rule definitions. ## Terms | Term | Meaning | | ---------------- | -------------------------------------------------------------------------------------------------------------------- | | **Policy** | The imported policy document and the rules extracted from it. In the product this is also the adapter you train. | | **Adapter** | The policy-specific model trained from that import. After a successful training poll, scoped enforcement can use it. | | **Rules** | Extracted, reviewed, and activated requirements from the import. Edit them before activation if needed. | | **Training run** | One Training Studio job for an import. Identified by `training_run_id`. | ## What you need * A **full-access** API key (`x-api-key`). Training returns `403` for a read-only key. * An import whose extracted rules are **activated**. Training returns `409` while the import is still pending review, or if another training run is already in progress. * The **original imported document** still retained. Default content retention is **7 days** ([Data Retention](/data-retention)). If the document is gone, training returns `410` — re-import the policy, activate the rules, then train again. * Training enabled for your environment. If it is not configured, training returns `503`. ## Flow ``` import → poll extraction → review → activate → train → poll training → enforce ``` | Step | What to do | API | | ---------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1. Intake | Upload the policy as a file or plain text, or sync it from a connected source. Large files use the presigned upload flow. | [Import Policy](/api-reference/custom-policies/import-policy), [Get Import Presigned URL](/api-reference/custom-policies/import-presigned-url) then [Start Import](/api-reference/custom-policies/start-import), or [MCP Connectors](/connecting-mcp) | | 2. Wait for extraction | Poll until status is no longer `processing`. Continue when it is `pending_review`. | [Get Import Details](/api-reference/custom-policies/get-import-details) | | 3. Review | Inspect extracted rules. Edit a pending rule if needed. Extracted rule severity is `low`, `medium`, or `high`. | [Edit Imported Rule](/api-reference/custom-policies/edit-imported-rule) | | 4. Activate | Activate all extracted rules or a subset. Training is rejected until this succeeds. | [Activate Rules](/api-reference/custom-policies/activate-rules) | | 5. Train | Start adapter training. Body is optional: `cost_cap_usd` and `examples_per_rule`. | [Train Policy Adapter](/api-reference/training-studio/train-policy) | | 6. Poll | Poll until `status` is `succeeded` or `failed`. Stop on either terminal status. | [Get Training Status](/api-reference/training-studio/get-training-status) | | 7. Enforce | After `succeeded`, send `validation_scope.imports` with that `import_id`. | [Enforce Content](/api-reference/validate/validate-content) | Do not enforce against a still-training run. ZeroDrift promotes `training_run_id` only after a successful status poll. Until then, scoped enforcement follows its configured fallback path. ## Intake You need an `import_id` before you can train. Three ways to get one: 1. **Inline upload** — [Import Policy](/api-reference/custom-policies/import-policy) with `source: "file"` (base64) or `source: "text"`. Payload size is limited; use the presigned flow for large files. 2. **Presigned upload** — [Get Import Presigned URL](/api-reference/custom-policies/import-presigned-url), put the file on S3, then [Start Import](/api-reference/custom-policies/start-import). 3. **Connected source** — In Command, connect Notion, Linear, Confluence, or Google Drive and sync a document. That produces the same import pipeline. See [MCP Connectors](/connecting-mcp). Extraction runs in the background. Poll [Get Import Details](/api-reference/custom-policies/get-import-details) until status is `pending_review`. `no_rules_found` and `failed` are not trainable — fix the document and import again. While the import is pending review, you can edit a rule's prompt, fix note, or extraction confidence with [Edit Imported Rule](/api-reference/custom-policies/edit-imported-rule). Then [Activate Rules](/api-reference/custom-policies/activate-rules). Training returns `409` until activation succeeds. Activated rules run on every enforcement request for your API key. Training does not replace that step. ## What training does [Train Policy Adapter](/api-reference/training-studio/train-policy) starts an asynchronous run. Training Studio: 1. Re-reads the **original imported document** (not only the extracted rule list). 2. Generates and judges training examples from that document. 3. Trains a policy-specific adapter for that import. POST returns `202` with `training_run_id` and a `poll` object pointing at GET on the same path. Omit the body to use Training Studio defaults, or set: | Field | Meaning | | ------------------- | ---------------------------------------------------------------------------------------------------- | | `cost_cap_usd` | Optional maximum generation spend in US dollars for this run. | | `examples_per_rule` | Optional number of generated training examples per extracted rule. More examples usually costs more. | Only one training run can be in progress for an import. A second POST while a run is active returns `409`. ## Training status Poll [Get Training Status](/api-reference/training-studio/get-training-status) after you start a run. GET returns `404` if no run has been started. | `status` | Meaning | | ----------- | -------------------------------------------------------------------- | | `queued` | Run accepted; training has not finished. | | `succeeded` | Terminal. ZeroDrift records this run as the import's active adapter. | | `failed` | Terminal. Read `error`. Stop polling. | `stage` and `progress` (`completed` / `total`) appear when Training Studio reports them. Treat `succeeded` and `failed` as the only stop conditions. A successful poll is what **promotes** the adapter. Until that poll reports `succeeded`, enforcement scoped to the import uses the configured fallback path — not a half-trained run. A failed retraining attempt does not replace an earlier working adapter. ## Train then poll Reuse the same `import_id` for `POST` and `GET` on `/api/policies/import/{import_id}/train`. ```python theme={null} import time import requests API_BASE = "https://api.zerodrift.ai" API_KEY = "YOUR_API_KEY" IMPORT_ID = "550e8400-e29b-41d4-a716-446655440000" URL = f"{API_BASE}/api/policies/import/{IMPORT_ID}/train" HEADERS = {"x-api-key": API_KEY} started = requests.post(URL, headers=HEADERS, json={}) started.raise_for_status() print(started.json()) while True: response = requests.get(URL, headers=HEADERS) response.raise_for_status() training = response.json() print(training["status"]) if training["status"] in ("succeeded", "failed"): break time.sleep(10) if training["status"] == "failed": raise SystemExit(training.get("error", "Training failed")) ``` A successful start looks like: ```json theme={null} { "import_id": "550e8400-e29b-41d4-a716-446655440000", "training_run_id": "run-808961af", "status": "queued", "message": "Training started. Poll the training status endpoint for progress.", "poll": { "method": "GET", "url": "/api/policies/import/550e8400-e29b-41d4-a716-446655440000/train" } } ``` A successful poll looks like: ```json theme={null} { "import_id": "550e8400-e29b-41d4-a716-446655440000", "training_run_id": "run-808961af", "status": "succeeded", "stage": "complete", "progress": { "completed": 4, "total": 4 }, "error": null } ``` Optional training options (omit the body to use defaults): ```json theme={null} { "cost_cap_usd": 25, "examples_per_rule": 8 } ``` ## Enforce against the trained import After status is `succeeded`, submit content with `validation_scope.imports` set to that `import_id`. Use `model_engine: "anchor_3_0"`. ```bash theme={null} curl -X POST "https://api.zerodrift.ai/api/v3/content/validate" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content": "Our fund guarantees 20% returns with zero risk!", "document_category": "scenario_retail_investor_letter", "mode": "async", "model_engine": "anchor_3_0", "validation_scope": { "imports": ["550e8400-e29b-41d4-a716-446655440000"] } }' ``` Async submissions return a `job_id`. Poll [Get Verdict](/api-reference/validate/get-results) until the job is `done` or `failed`. ## Errors Field-level detail lives on the endpoint pages. Typical training responses: | Status | Meaning | See | | ------ | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | Invalid training options (`cost_cap_usd` or `examples_per_rule`) | [Train Policy Adapter](/api-reference/training-studio/train-policy) | | 403 | Full-access key required (POST), invalid key (GET), or the import belongs to another customer | [Train Policy Adapter](/api-reference/training-studio/train-policy), [Get Training Status](/api-reference/training-studio/get-training-status) | | 404 | Import not found, or no training run has been started (GET) | Same pages | | 409 | Import is not activated, or another training run is already in progress | [Train Policy Adapter](/api-reference/training-studio/train-policy) | | 410 | Original document expired; re-import and activate, then train | [Train Policy Adapter](/api-reference/training-studio/train-policy), [Data Retention](/data-retention) | | 422 | Stored document is too short to train, or Training Studio rejected it | [Train Policy Adapter](/api-reference/training-studio/train-policy) | | 502 | Training Studio request failed | Both training endpoints | | 503 | Training is not configured in this environment | Both training endpoints | ## FAQ No. [Activate Rules](/api-reference/custom-policies/activate-rules) puts extracted rules into enforcement immediately. Training Studio adds a policy-specific adapter on top of that. Yes. A connected-source import is the same `import_id` as a file upload. Activate it, then call the train endpoints. See [MCP Connectors](/connecting-mcp). Stop polling when `status` is `failed`. A failed run does not replace an earlier working adapter. Fix the cause (document, options, or environment) and start a new training run. Training needs the original imported file or text. After default [content retention](/data-retention) (7 days unless your contract says otherwise), that document is deleted. Import again, activate the new rules, then train. No training run has been started for that import. POST first, then poll. ## API reference POST `/api/policies/import/{import_id}/train` GET `/api/policies/import/{import_id}/train` Upload a policy and extract rules. Activate extracted rules so they run during enforcement. Scope enforcement with `validation_scope.imports`. Import a policy from a connected source instead of uploading a file.