Developer docs
Wire your agent into Cautel’s rule-check before it acts.
POST /v1/check is the one gateway call your agents make before any side-effect. It returns allow or block, the rule that decided it, and a log id you can hand to your auditor. The protocol is plain HTTP and JSON — drop it into curl, Python, or any function-calling agent.
Before you call
Three fixed facts about every request.
Anything else — which data classes, which rule, which log id — is something the gateway decides. These three are what your code has to send.
- Method + path
POST /v1/checkRelative to the public Cautel origin issued when you sign up.
- Authorization
Bearer <YOUR_SECRET>The gateway secret is issued per tenant. Keep it server-side; never expose it in a browser bundle.
- Content-Type
application/jsonRequest body is a single JSON object matching the schema below.
Request schema
Four fields on the request, five values on data_classes.
The gateway rejects anything that does not match this shape — the rule engine only sees valid input.
| Field | Type | What it means |
|---|---|---|
agent_id | string | Stable identifier for the agent that wants to act. Used in every audit row. |
action | string | The side-effect you are about to take — e.g. send_email, refund.create, db.update. |
target | string | The resource the action targets — e.g. customer:42, ledger:acme-2026-Q1. |
data_classes | DataClass[] | Which data classes this action will read or write. Drives rule matching. |
allowed data_classes
piiPersonally identifiable information (names, emails, identifiers).phiProtected health information subject to HIPAA-style controls.financialPayment, billing, ledger, or other monetary records.credentialsSecrets, tokens, passwords, and key material.publicData with no regulatory or sensitivity constraint.
curl
The shortest end-to-end call.
Replace <YOUR_SECRET>with the gateway secret from your tenant dashboard. The example below asks for permission to email a customer record — the gateway blocks it because pii is not on the allow-list for send_email.
Run from your server, not a browser. The secret never belongs in client code.
curl -X POST "https://api.cautel.example/v1/check" \
-H "Authorization: Bearer <YOUR_SECRET>" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agent-7f9c",
"action": "send_email",
"target": "customer:42",
"data_classes": ["pii"]
}'{
"verdict": "block",
"reason": "pii data class access is not permitted",
"rule_cited": "pii_block",
"log_id": "b3a1f7e2-4d2c-4a1e-9c6f-7a8e1d3b5c12"
}Python
A reusable check() wrapper, and the pattern for gating a side-effect on it.
Put CAUTEL_GATEWAY_SECRETin your server environment, import the function wherever you orchestrate agent actions, and call it before any tool invocation. The returned dataclass carries the verdict and the log id — persist the log id if your auditors want per-decision receipts.
# pip install requests
import os
import requests
from dataclasses import dataclass
CAUTEL_URL = "https://api.cautel.example/v1/check"
@dataclass
class CheckResponse:
verdict: str # "allow" | "block"
reason: str
rule_cited: str | None
log_id: str
def check(action: str, target: str, data_classes: list[str]) -> CheckResponse:
"""Pre-execution rule-check. Returns the parsed verdict."""
resp = requests.post(
CAUTEL_URL,
headers={
"Authorization": f"Bearer {os.environ['CAUTEL_GATEWAY_SECRET']}",
"Content-Type": "application/json",
},
json={
"agent_id": "agent-7f9c",
"action": action,
"target": target,
"data_classes": data_classes,
},
timeout=10,
)
resp.raise_for_status()
payload = resp.json()
return CheckResponse(
verdict=payload["verdict"],
reason=payload["reason"],
rule_cited=payload["rule_cited"],
log_id=payload["log_id"],
)
# Gate my agent — only proceed when the gateway approves the action.
def send_email(to: str, body: str) -> None:
verdict = check(
action="send_email",
target=f"customer:{to}",
data_classes=["pii"],
)
if verdict.verdict != "allow":
raise PermissionError(
f"Cautel blocked outreach to customer:{to} "
f"(rule_cited={verdict.rule_cited}): {verdict.reason}"
)
deliver(to, body) # your existing mailer / provider call
OpenAI function-calling
Give the model Cautel as a tool. The model cannot act without the verdict.
Three pieces: the tool schema (what the model is allowed to ask for), the tool implementation (your cautel_check() call), and the agent loop (how to feed the verdict back and only run the side-effect when it is allow).
API shape as of openai-pythonSDK v1.60+. Function-calling is the same on the Responses and Chat Completions APIs — only the transport differs.
# pip install openai>=1.60.0
import os
import openai
client = openai.OpenAI() # reads OPENAI_API_KEY from env as usual
CAUTEL_TOOLS = [
{
"type": "function",
"name": "cautel_check",
"description": (
"Pre-execution rule-check. Call this BEFORE any side-effect "
"(email, write, payment, message) to verify Cautel approves it. "
"If the verdict is not 'allow', abort the side-effect."
),
"parameters": {
"type": "object",
"properties": {
"action": {
"type": "string",
"description": "The side-effect you are about to take (e.g. send_email).",
},
"target": {
"type": "string",
"description": "Resource the action targets (e.g. customer:42, ledger:acme-2026-Q1).",
},
"data_classes": {
"type": "array",
"items": {
"type": "string",
"enum": ["pii", "phi", "financial", "credentials", "public"],
},
"description": "Data classes this action touches.",
},
},
"required": ["action", "target", "data_classes"],
"additionalProperties": False,
},
"strict": True,
},
]import requests
def cautel_check(action: str, target: str, data_classes: list[str]) -> dict:
"""Tool implementation the agent calls. Returns the parsed verdict."""
resp = requests.post(
"https://api.cautel.example/v1/check",
headers={
"Authorization": f"Bearer {os.environ['CAUTEL_GATEWAY_SECRET']}",
"Content-Type": "application/json",
},
json={
"agent_id": "agent-7f9c",
"action": action,
"target": target,
"data_classes": data_classes,
},
timeout=10,
)
return resp.json() # {"verdict":..., "reason":..., "rule_cited":..., "log_id":...}# API shape as of openai-python SDK v1.60+ (Responses API).
def step(user_prompt: str) -> str:
response = client.responses.create(
model="gpt-4.1",
tools=CAUTEL_TOOLS,
input=user_prompt,
)
for item in response.output:
# The model decided to call our tool before acting — run the rule-check.
if item.type == "function_call" and item.name == "cautel_check":
args = json.loads(item.arguments)
verdict = cautel_check(
action=args["action"],
target=args["target"],
data_classes=args["data_classes"],
)
# Submit the tool result back; if the verdict is not "allow",
# continue the loop without executing the side-effect.
follow_up = client.responses.create(
model="gpt-4.1",
tools=CAUTEL_TOOLS,
previous_response_id=response.id,
input=[
{
"type": "function_call_output",
"call_id": item.call_id,
"output": json.dumps(verdict),
}
],
)
return follow_up.output_text
# No tool call — return the assistant reply directly.
return response.output_text
next step
Tell us which agent you are wiring in. We will issue a gateway secret and a sandbox tenant within one business day.
Write to us
cautel@polsia.appInclude the agent stack you have today (Temporal, LangGraph, custom MCP, etc.) and the first rule you would want it to honor.