Enforce Content
curl --request POST \
--url https://api.zerodrift.ai/api/v3/content/validate \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"content": "<string>",
"model_engine": "<string>",
"mode": "<string>"
}
'import requests
url = "https://api.zerodrift.ai/api/v3/content/validate"
payload = {
"content": "<string>",
"model_engine": "<string>",
"mode": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({content: '<string>', model_engine: '<string>', mode: '<string>'})
};
fetch('https://api.zerodrift.ai/api/v3/content/validate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zerodrift.ai/api/v3/content/validate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'content' => '<string>',
'model_engine' => '<string>',
'mode' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.zerodrift.ai/api/v3/content/validate"
payload := strings.NewReader("{\n \"content\": \"<string>\",\n \"model_engine\": \"<string>\",\n \"mode\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.zerodrift.ai/api/v3/content/validate")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"content\": \"<string>\",\n \"model_engine\": \"<string>\",\n \"mode\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zerodrift.ai/api/v3/content/validate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"content\": \"<string>\",\n \"model_engine\": \"<string>\",\n \"mode\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"api_version": "v3",
"job_id": "a1b2c3",
"status": "queued",
"mode": "async",
"submitted_at": "2026-09-23T04:44:13.879781+00:00",
"model_engine": "anchor_3_0",
"poll": { "method": "GET", "url": "/api/v3/jobs/a1b2c3" }
}
{
"api_version": "v3",
"job_id": "a1b2c3",
"status": "queued",
"mode": "async",
"submitted_at": "2026-09-23T04:44:13.879781+00:00",
"model_engine": "anchor_3_0_max",
"poll": { "method": "GET", "url": "/api/v3/jobs/a1b2c3" },
"engine_upgrade_reason": "custom_rules"
}
{
"api_version": "v3",
"job_id": "a1b2c3",
"status": "completed",
"mode": "sync",
"submitted_at": "2026-09-23T04:44:13.879781+00:00",
"model_engine": "anchor_3_0",
"applied_validation_scope": {
"source": "request",
"rules_count": 235
},
"result": {
"overall_status": "approved",
"summary": {
"do_not_send": 0,
"send_with_caution": 0,
"document_pages": 1
},
"revalidation_recommended": false,
"relevant_rule_ids": [],
"units": 1,
"cost": {
"currency": "USD",
"amount": "0.01"
},
"violations": []
},
"extracted_text": null
}
Enforce
Enforce Content
Send content to Anchor, the compliance enforcement model, and get a verdict. Async or sync.
POST
/
api
/
v3
/
content
/
validate
Enforce Content
curl --request POST \
--url https://api.zerodrift.ai/api/v3/content/validate \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"content": "<string>",
"model_engine": "<string>",
"mode": "<string>"
}
'import requests
url = "https://api.zerodrift.ai/api/v3/content/validate"
payload = {
"content": "<string>",
"model_engine": "<string>",
"mode": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({content: '<string>', model_engine: '<string>', mode: '<string>'})
};
fetch('https://api.zerodrift.ai/api/v3/content/validate', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.zerodrift.ai/api/v3/content/validate",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'content' => '<string>',
'model_engine' => '<string>',
'mode' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.zerodrift.ai/api/v3/content/validate"
payload := strings.NewReader("{\n \"content\": \"<string>\",\n \"model_engine\": \"<string>\",\n \"mode\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.zerodrift.ai/api/v3/content/validate")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"content\": \"<string>\",\n \"model_engine\": \"<string>\",\n \"mode\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.zerodrift.ai/api/v3/content/validate")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"content\": \"<string>\",\n \"model_engine\": \"<string>\",\n \"mode\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"api_version": "v3",
"job_id": "a1b2c3",
"status": "queued",
"mode": "async",
"submitted_at": "2026-09-23T04:44:13.879781+00:00",
"model_engine": "anchor_3_0",
"poll": { "method": "GET", "url": "/api/v3/jobs/a1b2c3" }
}
{
"api_version": "v3",
"job_id": "a1b2c3",
"status": "queued",
"mode": "async",
"submitted_at": "2026-09-23T04:44:13.879781+00:00",
"model_engine": "anchor_3_0_max",
"poll": { "method": "GET", "url": "/api/v3/jobs/a1b2c3" },
"engine_upgrade_reason": "custom_rules"
}
{
"api_version": "v3",
"job_id": "a1b2c3",
"status": "completed",
"mode": "sync",
"submitted_at": "2026-09-23T04:44:13.879781+00:00",
"model_engine": "anchor_3_0",
"applied_validation_scope": {
"source": "request",
"rules_count": 235
},
"result": {
"overall_status": "approved",
"summary": {
"do_not_send": 0,
"send_with_caution": 0,
"document_pages": 1
},
"revalidation_recommended": false,
"relevant_rule_ids": [],
"units": 1,
"cost": {
"currency": "USD",
"amount": "0.01"
},
"violations": []
},
"extracted_text": null
}
Send content to Anchor, the compliance enforcement model, and get a verdict.
Requests require
Max billing for custom rules is in Pricing.
Custom rule IDs look like
A Flagship request that names the same custom rule is upgraded to Max and
can be accepted as async. Poll until
mode and model_engine. With mode: "async", the endpoint
returns a job_id you poll via
Get Verdict. Set
mode: "sync" to run enforcement inline and receive the completed result in
a single response.
Pick Mini, Flagship, or Max with model_engine. Flagship is anchor_3_0
(the default; older notes called this Base). See
Choose a model for when to use each tier, and the
Anchor 3.0 benchmark for
quality and latency.
Custom rules run on Max (anchor_3_0_max). A scope-less Flagship
(anchor_3_0) request stays on Flagship and checks the active managed catalog;
activated custom rules are not evaluated. If a Flagship request explicitly
names a cust_* rule, the API upgrades that request to Max. See
Custom rules.
Request Body
string
required
Plain text to enforce.
string
default:"anchor_3_0"
required
Enforcement model.
anchor_3_0_mini (Mini): detection-only yes/no plus a
whole-message flagged_rules list — no per-line violations, rewrite, or
validation_scope. anchor_3_0 (Flagship, default): per-line violations
plus rewrite; managed packs and trained adapters. anchor_3_0_max (Max):
per-line violations, no rewrite; managed and custom rule text at inference.
Ask for Max explicitly to enforce activated custom rules. See
Choose a model.string
required
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.object
Optional metadata merged into the job.
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.object
Optional nested form to narrow live enforcement to a subset of already-active
rules, rule packs, and imports. Resolve IDs from the Policy API — see
Rule and rulepack IDs. A scope-less Flagship call
defaults to the active managed catalog. Explicitly naming a custom rule
upgrades Flagship to Max.
Show validation_scope properties
Show validation_scope properties
array
Rule IDs to evaluate. Use
rule_id from
List Rules. Custom IDs are
prefixed cust_.array
Rule pack IDs to evaluate. Use
id from
Rule Packs.array
Import IDs to evaluate. Use
import_id from
Import a Policy.boolean
Run every active managed catalog rule, and nothing else. Do not combine
with
rules, rulepacks, or imports. This is the best-effort default
when a Flagship request omits validation_scope.Response
Returns202 Accepted for async submissions. Poll
Get Verdict for the verdict.
string
Always
v3.string
Unique identifier for the enforcement job.
string
queued for async submissions. For sync responses the completed result
envelope is returned inline with status: completed and the verdict nested
under result.string
Mode the request was actually processed in:
async or sync. A sync
request that fell back returns async.string
Time when the job was submitted.
string
Engine that served the request:
anchor_3_0_mini (Mini), anchor_3_0
(Flagship), or anchor_3_0_max (Max). This can differ from the request
when the API upgrades a Flagship call that named custom rules. See
Choose a model.string
Present when a Flagship request explicitly named custom rule text or a
cust_* rule and was upgraded to Max. A scope-less Flagship call does not
upgrade merely because the workspace has activated custom rules.object
Counts for the scope the job ran against:
source plus rules_count,
rulepacks_count, or imports_count. The API does not echo the full rule ID
list. A scope-less Flagship call reports source: "request" because the
server supplies its managed-only default.object
For the
sync response body (HTTP 200), read the verdict from result.
The same nested envelope is returned by Get Verdict
for async jobs, with "mode": "async". Public responses report billed
result.units and result.cost, not raw token counts.Custom rules
Untrained custom rules (created with Create Custom Rule or activated from an import) run on Max when Max is requested. A scope-less Flagship request checks the managed catalog only, even when custom rules are active. Flagship can check a custom policy through a trained adapter.| You send | What happens |
|---|---|
model_engine: "anchor_3_0" with no validation_scope | Flagship stays on Flagship and checks active managed rules only. Activated custom rules are not evaluated. |
model_engine: "anchor_3_0_max" with no validation_scope | Max checks the workspace’s activated managed and custom rules. |
model_engine: "anchor_3_0_max" and custom rules in validation_scope | Max runs that explicit scope. mode: "sync" completes inline when it fits the inline budget; otherwise the response falls back to async. |
model_engine: "anchor_3_0" and a cust_* ID in validation_scope.rules | The explicit custom-rule request upgrades to Max and reports engine_upgrade_reason: "custom_rules". |
model_engine: "anchor_3_0" after Train an Adapter | Flagship can check that import. Scope with validation_scope.imports. |
Max detects, it does not rewrite. A Max verdict carries a rule’s own fix note
when that rule has one, so
violations[].fix.suggested_text is empty for most
rules and custom rules never return a rewritten line. Treat an empty
suggested_text as Escalate — see
Act on Each Verdict. Flagship is the tier that
returns a rewrite; to get one for an uploaded policy, train an
adapter and scope the request to its import.Rule and rulepack IDs
The Policy API returns the IDsvalidation_scope expects. Copy them from these
endpoints rather than from the Command network panel.
| You need | Call | Field to copy |
|---|---|---|
| Custom or managed rule ID | GET /api/policies/rules (List Rules) | custom_rules[].rule_id or default_rules[].rule_id |
| One custom rule after create | POST /api/policies/rules (Create Custom Rule) | rule_id |
| Custom rule IDs from an import | POST /api/policies/import/{import_id}/activate (Activate Rules) | activated_rule_ids |
| Rulepack ID | GET /api/rulepacks/ (Rule Packs) | data[].id |
| Import ID | Policy import flow (Import a Policy) | import_id |
cust_{customer_id}_{slug} — for example
cust_abc123_required_terms. Rulepack IDs look like zd-us-securities.
Example
Enforce one custom rule on Max. This example checks whether a financial advisor improperly disclaims fiduciary responsibility. SetCUSTOM_RULE_ID
from List Rules; custom rule IDs are workspace-specific.
: "${CUSTOM_RULE_ID:?set CUSTOM_RULE_ID from GET /api/policies/rules}"
curl -X POST "https://api.zerodrift.ai/api/v3/content/validate" \
-H "x-api-key: $ZERODRIFT_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"content\": \"A. As your financial advisor, I am no longer in charge of the requirements for your investment account if you lose money that is completely on you.\",
\"model_engine\": \"anchor_3_0_max\",
\"mode\": \"sync\",
\"validation_scope\": {
\"rules\": [\"$CUSTOM_RULE_ID\"]
}
}"
import os
import requests
API = "https://api.zerodrift.ai"
HEADERS = {
"x-api-key": os.environ["ZERODRIFT_API_KEY"],
"Content-Type": "application/json",
}
custom_rule_id = os.environ["CUSTOM_RULE_ID"]
r = requests.post(
f"{API}/api/v3/content/validate",
headers=HEADERS,
json={
"content": (
"A. As your financial advisor, I am no longer in charge of the "
"requirements for your investment account if you lose money that "
"is completely on you."
),
"model_engine": "anchor_3_0_max",
"mode": "sync",
"validation_scope": {"rules": [custom_rule_id]},
},
timeout=30,
)
r.raise_for_status()
print(r.json())
const API = "https://api.zerodrift.ai";
const customRuleId = process.env.CUSTOM_RULE_ID;
const r = await fetch(`${API}/api/v3/content/validate`, {
method: "POST",
headers: {
"x-api-key": process.env.ZERODRIFT_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
content:
"A. As your financial advisor, I am no longer in charge of the requirements for your investment account if you lose money that is completely on you.",
model_engine: "anchor_3_0_max",
mode: "sync",
validation_scope: { rules: [customRuleId] },
}),
});
if (!r.ok) throw new Error(`ZeroDrift ${r.status}`);
console.log(await r.json());
status is completed or failed:
{
"api_version": "v3",
"job_id": "a1b2c3",
"status": "queued",
"mode": "async",
"submitted_at": "2026-09-23T04:44:13.879781+00:00",
"model_engine": "anchor_3_0_max",
"poll": { "method": "GET", "url": "/api/v3/jobs/a1b2c3" },
"engine_upgrade_reason": "custom_rules"
}
curl "https://api.zerodrift.ai/api/v3/jobs/a1b2c3" \
-H "x-api-key: $ZERODRIFT_API_KEY"
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 |
{
"api_version": "v3",
"job_id": "a1b2c3",
"status": "queued",
"mode": "async",
"submitted_at": "2026-09-23T04:44:13.879781+00:00",
"model_engine": "anchor_3_0",
"poll": { "method": "GET", "url": "/api/v3/jobs/a1b2c3" }
}
{
"api_version": "v3",
"job_id": "a1b2c3",
"status": "queued",
"mode": "async",
"submitted_at": "2026-09-23T04:44:13.879781+00:00",
"model_engine": "anchor_3_0_max",
"poll": { "method": "GET", "url": "/api/v3/jobs/a1b2c3" },
"engine_upgrade_reason": "custom_rules"
}
{
"api_version": "v3",
"job_id": "a1b2c3",
"status": "completed",
"mode": "sync",
"submitted_at": "2026-09-23T04:44:13.879781+00:00",
"model_engine": "anchor_3_0",
"applied_validation_scope": {
"source": "request",
"rules_count": 235
},
"result": {
"overall_status": "approved",
"summary": {
"do_not_send": 0,
"send_with_caution": 0,
"document_pages": 1
},
"revalidation_recommended": false,
"relevant_rule_ids": [],
"units": 1,
"cost": {
"currency": "USD",
"amount": "0.01"
},
"violations": []
},
"extracted_text": null
}

