Laya Guard
In short: before your agent runs a tool call, send it with what the user asked for. You get allow, ask or block, the reasons, and calibrated scores for nine questions. Same key and balance as decisions; $0.20 per 1k checks.
Your first check
Put it in your pre-tool-call hook
Only an allow runs without a human. Keep your deterministic rules (blocked commands, allow-listed domains) in front of it: the guard is for the long tail your rules do not describe.
Request body
| Request body: POST /v1/guard | Description |
|---|---|
actionrequiredstring | {tool, args} | The tool call about to run: "db.drop_table(users)", or {"tool": "db.drop_table", "args": {"table": "users"}}. |
intentstring | What the user asked the agent to do. The most useful field after the action. |
user_messagestring | The latest user message, when it differs from the intent. |
trigger"user_request" | "tool_result" | "correction… | What produced the action. Use tool_result when the idea came from a tool output or a document (possible prompt injection). |
constraintsstring[] | Rules the action must respect ("staging only", "never email outside acme.com"). At most 32. |
contextstring | Agent role, environment (production / staging), anything else worth knowing. |
conversation{role, content}[] | Recent turns, oldest first, at most 50. Only the most recent part is read. |
Each text field is limited to 20,000 characters. The model reads about 384 tokens per check, so put the action and intent first and keep context short.
Response
| Field | Description |
|---|---|
verdict"allow" | "ask" | "block" | The default decision from the scores below. Run, ask a human, or never run. |
p_unsafenumber | Calibrated probability that the action is unsafe. |
reasonsstring[] | Plain-words reasons behind an ask or block. |
scores.safenumber | Probability the call is safe to run now. |
scores.violationobject | none, policy_violation, scope_violation, injection, goal_drift, corrigibility. |
scores.severity{expected, level, probabilities} | none < low < medium < high. |
scores.destructivenumber | Deletes, overwrites or irreversibly changes data. |
scores.exfiltrationnumber | Sends private data or secrets where they should not go. |
scores.injectednumber | Driven by a tool result or document rather than the user. |
scores.approval_policyobject | auto_approve, require_human, reject. |
scores.blast_radius{expected, level, probabilities} | read-only < reversible write < production-mutating or external side effect. |
scores.args_groundednumber | The arguments are supported by what the user asked. |
modelstring | The guard model that answered (mcp-guard-deberta-v1: DeBERTa-v3-base, 184M). |
The default verdict is a starting point. Thresholds fitted on one dataset do not carry over to every agent, so log the scores, compare them with what your users approve, and set your own cut-offs per head before you let allow run unattended.
Batches
POST /v1/guard/batch takes up to 64 checks and returns one result per check, in order. Use it when a generated script is about to make several calls at once. A check that fails validation comes back as {error} and is not billed.
From an MCP client
The Laya Studio MCP server (https://api.laya.studio/mcp) exposes guard_check and guard_batch next to the decision tools, with the same key. See MCP server.
Billing
$0.20 per 1k checks: 5,602 credits per check that returns a verdict, from the same balance as decisions, whatever the length of the check. Swiss-only checks (the workspace setting or the x-laya-residency: ch header) cost 15% more and never leave Switzerland. The response headers x-credits-charged and x-request-id tell you what was billed.
| Status | Meaning |
|---|---|
200 | Checked. x-credits-charged is the credits billed. |
400 | Malformed body: no action, a bad trigger, constraints not strings, a batch over 64. |
401 | Missing, malformed or revoked key. |
402 | The balance cannot cover the check (5,602 credits each). Add credits or subscribe. |
413 | A field is longer than 20,000 characters. |
429 | Rate limited (per key). Honour Retry-After. |
503 | The guard model is busy or restarting. Retry with backoff; nothing was charged. |