Enforcement API
Document POST /v1/enforce for policy enforcement decisions and GET /v1/enforce/hold/{hold_token} for polling step-up approval status.
POST /v1/enforce
Check enforcement policy for a proposed tool call. Returns an EnforcementDecision indicating
whether the tool should be allowed, blocked, or held for human approval.
The SDKs call this endpoint automatically before every tool execution. Call it directly only if you are building a custom integration.
Request
Request body
| Field | Type | Required | Description |
|---|---|---|---|
agent_id | string | Yes | Agent identifier |
tenant_id | string | Yes | Your Thoth tenant identifier |
tool_name | string | Yes | Name of the tool the agent wants to call |
session_id | string (UUID) | Yes | Current session identifier |
user_id | string | Yes | User who initiated the agent action |
approved_scope | string[] | Yes | Authorized tool names for this session |
enforcement_mode | string | Yes | Enforcement mode: observe | progressive | step_up | block |
session_tool_calls | string[] | Yes | Tool names already called in this session (in order) |
Response
ALLOW decision
BLOCK decision
STEP_UP decision
EnforcementDecision schema
| Field | Type | Present when | Description |
|---|---|---|---|
decision | "ALLOW" | "BLOCK" | "STEP_UP" | Always | The enforcement decision |
reason | string | Always | Human-readable explanation of the decision |
violation_id | string | BLOCK only | Audit ID — correlates with enforcement and evidence records |
hold_token | string | STEP_UP only | Opaque token for polling approval status |
risk_score | number | Always | Anomaly risk score (0.0–1.0) |
latency_ms | number | Always | Enforcer evaluation time in milliseconds |
Decision semantics
ALLOW — proceed with tool execution. Emit a TOOL_CALL_POST event after the tool returns.
BLOCK — do not execute the tool. Raise PolicyViolationError / ThothPolicyViolation with
the violation_id for the caller to log and handle gracefully.
STEP_UP — pause execution and poll GET /v1/enforce/hold/{hold_token} with the hold_token
until the approver responds or the timeout is reached.
How the enforcer evaluates calls
The enforcer evaluates several signals to produce a decision:
- Scope check — Is
tool_nameinapproved_scope? If not, this is an out-of-scope call. - Mode — In
observemode, always ALLOW. Inblockmode, always BLOCK out-of-scope calls. - Progressive scoring — In
progressivemode, the enforcer weights:- Ratio of out-of-scope calls in
session_tool_calls - Frequency of repeated blocked tools (probing behavior)
- Unusual tool sequences for this
agent_id(anomaly model)
- Ratio of out-of-scope calls in
- Step-up triggers — Tools tagged as high-risk in policy configuration always trigger STEP_UP regardless of scope.
Example (curl)
GET /v1/enforce/hold/{hold_token}
Poll the status of a pending step-up approval. Call this repeatedly until the status is
approved or denied (or until your timeout is reached).
Request
The hold token is passed as a path parameter — there is no request body.
Response
Pending
Approved
Denied
Expired
StepUpStatus schema
| Field | Type | Description |
|---|---|---|
status | "pending" | "approved" | "denied" | "expired" | Current approval status |
hold_token | string | The hold token you polled with |
tool_name | string | Tool name pending approval |
agent_id | string | Agent that requested the tool call |
created_at | string (ISO 8601) | When the step-up request was created |
expires_at | string (ISO 8601) | When the request will auto-expire |
approved_by | string | Approver email (present when status === "approved") |
approved_at | string (ISO 8601) | Approval timestamp |
denied_by | string | Denier email (present when status === "denied") |
denied_at | string (ISO 8601) | Denial timestamp |
reason | string | Denial reason (present when status === "denied") |
Polling pattern
The SDK handles this polling loop automatically — you only need to call it directly if building a custom integration.