policies.json
policies.json defines guard policies for your Swytchcode project.
Unlike tooling.json, which controls what can execute, policies.json controls whether a call may execute, and in what form, based on the call being made. Policies can block a call, hold it for human approval, change its arguments, or - once the call is made - hide or cut parts of its response before the agent sees it.
The file lives at:
.swytchcode/integrations/policies.jsonPolicies apply to every call made through Swytchcode: swy exec and the swytchcode_exec MCP tool.
How it works
Before a method runs, Swytchcode checks the policies that target it. If a policy’s condition is
true, its action is applied. Response policies run again after the call, before the result is returned.
Managing Policies
Add a policy
swy policy addCreates a policy interactively: target, one condition, and an action. Every prompt also has a flag, so it can run in scripts with --non-interactive:
swy policy add --non-interactive --name "Payment limit" \ --target stripe.payment_intent.create \ --field amount --operator ">" --value 100000 \ --action POLICY_BLOCKED --message "Payments above 1000.00 are blocked"| Flag | Description |
|---|---|
--name, --id | Policy name and id (default: generated from the name). |
--target | Canonical ID(s) the policy guards. Repeatable or comma-separated. |
--field, --operator, --value | The condition. |
--action, --message | What happens when it matches, and the message shown. |
--list <name> | Compare against a named list instead of --value. Version 2. |
--ignore-case | Compare text without regard to case. Version 2. |
--transform <names> | Change the field before comparing, e.g. domain or count. See transform. Version 2. |
--quantifier any|all | When the field has several values, whether any or all must match. Version 2. |
--mode monitor | Record matches only, never act on them. See mode. Version 2. |
--hint <text> | Tells the agent what to do instead. See hint. Version 2. |
Flags marked Version 2 need a version-2 file. A new policies.json gets version 2 automatically when the first policy needs it. swy policy add never upgrades an existing version-1 file: it refuses and tells you to add "version": 2 first.
For policies with several conditions or targets, write them by hand or with a coding agent through the MCP policy tools.
List policies
swy policy list [--json]Shows every policy with its targets, condition, and action. Policies in monitor mode, switched off, not yet active, or expired are marked.
Remove a policy
swy policy remove <policy-id>Deletes a policy by its ID.
Validate policies
swy policy validateValidation checks for:
- Unknown operators, actions, or fields
- Missing required fields
- Duplicate IDs
- Empty targets
- Invalid value types, windows, dates, and time zones
- Malformed nested conditions
- List files that can’t be read
- Conditions whose value can’t match the field’s type in the tool’s input schema (these make every call to that tool fail)
It also warns about things that are valid but probably not what you meant: a field the target tool doesn’t have, a target pattern that matches no installed tool, an expired policy, or ALLOW policies that have no effect.
Then it runs the tests written in the file. swy policy validate exits with code 1 when there is an error or a test fails, so it can run in CI.
After swy sync, swy get, and swy add, the same schema checks run again, because a provider update can remove or change a field a policy uses. Up to five problems are printed, with a pointer to swy policy validate. Nothing is printed when all is well.
Show counts for limits
swy policy state [<policy-id>] [--json]swy policy state [<policy-id>] --resetShows what the conditions on earlier calls have counted for this project on this machine, and the approvals that can still be reused. --reset clears them, for one policy or all of them.
Review past decisions
swy policy manages the rules. Every time a policy blocks, holds, changes, or hides something, Swytchcode writes an entry to a local, read-only history:
swy audit policy # recent entriesswy audit policy -n 20 # last 20 entriesswy audit policy --info <id> # full detail for one entry (id looks like pol_...)Entries show what happened: blocked, monitored, modified, redacted, truncated, response_withheld, approval_reused, alert_sent, and alert_failed. Approval requests show as hitl (waiting), approved, rejected, expired, or failed; swy audit policy <status> lists one kind, e.g. swy audit policy hitl. Request arguments are stored redacted. See the Command Reference for the full swy audit surface.
Fail-Closed Behavior
policies.json follows a fail-closed model: a policy that can’t be checked stops the call instead of letting it through.
| Situation | Behavior |
|---|---|
| File does not exist | No policies are applied. Execution continues normally. |
| File exists and is valid | Policies are evaluated for every call. |
| File exists but is malformed or invalid | Every call stops with a validation error. |
| A list file can’t be read | Every call stops. |
A condition can’t be evaluated for this call (for example a non-numeric value for >) | The call stops, with the reason. |
| A condition’s value doesn’t fit the field’s type in the tool’s schema | Calls to that tool stop. |
This ensures invalid policy definitions never silently bypass security checks.
File Versions
policies.json has two versions.
- Version 1 (no
"version"key) is the original format. Everything on this page not marked Version 2 works in it, and existing files keep working exactly as before. - Version 2 (
"version": 2at the top) adds everything marked Version 2: wildcard targets, field paths, built-in fields, lists, conditions on earlier calls,ALLOW,MODIFY, response policies, tests, and more.
A version-1 file that uses a version-2 key is a validation error, so nothing is silently ignored. Nothing converts a file for you. To upgrade, add "version": 2 and run swy policy validate.
File Structure
A minimal version-1 file:
{ "defaults": { "on_violation": "fail", "evaluation": "pre_execution" }, "policies": [ { "id": "block-large-payments", "target": ["stripe.payment_intent.create"], "when": { "field": "amount", "operator": ">", "value": 100000 }, "action": { "type": "POLICY_BLOCKED", "message": "Payments above the allowed limit are blocked." } } ]}A version-2 file:
{ "version": 2, "defaults": { "on_no_match": "allow", "timezone": "Asia/Kolkata" }, "lists": { "blocked_domains": ["gmail.com", "yahoo.com", "outlook.com"] }, "policies": [ { "id": "no-personal-email", "description": "Outbound email only to business addresses", "target": ["*"], "when": { "field": "$email_domains", "operator": "in", "value": { "$list": "blocked_domains" }, "ignore_case": true }, "action": { "type": "POLICY_BLOCKED", "message": "Emails to {{$email_domains}} are not allowed", "hint": "Use the customer's business email address" }, "tests": [ ] } ]}The file has up to four top-level keys: version, defaults, lists (version 2), and policies.
defaults
The defaults object sets file-wide behavior.
| Key | Values | Description |
|---|---|---|
on_violation | fail | What a match does. Only fail is supported. |
evaluation | pre_execution | When request policies run. Only pre_execution is supported. |
on_no_match | allow (default), deny, monitor | What happens to a call no ALLOW policy matches. Version 2. |
timezone | A time zone name, e.g. Asia/Kolkata, UTC | The zone for $time, $date, $weekday, and dates in active_from / expires_at. Default: the machine’s local zone. Version 2. |
on_no_match
on_no_match turns a file into an allowlist:
allow- calls run unless a policy stops them.ALLOWpolicies have no effect.deny- a call runs only when anALLOWpolicy matches it. Every other call is blocked.monitor- likedeny, but calls that would be blocked still run and are recorded inswy audit policy. Use it to try an allowlist on real traffic before switching todeny.
lists
Version 2. Named lists of values, used in conditions with {"$list": "<name>"}. Write the values inline, or keep them in a file:
{ "lists": { "blocked_domains": ["gmail.com", "yahoo.com"], "allowed_hosts": { "file": "lists/allowed_hosts.txt" } }}A list file has one value per line. Blank lines and lines starting with # are skipped. Its path is relative to .swytchcode/integrations/ and must stay inside that folder. Swytchcode only reads list files; it never creates them. A list file that can’t be read stops every call, so a guard never silently matches nothing.
Policy Objects
Every entry in the policies array is one guard. Four keys are required:
| Key | Description |
|---|---|
id | A unique identifier. Two policies with the same id are a validation error. |
target | The methods the policy guards. |
when | The condition that triggers the policy. |
action | What happens when the condition matches. |
Version 2 adds optional keys:
| Key | Description |
|---|---|
description, owner, tags | Notes for people. Not used when deciding. |
stage | request (default) or response. See Response policies. |
mode | enforce (default) or monitor. See mode. |
enabled | false switches the policy off without deleting it. |
active_from, expires_at | When the policy applies. See Schedule. |
notify | A call to make when the policy matches, e.g. a Slack message. See Alerts. |
reuse, approval_timeout | REQUIRES_APPROVAL only. See Approval options. |
tests | Example calls and what should happen to them. See Tests. |
target
target is always an array. A policy only runs for calls to one of its targets.
{ "target": ["stripe.payment_intent.create", "stripe.refund.create"] }Version 2 also accepts patterns: "*" matches every tool, and * inside a canonical ID matches any text, e.g. "stripe.*" or "github.repo.*". In a version-1 file "stripe.*" is an exact ID, so it matches nothing; swy policy validate warns about it.
when
The condition. If it is true for the call, the action is applied. See Conditions.
action
{ "action": { "type": "POLICY_BLOCKED", "message": "Payments above the limit are blocked.", "hint": "Split the payment or ask the account owner" }}type is required. message is required for the actions that stop a call.
Action Types
Actions that stop a call
| Action | What happens |
|---|---|
POLICY_BLOCKED | The call is blocked. |
AUTH_FAILED | The call is blocked as not authorized. |
RATE_LIMITED | The call is blocked as over a rate limit. |
QUOTA_EXCEEDED | The call is blocked as over a quota. |
REQUIRES_APPROVAL | The call is held until a person approves or denies it. See Human approval. |
When a policy blocks a call:
- the request is never sent
- Swytchcode exits with code 6
- an error is written as JSON, naming the policy:
{ "error": "blocked by policy \"block-large-payments\": Payments above the limit are blocked.", "category": "policy_denied", "suggested_action": "this call was blocked by a configured guard and was not executed - do not retry it or try to work around the guard; report to the user which guard blocked it. ...", "hint": "Split the payment or ask the account owner", "source": "policy", "docs_url": "https://docs.swytchcode.com/policies/policy-rules/"}"source": "policy" tells an agent that a guard stopped the call, not the provider. A policy block is never retryable. A RATE_LIMITED or QUOTA_EXCEEDED block from a limit on earlier calls adds retry_after (seconds until the window frees up); it is retryable only when that wait is 30 seconds or less.
A REQUIRES_APPROVAL match exits with code 7: the call is neither run nor rejected yet.
When several policies match, a block wins over an approval, and an approval over an ALLOW.
Actions that let a call through
Version 2.
| Action | What happens |
|---|---|
ALLOW | Marks the call as allowed. Only matters with on_no_match deny or monitor. A block still wins. |
MODIFY | Changes the call’s arguments, then lets it continue. See MODIFY. |
REDACT | Response policies only. Hides values in the response. See Response policies. |
TRUNCATE | Response policies only. Cuts lists in the response. |
message placeholders
Version 2. message, hint, and notify.args can include values from the call with {{field}}, using the same names as conditions: {{amount}}, {{customer.email}}, {{$email_domains}}. A field whose name looks secret (token, password, key, …) shows as [REDACTED]. Policies with a limit can also use {{$count}}, {{$sum}}, {{$limit}}, and {{$retry_after}}.
hint
Version 2. hint tells the agent what to do instead, for example “use the customer’s business email address”. It is returned in the error’s hint field.
Conditions
Conditions determine when a policy applies. There are two types:
- Leaf conditions compare one field:
{ "field": "amount", "operator": ">", "value": 100000 } - Group conditions combine others with
all,any, ornot.
Where fields are found
A field name is looked up in the call’s arguments, in this order: the top level, then params, then body. Case is ignored. If the same name appears in two places with different values, the condition can’t be evaluated and the call stops, so an agent can’t hide a value in another place.
When a field holds a list (for example several recipients), operators check each item: ==, in, starts_with, ends_with, and matches match when any item matches. not_in on an empty list doesn’t match. contains on a list matches whole items.
A missing field matches not_exists, empty, and not_in, and never matches the other comparisons. >, >=, <, and <= on a missing field stop the call with field "amount" not present in request.
Group Conditions
| Operator | Description |
|---|---|
all | Every condition must match (AND). |
any | At least one condition must match (OR). |
not | Inverts a condition (NOT). |
{ "operator": "all", "conditions": [ { "field": "currency", "operator": "==", "value": "usd" }, { "field": "amount", "operator": ">", "value": 5000 } ]}The policy matches only if both conditions are true. Groups can nest:
{ "when": { "operator": "all", "conditions": [ { "field": "amount", "operator": ">", "value": 100000 }, { "field": "currency", "operator": "in", "value": ["usd", "eur"] }, { "operator": "not", "conditions": [{ "field": "test_mode", "operator": "exists" }] } ] }}This matches when the amount is over 100000, the currency is usd or eur, and test_mode is not present.
Comparison Operators
| Operators | Value | Notes |
|---|---|---|
==, != | A scalar | |
>, >=, <, <= | A number | |
in, not_in | An array (or a list) | |
contains, not_contains | A string | Substring. On a list, a whole item. |
starts_with, ends_with | A string | |
matches | A regular expression, up to 512 characters | |
exists, not_exists, empty, not_empty | None | |
before, after | A timestamp (RFC3339) | |
between, outside | Exactly two timestamps |
{ "field": "branch", "operator": "starts_with", "value": "release/" }{ "field": "created_at", "operator": "between", "value": ["2026-01-01T00:00:00Z", "2026-12-31T23:59:59Z"]}Version 2 adds operators for earlier calls (rate, sum, repeat, called_before, returned_by) and detectors (contains_pii, contains_secret).
Field Paths
Version 2. A field can be a path into nested arguments:
| Path | Means |
|---|---|
customer.email | email inside customer |
items[*].price | price of every item in the items list |
items[0].price | price of the first item |
recipients.email | A key applied to a list applies to each item, like recipients[*].email |
Paths follow the same lookup order (top level, params, body) and conflict check as plain names.
Built-in Fields
Version 2. Fields starting with $ describe the call itself. Swytchcode fills them in; they are never read from the request arguments, so an agent can’t set them.
| Field | Value |
|---|---|
$tool | The canonical ID being called |
$provider | The provider (first part of the canonical ID) |
$http_method | GET, POST, … |
$host, $path, $url | Where the request goes ($url has no query string) |
$env | sandbox or production |
$workspace | The linked workspace name |
$caller | cli or mcp |
$mcp_profile | agent, operator or full (MCP calls only) |
$mcp_client | The name the MCP client reports when it connects. Self-reported, so don’t rely on it for security. |
$tenant | The end user of your app the call is for (--tenant / tenantId); empty for your own calls. Use per: ["$tenant"] to give each end user their own limit. See Multi-tenant apps. |
$time, $date, $weekday | HH:MM, YYYY-MM-DD, and monday … sunday, in defaults.timezone |
$emails | Every email address anywhere in the request |
$email_domains | The lowercase domain of each address in $emails |
$urls, $hosts | Every http(s) URL anywhere in the request, and their lowercase hosts |
$strings | Every string value in the request |
$body_bytes | Size of the request body in bytes |
$item_count | Length of the largest list in the request |
{ "field": "$http_method", "operator": "==", "value": "DELETE" }Condition Options
Version 2.
Value references
Instead of writing a value out, a condition can point to one:
| Value | Compares against |
|---|---|
{"$list": "blocked_domains"} | A named list |
{"$field": "account.owner_id"} | Another field of the same call |
{"$env_var": "ALLOWED_CUSTOMER"} | An environment variable of the process running Swytchcode |
{ "field": "customer_id", "operator": "!=", "value": { "$env_var": "ALLOWED_CUSTOMER" } }ignore_case
"ignore_case": true compares text without regard to case: Gmail.com matches gmail.com.
transform
Changes the field’s values before comparing. One name, or a list applied in order:
| Transform | Result |
|---|---|
lower, upper, trim | Text changed accordingly |
length | Length of each value |
count | Number of values (e.g. recipients) |
domain | Domain of an email address or host of a URL |
abs | Absolute value of a number |
{ "field": "to", "transform": "count", "operator": ">", "value": 10 }quantifier
When a field has several values, "quantifier": "any" (the default) matches if any value matches, and "all" only if every value matches.
Conditions on Earlier Calls
Version 2. These operators look at calls made earlier from this project on this machine.
| Operator | Matches when | Keys |
|---|---|---|
rate | max or more calls were made in the window | window, max, per |
sum | The total of field over the window, including this call, would be over max | field, window, max, per |
repeat | The same call (same tool and arguments) was made max or more times in the window | window, max, per |
called_before | One of the tools in value was called successfully in the window | value, window |
returned_by | The request’s field has a value that one of value.tools returned in its value.field | field, value, window (default 30 days) |
windowis a number withs,m,h, ord:"30s","10m","24h","7d".percounts separately for each value of the listed fields, e.g."per": ["customer"]for a limit per customer. Forrepeat,pernarrows “the same call” to those fields.rate,sum, andrepeatcount every sent call to the policy’s targets, whatever the rest of the condition says. A call that got no response still counts.called_beforeandreturned_byremember only calls that got a2xxresponse.- Dry runs and policy checks read the counts but never add to them.
{ "id": "daily-spend-cap", "target": ["stripe.payment_intent.create", "stripe.charge.create"], "when": { "operator": "sum", "field": "amount", "window": "24h", "max": 50000, "per": ["currency"] }, "action": { "type": "QUOTA_EXCEEDED", "message": "Daily spend of {{$sum}} {{currency}} would exceed {{$limit}}" }}{ "id": "delete-only-own-sandboxes", "target": ["e2b.sandbox.delete"], "when": { "operator": "not", "conditions": [{ "field": "sandbox_id", "operator": "returned_by", "value": { "tools": ["e2b.sandbox.create"], "field": "sandbox_id" }, "window": "7d" }] }, "action": { "type": "POLICY_BLOCKED", "message": "Only sandboxes the agent created can be deleted" }}A response-count limit uses sum with "field": "$response_items": each call adds the number of items its response returned.
Where the counts are kept
Counts are kept per project, per machine, in two tables of ~/.swytchcode/usage.db (the local usage database Swytchcode already uses). They store hashes of the counted values, times, policy IDs, and summed amounts - never request bodies. A project is the folder that holds .swytchcode/, so a second clone, or the same project on another machine, has its own counts.
This means a limit applies to one machine. Five deployed agents on five machines each get their own daily cap. Editing a condition starts its counts over. Use swy policy state to see or reset them.
Detectors
Version 2. Two operators look for sensitive data in text:
| Operator | Finds |
|---|---|
contains_pii | Email addresses, phone numbers (10-15 digits), card numbers (checked with the Luhn digit), US social security numbers, and IBANs (checked with their checksum). A value such as ["card", "ssn"] narrows the kinds. |
contains_secret | Stripe live keys (sk_live_, rk_live_), GitHub and Slack tokens, AWS access keys (AKIA...), PEM private keys, and JWTs. |
Use them on $strings for the whole request, on one field, or on a $response field.
{ "field": "$strings", "operator": "contains_secret" }Detectors are pattern-based. They catch common formats and will miss others; treat them as a safety net, not a guarantee.
MODIFY
Version 2. MODIFY changes the call instead of blocking it:
{ "id": "cap-page-size", "target": ["stripe.customer.list"], "when": { "field": "limit", "operator": ">", "value": 100 }, "action": { "type": "MODIFY", "set": { "limit": 100 }, "message": "limit capped at 100" }}| Key | Does |
|---|---|
set | Sets fields to values. A field is changed wherever it already is; a missing one is added to body when the call has one, otherwise to the top level. |
remove | Deletes fields. |
add | Appends a value (or each value of a list) to a list field, skipping values already there. A missing field is set. |
dry_run | true turns the call into a dry run. |
MODIFY policies run first. The changed call is checked against the tool’s input schema again, and every other policy - including approval - sees the changed call. The output’s policy_notes tell the agent what changed; the audit log keeps the original and the changed arguments.
MODIFY can’t change auth, header, host, or idempotency fields, or fields named like tokens, secrets, passwords, or API keys.
Response Policies
Version 2. A policy with "stage": "response" runs after the call, on its final response, before anyone sees it: the CLI output, the MCP result, and the audit log’s copy.
Fields:
| Field | Value |
|---|---|
$response | The whole response body |
$response.<path> | A path into the body, e.g. $response.data[*].email |
$status_code | The HTTP status |
$response_bytes | Size of the body in bytes |
$response_items | Length of the largest list in the body |
Request fields and built-in fields work too. Conditions on earlier calls don’t.
Actions:
| Action | Does |
|---|---|
REDACT | Replaces the values at fields ($response paths) and everything detect finds (email, phone, card, ssn, iban, pii for all of these, secret) with [REDACTED]. |
TRUNCATE | Keeps at most max_items items of the lists at fields, or - without fields - of the body when it is a list and of each list directly inside it (such as data or items). |
POLICY_BLOCKED | Withholds the whole response. Exit code 6. The error says the call was made and must not be repeated. |
{ "id": "hide-card-numbers", "target": ["stripe.*"], "stage": "response", "when": { "field": "$status_code", "operator": "<", "value": 300 }, "action": { "type": "REDACT", "detect": ["card", "secret"] }}A response policy can’t undo the call - the provider has already acted. It only controls what is shown.
The output gets policy_notes saying what was hidden or cut. Some limits apply:
- A response cut off at the provider’s
max_response_bytesis withheld when a response policy needs its structure (a$responsepath,fields, orTRUNCATE). Adetect-onlyREDACTstill runs on the text. swy exec --output <file>is refused for a tool with response policies, before the call is made, because a downloaded file can’t be checked.
mode
Version 2. "mode": "monitor" records matches in swy audit policy but never acts on them. Use it to try a rule on real traffic before enforcing it. A monitor-mode policy that can’t be evaluated never stops a call.
Schedule
Version 2. active_from and expires_at limit when a policy applies. Each is a date (YYYY-MM-DD) or an RFC3339 time. A date is read in defaults.timezone; expires_at with a date still applies on that day.
{ "id": "change-freeze", "active_from": "2026-12-20", "expires_at": "2026-12-31", "...": "..." }swy policy validate warns about expired policies so they can be removed. "enabled": false switches a policy off at any time.
Alerts
Version 2. notify makes a call through Swytchcode when the policy matches - usually a message to Slack or email. It uses a tool installed in the project and its connected account:
{ "notify": { "tool": "slack.chat.postMessage", "args": { "channel": "#payments", "text": "Blocked a payment of {{amount}} to {{customer}}" } }}- An alert is sent for blocks, monitor-mode matches,
MODIFY,REDACT, andTRUNCATE. ForREQUIRES_APPROVAL, it is sent once, when the approval request is created. - A failed alert never changes the outcome of the call. It is recorded as
alert_failed. - Dry runs,
--democalls, policy checks, and approved calls being replayed send no alerts.
Approval Options
Version 2. REQUIRES_APPROVAL policies take two more keys.
reuse
One approval covers later matching calls for a while:
{ "id": "big-refunds", "target": ["stripe.refund.create"], "when": { "field": "amount", "operator": ">", "value": 50000 }, "action": { "type": "REQUIRES_APPROVAL", "message": "Refunds over 500.00 need approval" }, "reuse": { "for": "24h", "same": ["charge"] }}After a call is approved and run, later calls to the same tool with the same values in the same fields go through without a new request until for runs out. Without same, any call to the tool is covered.
approval_timeout
"approval_timeout": "30m" sets how long an approval request waits. After that, a late approval never runs the call. Requests always expire after 48 hours.
Tests
Version 2. A policy can carry example calls and what should happen to them. swy policy validate runs them, and so do the MCP policy check and add tools.
{ "id": "hourly-email-limit", "target": ["sendgrid.mail.send"], "when": { "operator": "rate", "window": "1h", "max": 100 }, "action": { "type": "RATE_LIMITED", "message": "At most 100 emails an hour" }, "tests": [ { "name": "over the limit", "before": [{ "tool": "sendgrid.mail.send", "times": 100, "ago": "10m" }], "expect": "block" } ]}| Key | Description |
|---|---|
name | Optional; shown when the test fails. |
tool | The tool called. Default: the policy’s first target. Required when that is a pattern. |
args | The call’s arguments. "$x" keys set built-in fields (e.g. "$env": "production"), and "$response" / "$status_code" give a response for response policies. |
before | Earlier calls: tool, args, response, times (default 1), and ago (default 1s). Each counts as sent with a 2xx response. |
expect | allow, block, approval, modify, or - with "$response" in args - redact or truncate. |
modified_args | With modify: the arguments the call must be sent with. |
response | With redact or truncate: the response the agent must see. |
A test runs against the whole file, the way a real call is decided: MODIFY first, then the other policies on the changed call, then response policies, including on_no_match. So a test can fail because another policy blocks the call; the failure names it:
× validation errors: • policy "no-personal-email" test 2: expected allow, got block by on_no_matcherror: 1 of 4 policy test(s) failedTests start from no earlier calls and never send, count, alert, or log anything.
policies.json vs execution_policy
Two files decide how a call runs. They don’t overlap:
policies.json | execution_policy in manifest.json | |
|---|---|---|
| Question it answers | Should this call run, and in what form? | How is it sent? |
| Scope | Any call, matched by target and condition | One integration |
| Examples | Block, hold for approval, limit per day, change arguments, hide response data | Retries, timeouts, concurrency, idempotency, response size |
| Who writes it | You | Comes with the integration; you can override fields |
| When it runs | Before the call (request policies) and after the final response (response policies) | While the call is sent, including retries |
A few interactions to know:
- A retried call counts once for
rate,sum, andrepeat. - A policy’s
RATE_LIMITEDerror has"source": "policy"; a provider’s 429 doesn’t. max_response_bytescan cut a response a response policy needs; see Response Policies.
Best Practices
- Keep policies focused on a single responsibility, with descriptive IDs.
- Add
teststo each policy and runswy policy validatein CI. - Try new rules with
"mode": "monitor"before enforcing them. - Store policies in version control alongside your project, and review changes like code.
- Test policies in sandbox mode before deploying to production.
Now that you understand guard policies, continue with: