Skip to main content
This page explains what analyze() returns. The shape is the same whether you call the HTTP endpoint, the Python SDK, or the TypeScript SDK; the SDKs add a small ergonomic layer on top.

High-level shape

{
  "request_id": "5b3f6c7e-7d24-4d40-9b12-3a59c01c6e91",
  "policy_id": "cdc7…",
  "policy_slug": "default-inbound",
  "overall_status": "TERMINATED_EARLY",
  "terminated_early": true,
  "termination_reason": {
    "analyzer": "adversarial_detection_analyzer",
    "rule": "score >= 0.85 AND output_match INJECTION/JAILBREAK"
  },
  "analyzer_results": {
    "adversarial_detection_analyzer": { ... },
    "safety_moderation_analyzer":     { "status": "SKIPPED" },
    "dlp_analyzer":                   { "status": "SKIPPED" },
    "url_analyzer":                   { "status": "SKIPPED" },
    "yara_analyzer":                  { "status": "SKIPPED" }
  },
  "aggregated_metrics": {
    "total_processing_time_ms": 38.4,
    "total_cost_usd": 0.0002
  }
}
The exact field names follow the OpenAPI schema in sdk/openapi/openapi.json; the auto-rendered API reference is the authoritative source.

Top-level fields

FieldMeaning
request_idEcho of the X-Request-ID response header. Always quote this in support tickets.
policy_id / policy_slugWhich combined policy ran.
overall_statusOK (every analyzer passed), TERMINATED_EARLY (a termination rule fired), or ERROR (one or more analyzers errored).
terminated_earlyBoolean shortcut for overall_status == "TERMINATED_EARLY".
termination_reasonPresent when terminated_early == true. Names the analyzer and the rule that fired.
analyzer_resultsPer-analyzer block. Status is OK, TERMINATED_EARLY, ERROR, or SKIPPED.
aggregated_metricsSum of per-analyzer wall-clock and cost. Only present when the policy’s default_telemetry is true.

SDK ergonomics

The SDKs unpack the body into a Decision object:
decision = agnes.analyze("...", policy="default-inbound")

decision.allowed         # bool — False if terminated_early else True
decision.blocked_by      # tuple[str, ...] — canonical names of analyzers that blocked
decision.reasons         # tuple[str, ...] — human-readable reasons
decision.request_id      # str
decision.raw             # dict — the full response body
blocked_by / blockedBy is the easiest lever for branching in application code:
if not decision.allowed:
    if "prompt-injection-jailbreak" in decision.blocked_by:
        return safe_refusal()
    if "sensitive-data" in decision.blocked_by:
        return scrub_and_retry(decision.raw)
    return generic_block_message()
The SDK translates server keys (adversarial_detection_analyzer) to canonical names (prompt-injection-jailbreak) so your code stays stable even if the server-side key flips later. See Versioning.

Per-analyzer blocks

Every analyzer reports its result in analyzer_results.<server_key>. The shape is consistent across analyzers:
FieldMeaning
statusOK, TERMINATED_EARLY, ERROR, SKIPPED.
outputAnalyzer-specific structured output. See the per-analyzer page.
metricsMap of metric name → value. The dashboard editor exposes the same names.
terminated_byPresent when this analyzer is the one that terminated the run.
errorPresent when status == "ERROR".
Skipped analyzers carry only status: "SKIPPED" — they were declared in the policy but never reached because an earlier analyzer terminated.

Example: an inbound block by the prompt-injection classifier

"analyzer_results": {
  "adversarial_detection_analyzer": {
    "status": "TERMINATED_EARLY",
    "output": {
      "label": "INJECTION/JAILBREAK",
      "score": 0.97
    },
    "metrics": {
      "score": 0.97,
      "inference_time_ms": 38.4
    },
    "terminated_by": {
      "rule": "score >= 0.85 AND output_match INJECTION/JAILBREAK",
      "metric": "score",
      "value": 0.97,
      "operator": ">="
    }
  },
  "safety_moderation_analyzer": { "status": "SKIPPED" },
  "dlp_analyzer":                { "status": "SKIPPED" },
  "url_analyzer":                { "status": "SKIPPED" },
  "yara_analyzer":               { "status": "SKIPPED" }
}
The classifier scored 0.97 on INJECTION/JAILBREAK, which crossed both the score threshold (>= 0.85) and the output match in default-inbound. The execution engine terminated immediately and flipped overall_status to TERMINATED_EARLY. The remaining analyzers were skipped.

Example: outbound flag by safety + SDP

"analyzer_results": {
  "safety_moderation_analyzer": {
    "status": "TERMINATED_EARLY",
    "output": {
      "is_safe": false,
      "categories": [
        { "name": "Hate Speech", "score": 0.88, "verdict": "violation" }
      ]
    },
    "metrics": {
      "max_violation_score": 0.88,
      "violation_category_count": 1,
      "inference_time_ms": 142.0
    }
  }
}
The safety judge fired on hate speech with confidence 0.88. The combined policy’s is_safe boolean rule terminated the run.

Errors at the analyzer level

A single analyzer error does not always mean the whole call errored. Behavior depends on the step type:
  • In a sequential step, the first analyzer that errors stops the step and flips overall_status to ERROR.
  • In an asynchronous step, every analyzer in the group still runs to completion. Any error in the group flips overall_status to ERROR, but you’ll see the other analyzers’ results too.
When overall_status == "ERROR" and the cause is upstream infrastructure (model service unreachable, DLP API down), the API response is HTTP 503 with code: "analyzer_unavailable" instead of a 200 with embedded errors. SDKs retry these automatically. See analyzer_unavailable.

Termination reason

When terminated_early == true, the response carries a structured termination_reason:
FieldMeaning
analyzerThe server key of the analyzer whose rule fired.
ruleA human-readable description of which signal terminated.
matchPresent when output_match was used. The matched substring.
metric / value / operatorPresent when a threshold rule fired.
This is the easiest way to surface a precise message to your end user without parsing every analyzer block.

Aggregated metrics

When the policy’s default_telemetry is true, the response carries totals across the run:
MetricMeaning
total_processing_time_msSum of analyzer wall-clock. Useful for SLO tracking.
total_cost_usdSum of analyzer cost (when each analyzer reports a cost).
The numbers are summed only over analyzers that ran; skipped analyzers contribute zero.

Headers worth inspecting

  • X-Request-ID — same value as request_id in the body.
  • X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset — current rate-limit window state.
  • X-Billing-Status, X-Subscription-Status — subscription health. active is normal; past_due / canceled flag attention.
  • X-Agnes-Test-Mode: true — sandbox response. Exclude from billing dashboards.

Common questions

Why does blocked_by have multiple analyzers? Asynchronous steps can have more than one analyzer terminate in the same step. The SDK reports every analyzer that fired a terminate_immediately rule. Where is the prompt in the response? It is not echoed back. The SDKs keep the input you sent locally; the server does not persist it unless you explicitly ingest into the threat-intel store. Can I get the cost in tokens, not dollars? total_cost_usd is the public field. The metered usage report at agnes.lasscyber.com/agnes-info/billing shows token-level metering for billing reconciliation.

Next

  • Errors — what every error response looks like.
  • Combined analyzer — author the termination rules that decide what terminated_early means.
  • API reference — full auto-rendered schema with interactive playground.