> ## 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 (the OpenAPI flattened fields are the same names). Poll until `status` is `completed` or `done`. 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](/api-reference/validate/validate-content) with `mode: "sync"`. If the response reports `mode: "async"`, poll [Get Verdict](/api-reference/validate/get-results) until the job is terminal (see [Long-Form Content](/guides/long-form-content)).

<CodeGroup>
  ```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", "done", "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,
      *,
      scenario: str | None = None,
      scope: dict | None = None,
      meta: dict | None = None,
  ) -> Outcome:
      body = {
          "content": content,
          "model_engine": "anchor_3_0",
          "mode": "sync",
      }
      if scenario:
          body["document_category"] = scenario
      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", "done", "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: { scenario?: string; scope?: object; meta?: object } = {},
  ): Promise<Outcome> {
    const body: Record<string, unknown> = {
      content,
      model_engine: "anchor_3_0",
      mode: "sync",
    };
    if (opts.scenario) body.document_category = opts.scenario;
    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);
  ```
</CodeGroup>

## 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.
* Do not poll only for `done`. Live jobs complete as `completed`.

## Next

<CardGroup cols={2}>
  <Card title="Enforce a Chatbot" icon="comments" href="/guides/enforce-a-chatbot">
    Apply this handler on every chatbot reply before delivery.
  </Card>

  <Card title="Agent-to-Agent Traffic" icon="diagram-project" href="/guides/agent-to-agent">
    Enforce each hop in a multi-agent workflow.
  </Card>

  <Card title="Export Activity" icon="file-export" href="/guides/export-activity">
    Tune your threshold from real verdicts.
  </Card>
</CardGroup>
