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/check

Relative 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/json

Request 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.

FieldTypeWhat it means
agent_idstringStable identifier for the agent that wants to act. Used in every audit row.
actionstringThe side-effect you are about to take — e.g. send_email, refund.create, db.update.
targetstringThe resource the action targets — e.g. customer:42, ledger:acme-2026-Q1.
data_classesDataClass[]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.

Request · POST /v1/check
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"]
  }'
Response · 200 OK
{
  "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.

check.py — wrapper + gate my agent
# 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.

1 · Tool schema (CAUTEL_TOOLS)
# 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,
    },
]
2 · Tool implementation (cautel_check)
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":...}
3 · Agent loop — pre-check, execute when 'allow'
# 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.app

Include the agent stack you have today (Temporal, LangGraph, custom MCP, etc.) and the first rule you would want it to honor.