> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zerodrift.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Act on Each Verdict

> The production handler: Pass, Fix, Block, Escalate, with thresholds and logging.

You have a verdict and need code that decides what the user sees. This is the reference handler every other guide builds on.

## The four outcomes

| Verdict | API today | Your code | User sees |
| - | - | - | - |
| **Pass** | `overall_status: "approved"` | Deliver as is. | Original content. |
| **Fix** | `overall_status: "needs_changes"`, a suggested **replace**, confidence at or above your bar | Apply each confident `replace`. Deliver. | Compliant content. |
| **Escalate** | `needs_changes` with any fix below your bar, grouped-only findings, `insert_after`/`remove`/`warning`, or empty line lists | Hold. Put it in a human queue with the rules cited. | A holding message, then a human follow-up. |
| **Block** | `overall_status: "do not send"` | Do not deliver. Return a fallback. Log. | A fallback message. |

Read `overall_status` from the nested `result` object on a completed job. Poll until `status` is `completed` or `failed`. Current Anchor responses use `violations[]` with `quote`, `fix.suggested_text`, and string `confidence` (`high` / `medium` / `low`). Treat `high` as auto-fix; escalate `medium` and `low`. If a response includes `violations_by_line[].best_fix` with a numeric confidence, compare it to `0.85`.

Tune from Activity: if humans approve almost every escalated fix, auto-apply `medium` as well; if they change auto-fixes often, require review even on `high`.

## Handler

Call [Enforce Content](https://docs.zerodrift.com/api-reference/validate/validate-content.md) with `mode: "sync"`. If the response reports `mode: "async"`, poll [Get Verdict](https://docs.zerodrift.com/api-reference/validate/get-results.md) until the job is terminal (see [Long-Form Content](https://docs.zerodrift.com/guides/long-form-content.md)).

```bash cURL theme={null}
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": "Our fund guarantees 20% returns with zero risk!",
    "model_engine": "anchor_3_0",
    "mode": "sync"
  }'
```

```python Python theme={null}
import os
import time
import logging
from dataclasses import dataclass

import requests

API = "https://api.zerodrift.ai"
HEADERS = {"x-api-key": os.environ["ZERODRIFT_API_KEY"]}
NUMERIC_THRESHOLD = 0.85
TERMINAL = {"completed", "failed"}


@dataclass
class Outcome:
    verdict: str  # "pass" | "fix" | "block" | "escalate"
    text: str | None
    rules: list[str]


def payload(job: dict) -> dict:
    inner = job.get("result")
    return inner if isinstance(inner, dict) else job


def confident(value) -> bool:
    if isinstance(value, str):
        return value == "high"
    try:
        return float(value) >= NUMERIC_THRESHOLD
    except (TypeError, ValueError):
        return False


def wait_for_job(poll_url: str, timeout_s: int = 180) -> dict:
    delay, waited = 1.0, 0.0
    while waited < timeout_s:
        r = requests.get(f"{API}{poll_url}", headers=HEADERS, timeout=10)
        r.raise_for_status()
        job = r.json()
        if job["status"] in TERMINAL:
            if job["status"] == "failed":
                raise RuntimeError(job.get("error", "enforcement failed"))
            return job
        time.sleep(delay)
        waited += delay
        delay = min(delay * 2, 5.0)
    raise TimeoutError(poll_url)


def violations_of(job: dict) -> list[dict]:
    body = payload(job)
    return body.get("violations_by_line") or body.get("violations") or []


def rule_label(entry: dict) -> str | None:
    return (
        entry.get("rule_ref")
        or entry.get("rule_id")
        or entry.get("signal_name")
        or entry.get("rule_name")
    )


def collect_rules(job: dict) -> list[str]:
    labels = set()
    for v in violations_of(job):
        if v.get("rules_violated"):
            for rv in v["rules_violated"]:
                if rule_label(rv):
                    labels.add(rule_label(rv))
        elif rule_label(v):
            labels.add(rule_label(v))
    return sorted(labels)


def apply_fixes(content: str, job: dict) -> str | None:
    items = violations_of(job)
    if not items:
        return None
    text = content
    for v in items:
        fix = v.get("best_fix") or v.get("fix") or {}
        action = fix.get("action") or "replace"
        suggested = fix.get("suggested_text")
        conf = fix.get("confidence", v.get("confidence"))
        if action != "replace" or not suggested or not confident(conf):
            return None
        if "line_number" in v:
            lines = text.split("\n")
            lines[v["line_number"] - 1] = suggested
            text = "\n".join(lines)
        else:
            quote = v.get("quote") or v.get("line_text")
            if not quote or quote not in text:
                return None
            text = text.replace(quote, suggested)
    return text


def enforce(
    content: str,
    *,
    scope: dict | None = None,
    meta: dict | None = None,
) -> Outcome:
    body = {
        "content": content,
        "model_engine": "anchor_3_0",
        "mode": "sync",
    }
    if scope:
        body["validation_scope"] = scope
    if meta:
        body["metadata"] = meta

    r = requests.post(
        f"{API}/api/v3/content/validate",
        headers=HEADERS,
        json=body,
        timeout=30,
    )
    r.raise_for_status()
    job = r.json()
    if job.get("mode") == "async":
        job = wait_for_job(job["poll"]["url"])

    rules = collect_rules(job)
    status = payload(job).get("overall_status")

    if status == "approved":
        return Outcome("pass", content, rules)
    if status == "do not send":
        logging.warning("blocked job=%s rules=%s", job.get("job_id"), rules)
        return Outcome("block", None, rules)

    fixed = apply_fixes(content, job)
    if fixed is None:
        return Outcome("escalate", None, rules)
    return Outcome("fix", fixed, rules)


outcome = enforce("Our fund guarantees 20% returns with zero risk!")
print(outcome)
```

```typescript TypeScript theme={null}
type Verdict = "pass" | "fix" | "block" | "escalate";

export interface Outcome {
  verdict: Verdict;
  text: string | null;
  rules: string[];
}

const API = "https://api.zerodrift.ai";
const HEADERS = {
  "x-api-key": process.env.ZERODRIFT_API_KEY!,
  "Content-Type": "application/json",
};
const NUMERIC_THRESHOLD = 0.85;
const TERMINAL = new Set(["completed", "failed"]);

function payload(job: any) {
  return job?.result && typeof job.result === "object" ? job.result : job;
}

function confident(value: unknown) {
  if (typeof value === "string") return value === "high";
  const n = Number(value);
  return Number.isFinite(n) && n >= NUMERIC_THRESHOLD;
}

export async function waitForJob(pollUrl: string, timeoutMs = 180_000) {
  let delay = 1000;
  let waited = 0;
  while (waited < timeoutMs) {
    const r = await fetch(`${API}${pollUrl}`, { headers: HEADERS });
    if (!r.ok) throw new Error(`ZeroDrift ${r.status}`);
    const job = await r.json();
    if (job.status === "failed") throw new Error(job.error ?? "enforcement failed");
    if (TERMINAL.has(job.status)) return job;
    await new Promise((res) => setTimeout(res, delay));
    waited += delay;
    delay = Math.min(delay * 2, 5000);
  }
  throw new Error(`timeout ${pollUrl}`);
}

function violationsOf(job: any): any[] {
  const body = payload(job);
  return body.violations_by_line ?? body.violations ?? [];
}

function ruleLabel(entry: any): string | undefined {
  return entry?.rule_ref || entry?.rule_id || entry?.signal_name || entry?.rule_name;
}

function collectRules(job: any): string[] {
  const labels = new Set<string>();
  for (const v of violationsOf(job)) {
    if (v.rules_violated) {
      for (const rv of v.rules_violated) {
        const label = ruleLabel(rv);
        if (label) labels.add(label);
      }
    } else {
      const label = ruleLabel(v);
      if (label) labels.add(label);
    }
  }
  return [...labels].sort();
}

function applyFixes(content: string, job: any): string | null {
  const items = violationsOf(job);
  if (!items.length) return null;
  let text = content;
  for (const v of items) {
    const fix = v.best_fix ?? v.fix ?? {};
    const action = fix.action ?? "replace";
    const suggested = fix.suggested_text;
    const conf = fix.confidence ?? v.confidence;
    if (action !== "replace" || !suggested || !confident(conf)) return null;
    if (v.line_number != null) {
      const lines = text.split("\n");
      lines[v.line_number - 1] = suggested;
      text = lines.join("\n");
    } else {
      const quote = v.quote ?? v.line_text;
      if (!quote || !text.includes(quote)) return null;
      text = text.replaceAll(quote, suggested);
    }
  }
  return text;
}

export async function enforce(
  content: string,
  opts: { scope?: object; meta?: object } = {},
): Promise<Outcome> {
  const body: Record<string, unknown> = {
    content,
    model_engine: "anchor_3_0",
    mode: "sync",
  };
  if (opts.scope) body.validation_scope = opts.scope;
  if (opts.meta) body.metadata = opts.meta;

  const r = await fetch(`${API}/api/v3/content/validate`, {
    method: "POST",
    headers: HEADERS,
    body: JSON.stringify(body),
  });
  if (!r.ok) throw new Error(`ZeroDrift ${r.status}`);
  let job = await r.json();
  if (job.mode === "async") job = await waitForJob(job.poll.url);

  const rules = collectRules(job);
  const status = payload(job).overall_status;

  if (status === "approved") return { verdict: "pass", text: content, rules };
  if (status === "do not send") return { verdict: "block", text: null, rules };

  const fixed = applyFixes(content, job);
  if (fixed == null) return { verdict: "escalate", text: null, rules };
  return { verdict: "fix", text: fixed, rules };
}

const outcome = await enforce("Our fund guarantees 20% returns with zero risk!");
console.log(outcome);
```

## Showing a reviewer why

Each violation includes a rule id (`rule_id` / `signal_name`) and the quoted text. Line-grouped responses add `rules_violated[].rule_ref` (for example `FINRA 2210(d)(1)(B)`). Surface the id, quote, severity, and confidence in your review UI.

## Do not

* Do not deliver the original text on Fix. The fix is the compliant version.
* Do not retry a Block with the same content. Block means no compliant version exists.
* Do not strip the disclosure a fix added to make the message shorter.
* Do not fail open silently. If the API is unreachable, log it and apply your fail-open or fail-closed policy on purpose.
* Poll until `completed` or `failed`.

## Next

> **[Enforce a Chatbot](https://docs.zerodrift.com/guides/enforce-a-chatbot.md)**
>
> Apply this handler on every chatbot reply before delivery.

> **[Agent-to-Agent Traffic](https://docs.zerodrift.com/guides/agent-to-agent.md)**
>
> Enforce each hop in a multi-agent workflow.

> **[Export Activity](https://docs.zerodrift.com/guides/export-activity.md)**
>
> Tune your threshold from real verdicts.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.