Skip to content

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

Policies 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

Terminal window
swy policy add

Creates a policy interactively: target, one condition, and an action. Every prompt also has a flag, so it can run in scripts with --non-interactive:

Terminal window
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"
FlagDescription
--name, --idPolicy name and id (default: generated from the name).
--targetCanonical ID(s) the policy guards. Repeatable or comma-separated.
--field, --operator, --valueThe condition.
--action, --messageWhat happens when it matches, and the message shown.
--list <name>Compare against a named list instead of --value. Version 2.
--ignore-caseCompare 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|allWhen the field has several values, whether any or all must match. Version 2.
--mode monitorRecord 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

Terminal window
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

Terminal window
swy policy remove <policy-id>

Deletes a policy by its ID.


Validate policies

Terminal window
swy policy validate

Validation 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

Terminal window
swy policy state [<policy-id>] [--json]
swy policy state [<policy-id>] --reset

Shows 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:

Terminal window
swy audit policy # recent entries
swy audit policy -n 20 # last 20 entries
swy 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.

SituationBehavior
File does not existNo policies are applied. Execution continues normally.
File exists and is validPolicies are evaluated for every call.
File exists but is malformed or invalidEvery call stops with a validation error.
A list file can’t be readEvery 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 schemaCalls 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": 2 at 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": [
{ "tool": "sendgrid.mail.send", "args": { "to": "[email protected]" }, "expect": "block" },
{ "tool": "sendgrid.mail.send", "args": { "to": "[email protected]" }, "expect": "allow" }
]
}
]
}

The file has up to four top-level keys: version, defaults, lists (version 2), and policies.


defaults

The defaults object sets file-wide behavior.

KeyValuesDescription
on_violationfailWhat a match does. Only fail is supported.
evaluationpre_executionWhen request policies run. Only pre_execution is supported.
on_no_matchallow (default), deny, monitorWhat happens to a call no ALLOW policy matches. Version 2.
timezoneA time zone name, e.g. Asia/Kolkata, UTCThe 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. ALLOW policies have no effect.
  • deny - a call runs only when an ALLOW policy matches it. Every other call is blocked.
  • monitor - like deny, but calls that would be blocked still run and are recorded in swy audit policy. Use it to try an allowlist on real traffic before switching to deny.

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:

KeyDescription
idA unique identifier. Two policies with the same id are a validation error.
targetThe methods the policy guards.
whenThe condition that triggers the policy.
actionWhat happens when the condition matches.

Version 2 adds optional keys:

KeyDescription
description, owner, tagsNotes for people. Not used when deciding.
stagerequest (default) or response. See Response policies.
modeenforce (default) or monitor. See mode.
enabledfalse switches the policy off without deleting it.
active_from, expires_atWhen the policy applies. See Schedule.
notifyA call to make when the policy matches, e.g. a Slack message. See Alerts.
reuse, approval_timeoutREQUIRES_APPROVAL only. See Approval options.
testsExample 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

ActionWhat happens
POLICY_BLOCKEDThe call is blocked.
AUTH_FAILEDThe call is blocked as not authorized.
RATE_LIMITEDThe call is blocked as over a rate limit.
QUOTA_EXCEEDEDThe call is blocked as over a quota.
REQUIRES_APPROVALThe 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.

ActionWhat happens
ALLOWMarks the call as allowed. Only matters with on_no_match deny or monitor. A block still wins.
MODIFYChanges the call’s arguments, then lets it continue. See MODIFY.
REDACTResponse policies only. Hides values in the response. See Response policies.
TRUNCATEResponse 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, or not.

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

OperatorDescription
allEvery condition must match (AND).
anyAt least one condition must match (OR).
notInverts 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

OperatorsValueNotes
==, !=A scalar
>, >=, <, <=A number
in, not_inAn array (or a list)
contains, not_containsA stringSubstring. On a list, a whole item.
starts_with, ends_withA string
matchesA regular expression, up to 512 characters
exists, not_exists, empty, not_emptyNone
before, afterA timestamp (RFC3339)
between, outsideExactly 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:

PathMeans
customer.emailemail inside customer
items[*].priceprice of every item in the items list
items[0].priceprice of the first item
recipients.emailA 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.

FieldValue
$toolThe canonical ID being called
$providerThe provider (first part of the canonical ID)
$http_methodGET, POST, …
$host, $path, $urlWhere the request goes ($url has no query string)
$envsandbox or production
$workspaceThe linked workspace name
$callercli or mcp
$mcp_profileagent, operator or full (MCP calls only)
$mcp_clientThe name the MCP client reports when it connects. Self-reported, so don’t rely on it for security.
$tenantThe 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, $weekdayHH:MM, YYYY-MM-DD, and monday … sunday, in defaults.timezone
$emailsEvery email address anywhere in the request
$email_domainsThe lowercase domain of each address in $emails
$urls, $hostsEvery http(s) URL anywhere in the request, and their lowercase hosts
$stringsEvery string value in the request
$body_bytesSize of the request body in bytes
$item_countLength 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:

ValueCompares 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:

TransformResult
lower, upper, trimText changed accordingly
lengthLength of each value
countNumber of values (e.g. recipients)
domainDomain of an email address or host of a URL
absAbsolute 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.

OperatorMatches whenKeys
ratemax or more calls were made in the windowwindow, max, per
sumThe total of field over the window, including this call, would be over maxfield, window, max, per
repeatThe same call (same tool and arguments) was made max or more times in the windowwindow, max, per
called_beforeOne of the tools in value was called successfully in the windowvalue, window
returned_byThe request’s field has a value that one of value.tools returned in its value.fieldfield, value, window (default 30 days)
  • window is a number with s, m, h, or d: "30s", "10m", "24h", "7d".
  • per counts separately for each value of the listed fields, e.g. "per": ["customer"] for a limit per customer. For repeat, per narrows “the same call” to those fields.
  • rate, sum, and repeat count every sent call to the policy’s targets, whatever the rest of the condition says. A call that got no response still counts. called_before and returned_by remember only calls that got a 2xx response.
  • 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:

OperatorFinds
contains_piiEmail 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_secretStripe 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" }
}
KeyDoes
setSets 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.
removeDeletes fields.
addAppends a value (or each value of a list) to a list field, skipping values already there. A missing field is set.
dry_runtrue 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:

FieldValue
$responseThe whole response body
$response.<path>A path into the body, e.g. $response.data[*].email
$status_codeThe HTTP status
$response_bytesSize of the body in bytes
$response_itemsLength of the largest list in the body

Request fields and built-in fields work too. Conditions on earlier calls don’t.

Actions:

ActionDoes
REDACTReplaces the values at fields ($response paths) and everything detect finds (email, phone, card, ssn, iban, pii for all of these, secret) with [REDACTED].
TRUNCATEKeeps 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_BLOCKEDWithholds 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_bytes is withheld when a response policy needs its structure (a $response path, fields, or TRUNCATE). A detect-only REDACT still 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, and TRUNCATE. For REQUIRES_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, --demo calls, 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": "under the limit", "args": { "to": "[email protected]" }, "expect": "allow" },
{
"name": "over the limit",
"args": { "to": "[email protected]" },
"before": [{ "tool": "sendgrid.mail.send", "times": 100, "ago": "10m" }],
"expect": "block"
}
]
}
KeyDescription
nameOptional; shown when the test fails.
toolThe tool called. Default: the policy’s first target. Required when that is a pattern.
argsThe call’s arguments. "$x" keys set built-in fields (e.g. "$env": "production"), and "$response" / "$status_code" give a response for response policies.
beforeEarlier calls: tool, args, response, times (default 1), and ago (default 1s). Each counts as sent with a 2xx response.
expectallow, block, approval, modify, or - with "$response" in args - redact or truncate.
modified_argsWith modify: the arguments the call must be sent with.
responseWith 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_match
error: 1 of 4 policy test(s) failed

Tests 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.jsonexecution_policy in manifest.json
Question it answersShould this call run, and in what form?How is it sent?
ScopeAny call, matched by target and conditionOne integration
ExamplesBlock, hold for approval, limit per day, change arguments, hide response dataRetries, timeouts, concurrency, idempotency, response size
Who writes itYouComes with the integration; you can override fields
When it runsBefore 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, and repeat.
  • A policy’s RATE_LIMITED error has "source": "policy"; a provider’s 429 doesn’t.
  • max_response_bytes can cut a response a response policy needs; see Response Policies.

Best Practices

  • Keep policies focused on a single responsibility, with descriptive IDs.
  • Add tests to each policy and run swy policy validate in 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: