Guardrails
Guardrails classify every tool call an agent makes into an action class — execute autonomously, route to human approval, or block — using deterministic, non-LLM guard expressions.
Overview
A guardrail is a standalone, versioned resource — separate from IAM policies. Where an IAM policy answers "may this caller invoke this endpoint?" at request time, a guardrail answers "may this agent take this specific action, with these arguments, in this context, on its own — or must a human sign off?". It maps tool calls to action classes (A/B/C/D) and gates class-B autonomy behind guard expressions evaluated at the tool-execution boundary — after the model produces the call and before anything touches the outside world. There is no LLM in the evaluation path.
Guardrails are the platform's single tool-call gating mechanism: class-C actions route into the approvals queue, guards read spend from usage metering, and expressions use the shared JSON Logic evaluator that orchestrations use. A guardrail is a reusable template: tools, agents, and projects each carry a guardrail_ids list, so it attaches at any of those three scopes, several can apply to one call, and the strictest decision wins — every added guardrail can only tighten the result, never loosen it.
See the Permissions Reference for the IAM action strings for this module.
Related Tutorials
- Gate a Dangerous Tool with Guardrails - Step 4 (Write the guardrail)
- Gate a Dangerous Tool with Guardrails - Step 5 (Dry-run every decision)
- Gate a Dangerous Tool with Guardrails - Step 10 (A failing guard: the tripwire)
- Gate a Dangerous Tool with Guardrails - Step 12 (Raise the floor for the whole project)
Data Model
Guardrail
| Field | Type | Description |
|---|---|---|
id | string | Public identifier prefixed with guard_ |
project_id | string | ID of the owning project |
name | string | Human-readable name |
description | string | Optional description |
version | integer | Incremented on every document write; prior versions are archived |
document | object | The action-class document (see below) |
context_tool_id | string | Optional tool the platform calls at evaluation time to fetch fresh guardrail context |
context_mode | string | null | How tool-fetched context combines with the caller-supplied context: merge (default) or replace. null when explicitly cleared |
created_at | string | ISO 8601 creation timestamp |
updated_at | string | ISO 8601 last-updated timestamp |
The document:
| Field | Type | Description |
|---|---|---|
class | string | object | A class literal ("A" | "B" | "C" | "D") or a JSON Logic expression returning one — see Classification |
default_class | string | Applied when the class expression returns anything other than a valid class (a missing key, null, a typo). Defaults to C (fail-closed) |
guard | object | A single JSON Logic expression; when the call classifies as B, it must evaluate truthy to execute autonomously. Compose multiple conditions with { "and": [...] } |
escalate | boolean | When true, a failing guard routes to approval instead of tripping fail-closed |
GuardrailVersion
An immutable archive of a guardrail's configuration at one version. Shares its shape — and the engine that reads and writes it — with AgentVersion.
| Field | Type | Description |
|---|---|---|
id | string | guard_ver_-prefixed public ID of the archived version |
guardrail_id | string | The guard_-prefixed guardrail this version belongs to |
version | integer | The archived version number |
config | object | The versioned surface at that version — today { document } and nothing else |
label | string | Optional human tag, e.g. pre-tightening; null when unset |
created_by | string | Public ID of the user whose action produced this version; null when there was none |
created_at | string | ISO 8601 timestamp |
Only the policy document is versioned. Name, description and the context binding are metadata — versioning them would make two version numbers denote the same policy, and the version number is what an evaluation record cites.
Key Concepts
Attachment
A guardrail attaches through a guardrail_ids array on one of three resources — not the way IAM policies attach to users and API keys. Every field is a list, so each scope can carry several composable guardrails:
- On a project — governs every tool call by every agent in the project: the baseline scope, a floor narrower scopes can only raise.
- On an agent — governs every tool call the agent makes, across all its bindings.
- On a tool — governs that tool wherever it is used, by any agent; binding a dangerous tool to a new agent can never silently escape classification.
Attach is cheap, detach is gated. Adding an id can only tighten the outcome, so it needs only the carrying resource's update permission (tools:UpdateTool, agents:UpdateAgent, projects:UpdateProject). Removing an id — at any scope — can loosen posture, so it additionally requires guardrails:DetachGuardrail. The floor can't be silently lowered from any scope.
Every guardrail that applies to a call evaluates, and the strictest decision wins, ordered blocked > tripwire > route_to_approval > execute; where several classify the same call as B, all their guards must pass. Composition is order-independent — A is the identity, so a guardrail returning "A" defers to the others. One guardrail_evaluation record is written per guardrail evaluated.
Action Classes
| Class | Meaning | Behavior |
|---|---|---|
| A | Read-only / harmless | Always execute; logged to the activity feed |
| B | Autonomous with a guard | Execute iff the guard passes; a failing guard trips fail-closed (or routes to approval — see Tripwires) |
| C | Human sign-off | Files an ApprovalItem (origin: tool_call); executes only on approval |
| D | Forbidden | The call is blocked at dispatch; the model receives a blocked tool result and continues its turn |
A class expression that returns anything other than "A" / "B" / "C" / "D" resolves to default_class, which itself defaults to C: a misconfigured or absent classification never grants autonomy.
The classes are also the platform's per-call delegation dial — how autonomy maturity grades map onto them is covered in Layers are concerns, not autonomy levels.
Class-C interception uses the return-pending mechanics the approvals queue defines: the call returns { "status": "pending_approval", "approval_id": …, "expires_at": … } as the tool result, and the turn completes normally. Approval then starts a continuation generation that executes the frozen (or edited) arguments; rejection executes nothing; and an expiry ends the chain without a continuation unless the agent opts in — see Agents → Approval Expiry.
A guardrail may carry an optional expires_in (seconds) in its document — the sign-off window for a class-C approval it files (default 24h). When several guardrails apply, the governing (strictest-matching) guardrail's expires_in wins; it applies wherever a guardrail files an approval — agent tool-dispatch and the orchestration tool node alike.
Classification
class is either a literal ({ "class": "C" } always requires sign-off) or a single JSON Logic expression returning the class, evaluated over the same three namespaces as guards (args.* / context.* / runtime.*). There is no rule list and no matching order: one expression, one result. Anything the expression doesn't account for falls through to default_class.
A guardrail reasons about this call, not about which tool it is: to gate several tools differently, create a guardrail per tool and attach each to its tool rather than branching on runtime.tool.name. This example classifies a budget-update call B below a threshold and C at or above it:
{
"default_class": "C",
"class": { "if": [{ "<": [{ "var": "args.amount" }, 500] }, "B", "C"] },
"guard": { "<=": [{ "var": "args.amount" }, { "var": "context.max_daily_budget" }] }
}
Guards and Guardrail Context
Both class and guard are single JSON Logic expressions — the same evaluator orchestration mappings use — with no eval and no LLM in the path. JSON Logic composes on its own (if/and/or/!), so there are no rule or guard arrays. Every var resolves against exactly three namespaces:
| Namespace | Source |
|---|---|
args.* | The proposed call's arguments (post preset-merge — the same frozen arguments an approval item records) |
context.* | The effective guardrail context — application-owned, see below |
runtime.* | Platform-computed values (fixed catalog below); reserved — never writable by the caller or the context tool |
Guardrail context is application-owned. The caller passes a free-form guardrail_context object on the generation request or orchestration-run start; the platform never interprets it. For long-lived work (an orchestration run can park at an approval node for days), a run-start snapshot goes stale, so a guardrail may also name a context_tool_id — an ordinary tool the platform calls at evaluation time, immediately before classifying each gated call. context_mode controls the combination: merge (default) shallow-merges top-level keys over the caller-supplied object, the tool's value winning on conflict; replace substitutes it entirely.
The context tool executes under the calling agent's credentials — same project scoping, same secret resolution — so a guardrail can never read data the agent could not reach. The platform's dispatch path invokes it; the model never sees it and its result never enters the model context. If the agent cannot access the tool, the standard fail-closed rule applies. The call is bounded by a per-call timeout and a short per-(project, guardrail) TTL cache.
The runtime.* catalog (windows are baked into the key name — a fixed suffix set _1h / _24h / _7d / _30d, each rolling and ending at evaluation time):
| Key | Type | Source |
|---|---|---|
runtime.action / runtime.tool.id / runtime.tool.name | string | The call being classified |
runtime.agent.id / runtime.project.id | string | Evaluation identity |
runtime.orchestration_run.node_attempt / runtime.orchestration_run.tool_calls | integer | Current orchestration run state |
runtime.activity.actions_1h / runtime.activity.actions_24h | integer | Activity feed (per project) |
runtime.usage.cost_usd_1h / _24h / _7d / _30d | number | Usage metering (per project) |
runtime.usage.tokens_24h / runtime.usage.tokens_30d | integer | Usage metering (per project) |
runtime.usage.orchestration_run_tokens / runtime.usage.orchestration_run_cost_usd | number | Usage metering (per run, cumulative) |
runtime.activity.actions_1h / actions_24h count this project's action_executed entries on the activity feed over the rolling window, read live. An empty feed reads as a real 0; only a failing query falls back to the fail-closed rule.
runtime.usage.orchestration_run_tokens / orchestration_run_cost_usd are the odd pair out: they sum only the usage events of the current orchestration run, read live — see Per-run spend ceilings.
A cost key resolves to null when its spend cannot be priced. SUM(cost_usd) ignores unpriced events, so a window that metered LLM usage and priced none of it would otherwise report a figure that understates real spend — and every ceiling reading it would pass. Both runtime.usage.cost_usd_* and runtime.usage.orchestration_run_cost_usd report null for such a window instead, which the fail-closed rule below turns into a failed guard. It is the same verdict a cost_usd quota answers with QUOTA_UNENFORCEABLE, and it clears the moment the models involved carry price book rows.
Three cases deliberately do not trigger it, so a ceiling never refuses a project that cannot clear it: a window holding no LLM usage at all (a project that has not generated yet reads a real 0), unpriced embeddings (their rate is deployment configuration, with no price row a tenant can create), and a partly priced window (any priced LLM event clears the verdict, and the unpriced ones still count as zero).
That last one is a deliberate limit, not an oversight: refusing on a ratio would block the very generation that would price the window. It is reported instead — the same verdict and the same per-model reading of the window feed a quota_unpriced exception on the project's cost_usd quota, so the pricing gap a ceiling is silently reading past has a triage item naming the rows to price.
Fail-closed at both ends. At write time, a document referencing a var outside the three namespaces — or a runtime.* key outside the catalog — is rejected with 400. At evaluation time, a context.* key absent from the effective context, a context-tool failure or timeout, or an unresolvable runtime.* provider all fail closed: in class, the result resolves to default_class; in guard, it counts as a failed guard and tripwire semantics apply. Forgetting to supply context tightens the posture, never loosens it.
Variable casing. guardrail_context (and a dry-run's args) is an application-owned bag — keys pass through verbatim, with no snake↔camel conversion. Author the document path and the context key in the same case; snake_case is recommended (it matches the runtime.* catalog), so { "var": "context.max_daily_budget" } reads a supplied max_daily_budget.
Missing keys and comparisons. JSON Logic coerces an absent var to a falsy, zero-ish value, so { "<": [{ "var": "args.amount" }, 500] } is true when args.amount is absent. When a missing argument must not reach the permissive branch, test presence explicitly: { "and": [{ "var": "args.amount" }, { "<": [{ "var": "args.amount" }, 500] }] }.
Tripwires and escalate
A failing class-B guard is a tripwire: by default it aborts the action and files an exception — a runaway loop hits a hard, non-LLM stop. escalate: true opts into the softer behavior: a failing guard routes the call to the approvals queue instead.
escalate is per-guardrail: a failing guard yields that guardrail's own decision — tripwire without escalate, route_to_approval with it — and the strictest decision across all applying guardrails still wins (tripwire outranks route_to_approval in the decision ordering), so opting one guardrail into escalation never softens another's hard stop.
Per-run spend ceilings
A runaway orchestration run is not caught by a project-windowed budget guard: the window barely moves while one run burns through its budget. runtime.usage.orchestration_run_tokens and runtime.usage.orchestration_run_cost_usd expose the current run's cumulative metered spend, live at evaluation time, so a ceiling trips mid-run on the tool call that crosses it.
Give the ceiling itself as guardrail_context (or a context tool) so one guardrail serves every run:
soat create-guardrail \
--name "Per-run token ceiling" \
--document '{
"class": "B",
"guard": {
"<": [
{ "var": "runtime.usage.orchestration_run_tokens" },
{ "var": "context.action_token_ceiling" }
]
}
}'
Attach it to the tools the run dispatches: once the run crosses the ceiling the guard fails and class-B tripwire semantics abort the call before the tool runs. Swap orchestration_run_tokens for orchestration_run_cost_usd to cap dollars.
Two properties worth knowing:
- Fail-closed outside a run. Both keys are unresolvable when no run is in scope (they do not read as
0), so a per-run ceiling attached at project scope trips on plain agent calls too — attach at tool scope unless that is intended. - Metering granularity is the resolution. The counters advance as each provider call is metered (see usage coverage), so a ceiling trips on the first gated call after it is crossed — a single over-budget call can still complete.
Client Tools
Guardrails classify calls to client tools like any other, but because actuation happens on the client, the gate sits at the requires_action handoff: class A and a passing B hand the call to the client as usual; class C files the approval item first, and the handoff happens only on approval; class D blocks the handoff; a tripwire aborts before anything reaches the client. The guardrail governs whether the call is released to the client at all — the platform cannot observe what the client does after.
Orchestration tool nodes
An orchestration tool node is gated at dispatch just like an agent tool call, but with no agent in scope it composes only the project + tool scopes (agentId/generationId are null on the evaluation identity and audit record). The strictest decision is enacted in orchestration terms:
- A / passing B — the tool executes with the (cleaned) node inputs.
- C — the run parks on the node with a
requires_actionoftype: "approval", filing anApprovalItem(origin: node). On approval the node re-dispatches with the frozen (or edited) arguments — the guardrail is not re-evaluated; on rejection or expiry the tool never runs and only a matching decision edge (condition: "rejected"/"expired") follows. - D / tripwire — a routable
blockedoutcome, not a run failure: the node records a{ status, reason }artifact and branches by label, so an edge conditioned onblocked(ortripwire) routes to a fallback path. An unlabeled success edge does not auto-follow a blocked node.
Direct calls and pipeline steps
A tool-scoped guardrail governs its tool wherever it is used, so the gate also
sits on the dispatches that involve no agent and no orchestration graph:
POST /api/v1/tools/{tool_id}/call, every step of
a pipeline tool, a trigger whose target is a tool, an
ingestion rule converter, an
eval tool scorer, and a tool_id embedded in a message.
Like an orchestration node these compose project + tool scope only.
None of them can await a decision, because there is no turn to return a pending result into and no run to park:
- A / passing B — the call runs with the cleaned arguments.
- C / D / tripwire — the call is refused with
422 TOOL_DISPATCH_FAILED, whosemetacarries thetool_idand theoutcomethat settled it. Inside a pipeline the step's ownPIPELINE_STEP_FAILEDnames which step was settled.
A gated pipeline is adjudicated before its first step runs, so a refusal never leaves half a pipeline applied. Reach an approval-gated tool through an agent or an orchestration instead: both can park on the sign-off and resume from it.
The one dispatch that is deliberately not gated is a guardrail's own context fetch — gating it would run the guardrails that decide a call in order to decide that call. An approved call is not re-gated either: the guardrail that filed the approval is what classified it, and a human then signed off on those exact arguments.
Running a tighter posture in one project
There is no separate override resource. A project runs a stricter posture by attaching a tighter guardrail at its project scope — e.g. { "class": "C" } forces sign-off on every call its agents make — or at one tool's scope to tighten just that tool. Stricter-wins guarantees the attachment can only tighten, and other projects are untouched.
Versioning
A guardrail's policy is versioned by the same append-only archive that backs agent versions. Version 1 is written on create, and every write that changes the document increments version and archives it as a GuardrailVersion. Approval items, activity entries, and exceptions record the version that governed them.
Three writes archive nothing — a version exists to name a distinct policy: a metadata-only edit (name, description, context_tool_id, context_mode); re-writing the document the guardrail already holds (compared structurally); restoring the version that is already live. version_label on a create or update annotates the version that write archives; it is not part of the config, so labelling a change is never itself a change.
| Operation | Endpoint |
|---|---|
| List versions, newest first | GET /api/v1/guardrails/{guardrail_id}/versions |
| Fetch one version | GET /api/v1/guardrails/{guardrail_id}/versions/{version} |
| Roll back to a version | POST /api/v1/guardrails/{guardrail_id}/versions/{version}/restore |
Restore appends, it does not rewind. Restoring v1 of a guardrail at v2 writes v1's document back as v3, so records citing v2 still resolve. The restore runs through the ordinary update path (the archived document is re-validated), takes an optional label, and rolls back only the policy — name, description and the context binding are untouched.
Attachments reference the guardrail's id, not a version: a document edit takes effect immediately everywhere the id is attached. Dry-run an edited document before writing it when the guardrail is attached at scale.
Guardrails have no release/canary layer, unlike agents: splitting traffic across two policies would mean deliberately under-enforcing one of them.
Deletion
A guardrail cannot be deleted while it is attached: DELETE /api/v1/guardrails/{guardrail_id} returns 409 listing the tools, agents, and projects whose guardrail_ids still reference it. Each reference must be detached first — a guardrails:DetachGuardrail operation (see Attachment) — so deletion can never do what detach permissions forbid. As defense-in-depth, a dangling reference encountered at evaluation time fails closed: the unresolvable guardrail evaluates as class C.
Dry-run Evaluation
POST /api/v1/guardrails/{guardrail_id}/evaluate runs the full evaluation pipeline — the class expression, the guard, the context tool per context_mode, live runtime.* resolution — against caller-supplied args and guardrail_context, and returns the exact evaluation record a real call would produce. Nothing executes, no approval item is filed, no activity entry is written. Pass an optional tool_id to resolve runtime.tool.*; an unresolvable runtime.* key behaves exactly as at runtime (fail-closed).
This is the adoption path: preview a document's decisions against production-shaped calls before attaching it — or before editing a widely-attached one.
Evaluation Audit Record
Every evaluation — execute, route-to-approval, block, or tripwire — writes a guardrail_evaluation activity entry (and stamps the generation/run record):
{
"kind": "guardrail_evaluation",
"guardrail_id": "guard_V1StGXR8Z5jdHi6B",
"guardrail_version": 3,
"scope": "tool",
"tool": "update-budget",
"action": "update-budget",
"class": "B",
"decision": "execute",
"guard_result": true,
"context_source": "merged",
"context_snapshot": {
"args.amount": 450,
"context.max_daily_budget": 500,
"context.cost_ceiling": 1000,
"runtime.usage.cost_usd_24h": 812.4
},
"agent_id": "agent_V1StGXR8Z5jdHi6B",
"orchestration_run_id": "orch_run_V1StGXR8Z5jdHi6B",
"generation_id": "gen_V1StGXR8Z5jdHi6B"
}
tool/actionname the call being classified; both arenullfor a call with no tool in scope.decisionis one ofexecute|route_to_approval|blocked|tripwire.classis the resolved class; when theclassexpression returned an invalid value it is the applieddefault_class.scoperecords where this guardrail was attached:project|agent|tool. One record is written per applying guardrail; the enacteddecisionis the strictest across them.context_sourcerecords where the effective context came from:caller|tool|merged|none.guard_resultis the guard expression's boolean outcome;nullwhen the document has no guard or the call did not classify asB.context_snapshotis a flat map of only the vars the evaluation actually referenced, keyed by fully-qualified path and frozen at evaluation-time value — enough to answer "why did this pass?" later, without recording unreferenced (possibly sensitive) context.
Evaluations that changed the call's outcome — route_to_approval, blocked, or tripwire, but not execute — are additionally mirrored into the audit log as a platform-originated entry (action: guardrails:Evaluate, detail.kind: guardrail_evaluation); a route_to_approval entry also carries the filed approval_id.
Formation resource
Guardrails can be declared as a guardrail formation resource (GuardrailResourceProperties): name, description, class, default_class, guard, escalate, context_tool_id, context_mode — the same fields as Create a guardrail, with the REST API's single document object flattened to top-level properties. context_tool_id may be a { "ref": "ResourceName" } to a tool resource in the same template, and a tool or agent resource can attach the guardrail via guardrail_ids: [{ "ref": "ResourceName" }], so a full gate deploys from one template. class/default_class/guard/escalate are recombined into a single document write on every create/update, so an update that omits one of them drops it (matching PATCH /api/v1/guardrails/{guardrail_id}'s full-replace semantics for document).
Examples
Create a guardrail
This guardrail governs the budget-update tool it is attached to: class B below 500, C at or above, executing autonomously only while 24h spend stays under 1000.
- CLI
- SDK
- curl
soat create-guardrail \
--name "Budget Update Guardrail" \
--document '{
"default_class": "C",
"class": { "if": [{ "<": [{ "var": "args.amount" }, 500] }, "B", "C"] },
"guard": { "<": [{ "var": "runtime.usage.cost_usd_24h" }, 1000] }
}'
import { SoatClient } from '@soat/sdk';
const soat = new SoatClient({
baseUrl: 'https://api.example.com',
token: 'sk_...',
});
const { data, error } = await soat.guardrails.createGuardrail({
body: {
name: 'Budget Update Guardrail',
document: {
default_class: 'C',
class: { if: [{ '<': [{ var: 'args.amount' }, 500] }, 'B', 'C'] },
guard: { '<': [{ var: 'runtime.usage.cost_usd_24h' }, 1000] },
},
},
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/guardrails \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Budget Update Guardrail",
"document": {
"default_class": "C",
"class": { "if": [{ "<": [{ "var": "args.amount" }, 500] }, "B", "C"] },
"guard": { "<": [{ "var": "runtime.usage.cost_usd_24h" }, 1000] }
}
}'
Dry-run a guardrail before attaching
Preview the decision the guardrail above would make for a production-shaped call — nothing executes, nothing is filed:
- CLI
- SDK
- curl
soat evaluate-guardrail \
--guardrail-id guard_V1StGXR8Z5jdHi6B \
--args '{"amount": 450}'
const { data, error } = await soat.guardrails.evaluateGuardrail({
path: { guardrail_id: 'guard_V1StGXR8Z5jdHi6B' },
body: { args: { amount: 450 } },
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/guardrails/guard_V1StGXR8Z5jdHi6B/evaluate \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "args": { "amount": 450 } }'
The response is the would-be evaluation record — here class B with a passing guard, runtime.usage.cost_usd_24h resolved live:
{
"class": "B",
"decision": "execute",
"guard_result": true,
"context_source": "none",
"context_snapshot": {
"args.amount": 450,
"runtime.usage.cost_usd_24h": 812.4
}
}
Attach a guardrail
A tool-scoped guardrail attaches to its tool, governing it for every agent that uses it. Attach to an agent instead (soat update-agent --agent-id agent_01 --guardrail-ids …) for a blanket posture over the agent's whole tool surface, or to a project (soat update-project --project-id proj_01 --guardrail-ids …) for a baseline over every agent in it — see Attachment.
- CLI
- SDK
- curl
soat update-tool \
--tool-id tool_01 \
--guardrail-ids guard_V1StGXR8Z5jdHi6B guard_9f3Kd2Lm0PqRsT4u
const { data, error } = await soat.tools.updateTool({
path: { tool_id: 'tool_01' },
body: { guardrail_ids: ['guard_V1StGXR8Z5jdHi6B', 'guard_9f3Kd2Lm0PqRsT4u'] },
});
if (error) throw new Error(JSON.stringify(error));
curl -X PATCH https://api.example.com/api/v1/tools/tool_01 \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"guardrail_ids": ["guard_V1StGXR8Z5jdHi6B", "guard_9f3Kd2Lm0PqRsT4u"]}'
Pass guardrail context on a generation
The application supplies the context.* values guards evaluate over. If the guardrail also names a context_tool_id, the tool's output is combined over this object per context_mode at evaluation time.
- CLI
- SDK
- curl
soat create-agent-generation --wait true \
--agent-id agent_01 \
--messages '[{"role":"user","content":"Raise the campaign budget to 450"}]' \
--guardrail-context '{"max_daily_budget": 500, "cost_ceiling": 1000}'
const { data, error } = await soat.agents.createAgentGeneration({
path: { agent_id: 'agent_01' },
query: { wait: true },
body: {
messages: [{ role: 'user', content: 'Raise the campaign budget to 450' }],
guardrail_context: { max_daily_budget: 500, cost_ceiling: 1000 },
},
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/agents/agent_01/generate?wait=true \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"messages": [{ "role": "user", "content": "Raise the campaign budget to 450" }],
"guardrail_context": { "max_daily_budget": 500, "cost_ceiling": 1000 }
}'
List archived versions
- CLI
- SDK
- curl
soat list-guardrail-versions --guardrail-id guard_V1StGXR8Z5jdHi6B
const { data, error } = await soat.guardrails.listGuardrailVersions({
path: { guardrail_id: 'guard_V1StGXR8Z5jdHi6B' },
});
if (error) throw new Error(JSON.stringify(error));
curl -X GET https://api.example.com/api/v1/guardrails/guard_V1StGXR8Z5jdHi6B/versions \
-H "Authorization: Bearer <admin-token>"