Policies
A policy file is your authorization rulebook. It lives on your machine, it is human-readable, and every authorization decision is evaluated against it locally.
How policies work
When authorize() runs, it reads your policy.yaml file and evaluates the incoming request against the rules top-to-bottom. The first rule that matches wins. If nothing matches, the defaultVerdict applies — and if you omit it, it defaults to pending_approval.
Shotoku fails closed. If the policy file is missing, malformed, or contains unknown fields, the request is denied. Unknown fields are rejected instead of silently ignored, because a typo in an authorization policy should not weaken it.
Example policy
Top-level fields
| Field | Type | What it does |
|---|---|---|
| version | number | Policy format version. Currently 1. |
| defaultVerdict | AuthorizationStatus | What happens when no rule matches. Defaults to pending_approval if omitted. |
| rules | PolicyRule[] | The ordered list of rules. Evaluated top-to-bottom — the first match wins. |
Rule fields
Each entry in the rules array is a PolicyRule:
| Field | Type | What it does |
|---|---|---|
| resource | string | The domain or service this rule applies to. Use "*" to match anything not matched by an earlier rule. |
| actions | AgentAction[] | Optional. Limits this rule to specific action types. Omit to match all actions. |
| rails | ExecutionRail[] | Optional. Limits this rule to specific execution rails (x402, mcp, etc.). Omit to match all rails. |
| verdict | AuthorizationStatus | The outcome when this rule matches: approved, denied, or pending_approval. |
| maxAmount | number | Optional. Maximum allowed spend per single transaction in USD. Exceeding this denies the request even if the rule would otherwise approve it. |
| maxDailyAmount | number | Optional. Maximum total spend for this resource in a rolling 24-hour window in USD. Exceeding this denies the request. |
Wildcard rules
A resource: "*" rule matches any request that was not matched by an earlier rule. Use it to define a catch-all policy at the bottom of your file.
Rules are evaluated in order. A wildcard at position 3 only catches requests that did not match rules 1 and 2. Order matters.
Daily limits
maxDailyAmount is evaluated against a rolling 24-hour window, not a calendar day. Shotoku reads the local ledger to compute how much has been spent for the given actor and resource combination in the past 24 hours, then checks whether this request would push it over the cap.
The daily total is keyed as actor|resource. Two different actors spending against the same resource do not share a budget — each actor has its own counter.
Rail-specific rules
You can write rules that only apply when an action is taken through a specific execution rail. For example, to require approval for all x402 payments regardless of resource:
x402 policy example
If you are using the x402 payment rail, the agent passes rail: "x402"and action: "purchase" with the payment amount. Your policy can match specifically on these: