Skip to main content
Guardrails let you run content safety checks before sending a prompt to an LLM (pre-check) and before returning the completion to your application (post-check). Each check is evaluated against the guardrail policies you configure in the Zespan dashboard, and the SDK either allows the call to proceed, applies redaction, or throws a GuardrailBlockedError.

Enabling guardrails

Pass guardrails: true (guardrails=True in Python) to any wrapper to enable both pre- and post-LLM checks with default settings.
When guardrails are enabled, all calls through this client run a pre-check on the prompt and a post-check on the completion. If any guardrail blocks the content, a GuardrailBlockedError is thrown before the LLM call is made (pre) or before the result is returned (post).
In TypeScript, wrapOpenAI() returns a wrapped client instance you call directly. In Python, patch_openai() monkey-patches the openai module in place — import openai (or construct openai.OpenAI()) after calling patch_openai(), then call it exactly as you would unpatched.

Fine-grained configuration

Pass a configuration object instead of true/True to control exactly which phases run and how errors in the guardrail service are handled.
boolean
default:"true"
When true, runs a content check on the prompt before the LLM call is made. A block at this phase prevents the LLM from ever receiving the prompt.
boolean
default:"true"
When true, runs a content check on the completion before it is returned to your application. A block at this phase prevents unsafe completions from reaching users.
boolean
default:"false"
Controls behavior when the guardrail service itself returns an error (e.g. network timeout, service unavailable). Named fail_closed in Python.
  • false (default): errors from the guardrail service are swallowed and the LLM call proceeds normally.
  • true: errors from the guardrail service are re-thrown, blocking the LLM call. Use this in high-risk applications where you prefer to fail safe.

Handling GuardrailBlockedError

When a guardrail policy blocks content, the SDK throws (Python: raises) a GuardrailBlockedError. Catch it to handle the block gracefully — for example, by returning a fallback response to the user.
Python SDK before the October 2026 release: guardrail checks from Python went to a URL without the /v1 prefix and got a 404, so with the default fail_closed=False every check was silently skipped and the call went through (with fail_closed=True every guarded call raised). Upgrading makes the checks run for real: a rule whose action is block (or a policy applied at deny) now raises GuardrailBlockedError from Python, and each guarded call waits for the check, bounded by its timeout (5 seconds by default). Rules at warn or log, and policies at dryrun or warn, only record. If you turned guardrails on in Python earlier, review your enabled rules before upgrading, or set them to warn first. A check that errors or times out still lets the call through unless you set fail_closed=True.

GuardrailBlockedError properties

Both SDKs expose the same two attributes on the error instance.
string
Which phase was blocked: "pre" (input check) or "post" (output check).
GuardrailResult[]
Array (Python: list) of results from individual guardrail policies that ran. Each result describes what the guardrail found and what action it took.

GuardrailResult fields

Each entry in error.results represents one guardrail policy evaluation:
string
Unique identifier of the guardrail policy that produced this result. Named guardrail_slug in Python.
boolean
true if this guardrail allowed the content, false if it triggered.
string
What the guardrail did. One of "allowed", "blocked", "redacted", or "warned".
  • "allowed" — content passed without modification
  • "blocked" — content was rejected; GuardrailBlockedError is thrown
  • "redacted" — sensitive content was removed and the modified text is used instead
  • "warned" — content was flagged but allowed through
string
Human-readable explanation of why this guardrail triggered, or null (Python: None) if it did not trigger.
string
The redacted version of the text, or null (Python: None) if no modification was made. When a guardrail redacts content, the modified text is used in place of the original for the LLM call or returned response. Named modified_text in Python.

Guardrails on other wrappers

Guardrails work on all provider wrappers. Pass the same guardrails option to wrapAnthropic, wrapGoogle, wrapGoogleGenAI, wrapOpenRouter, wrapBedrock, wrapMistral, wrapLiteLLM, or the LangChain callback handler.
The Python SDK accepts the same guardrails keyword argument on patch_anthropic, patch_google, patch_google_genai, patch_bedrock, patch_mistral, patch_litellm, and patch_groq.
patch_openrouter() in the Python SDK does not currently accept a guardrails argument — it delegates to patch_openai() with no options. If you need guardrails on OpenRouter traffic in Python today, call patch_openai(guardrails=...) against the OpenAI-compatible client you use for OpenRouter, or check content with the direct API instead.
If you trace multi-agent workflows with withAgent, the agent name and its recent tool calls are automatically included in every guardrail check made inside that block — no extra wiring needed. This lets agent-specific guardrail policies (agent rate limiting, tool misuse, loop detection, scope enforcement, delegation control) evaluate agent behavior alongside standard content checks.Both SDKs send the tool the model asked for, its arguments and the earlier tool calls of the trace with the check made after the model replies, including when the reply is only tool calls (one check per call).
Configure your guardrail policies in the Zespan dashboard under Settings → Guardrails. Policies are evaluated server-side, so you can update them without redeploying your application.

Calling guardrails directly

The guardrails wrapper option only checks content that flows through an LLM wrapper call. Sometimes you need to gate something that isn’t an LLM call at all — a RAG retrieval step before it’s fed into a prompt, a tool result, or any other non-LLM action. For that, call the guardrail check directly instead of going through a wrapper.
Unlike the wrapper-level flow, this direct call never throws GuardrailBlockedError itself — it always resolves to a response object. Check allowed (result.allowed in TypeScript, result["allowed"] in Python) yourself and decide what to do: skip the step, raise your own error, log and continue, and so on.

Input fields

string
required
The content to check.
"pre" | "post"
required
Which phase this check represents. Purely descriptive at the call site — it’s passed straight through to whichever guardrail policies key off phase, and shows up in guardrail event logs.
string
The trace this check is associated with, for correlating the guardrail event with the rest of the trace in the dashboard. Named trace_id in Python.
string
The span this check is associated with. Named span_id in Python.
string
The model this check relates to, if any. Some guardrail policies scope their rules to specific models.
string
A label for what kind of call this is (e.g. "rag-retrieval", "chat"). Lets you scope guardrail policies to specific operations.
number
Estimated cost in USD of the operation being checked, in case a guardrail policy enforces a per-call cost ceiling. Named estimated_cost in Python.
number
Token count of the input being checked, in case a guardrail policy enforces a token limit. Named input_tokens in Python.
string
TypeScript only. Name of the agent making this call, for agent-scoped policies (agent rate limiting, scope enforcement, delegation control).
string
TypeScript only. Name of the tool being invoked, for tool-misuse and delegation-control policies that allowlist/blocklist specific tools.
Array<{ toolName: string; args?: string }>
TypeScript only. Recent tool calls in this trace (not including the current one), used by loop-detection and tool-misuse policies to spot repeated or excessive calls.
boolean
default:"true"
Python only. When the guardrail service request itself fails (network error, timeout, non-2xx response), fail_open=True (default) swallows the error and returns {"allowed": True, "results": [], "modifiedText": None}. Set fail_open=False to re-raise the underlying exception instead.
float
default:"5.0"
Python only. Request timeout in seconds for the guardrail check call.

Response shape

Both languages return the same three top-level fields: allowed (boolean), results (an array/list of per-policy results), and modifiedText — the redacted text, or null/None if nothing was modified.

Guardrail spans

Every check, direct or from a wrapper or integration, is recorded as a guardrail span under the span it checked, with the check’s latency and status and two metadata keys: zespan.guardrail.action (allowed, warned, redacted or blocked: the strictest action any rule took) and zespan.guardrail.slugs (the rules that fired). The span holds no checked text, no reason and no replacement text. A direct call that names no span (spanId in TypeScript, span_id in Python) checks the current span, so the check appears under the work it guarded. The span never changes the check: a blocked check still throws GuardrailBlockedError, and a check that fails still throws its own error (or, in Python with fail_open=True, returns the allowed fallback and the span is marked as an error). Each check adds one span, which counts toward your event quota. To stop recording it, set guardrailSpans: false (TypeScript) or guardrail_spans=False (Python) in the SDK options, or set the environment variable ZESPAN_GUARDRAIL_SPANS=off (also false, 0, no). Either one turns it off, it is read at call time, and the check itself is not affected.

Deadline

Each check tells Zespan how long the SDK will wait, using the x-zespan-deadline-ms header set to the check’s timeout (timeoutMs in TypeScript, timeout in Python; 5 seconds by default). Zespan fits its rules into that budget so you get an answer before the SDK gives up. AI rules (such as Semantic Judge) that it had no time to start come back as errored results, handled by their If the check can’t run setting, and their slugs are listed in an optional skippedForDeadline field on the response. In TypeScript, results is a typed array of GuardrailResult objects using the field names documented above (guardrailSlug, passed, action, reason, modifiedText).
In Python, zespan.check_guardrails() returns a plain dict — result["allowed"], result["results"], result["modifiedText"] — and each entry in result["results"] is also a raw dict using the same camelCase keys as the wire format (guardrailSlug, passed, action, reason, modifiedText, latencyMs), not the snake_case names used elsewhere in the Python SDK. Use dict-key access (r["guardrailSlug"] or r.get("reason")) when working with the direct API’s response in Python.By design, this differs from GuardrailBlockedError.results (used in the wrapper flow above), where each entry is meant to be a GuardrailResult object with snake_case attributes (guardrail_slug, modified_text, etc.) — but see the known-issue warning in Handling GuardrailBlockedError above: that conversion currently fails at runtime, so this direct API is the reliable option in Python today.
Reach for this direct API whenever the content you want checked doesn’t pass through a wrapped LLM call — a retrieved document before it’s inserted into a prompt, a tool’s return value, or a user-supplied value you’re about to act on.