---
title: "Create or replace the policy for an effect type"
method: PUT
path: "/v1/policies/{effectType}"
tags: ["Policies"]
---

# Create or replace the policy for an effect type

`PUT /v1/policies/{effectType}`

## Request

- Base URL: `https://ratchetgate.com`
- URL: `https://ratchetgate.com/v1/policies/{effectType}`
- Auth: HTTP bearer

on_indeterminate is the important field. "block" (the default) refuses to auto-retry an effect whose real-world outcome is unknown. Use "retry" only when the underlying vendor is genuinely idempotent, and "probe" when a caller must verify first.

## Path parameters

- `effectType` string, required

## Request body

- object
  - `mode` 'allow' | 'require_approval' | 'deny'
  - `on_indeterminate` 'block' | 'retry' | 'probe'
  - `lease_seconds` integer
  - `max_attempts` integer
  - `max_cost_micros` integer, nullable
  - `daily_budget_micros` integer, nullable
  - `retention_days` integer
  - `require_cost` boolean: Refuse a begin for this effect type unless it declares a cost. Turn this on wherever you have set a spend ceiling, so the ceiling cannot be bypassed by simply not declaring anything.
  - `required_dimensions` string[]: Dimensions a caller must declare on begin, or the call is refused. Set this alongside any dimension_limits entry: a ceiling keyed on a dimension is otherwise evaded by simply not declaring it.
  - `dimension_limits` object: Per-destination ceilings, e.g. {"counterparty":{"daily_micros":200000000,"daily_count":20}} for "no more than $200 and no more than 20 effects to any one counterparty per UTC day". daily_count applies to effects that declare no cost at all, which is what makes it usable for outbound messaging. Only a keyed hash of the value is ever stored.
  - `approval_above_micros` integer, nullable: Hold an effect for a human when the declared cost reaches this amount, in micro-USD. mode="require_approval" is all-or-nothing per effect type, which is why it gets switched off; this is the rule people actually have — "anything over $5,000 needs a second pair of eyes" — and it leaves routine work flowing. Sits below max_cost_micros, which refuses outright, and must not exceed it or it could never fire. Setting it makes a declared cost mandatory, since an effect declaring nothing would otherwise sail past it.
  - `reconcile_every_hours` integer, nullable: Remind me to reconcile this effect type this often, in hours. Ratchet cannot fetch your vendor's records — it has no credentials and no outbound access to your systems. It keeps the calendar instead: when the gap since the last POST /v1/reconcile exceeds this, it emits reconciliation.due. Reconciliation is the only control that finds actions which never asked at all, and it used to run only when somebody was already suspicious.
  - `structuring_threshold_micros` integer, nullable: A line to WATCH, never one to enforce. Nothing is refused for exceeding it. The structuring analysis measures how closely declared amounts crowd it, which is how you point Ratchet at a threshold it does not own — a reporting limit, an internal review limit. Null falls back to max_cost_micros.
  - `surge_per_hour` integer, nullable: Surge containment. New effects of this type per hour above which the circuit breaker opens. Budget ceilings catch an agent spending too much; this catches one doing too MUCH — a retry loop sending five thousand emails instead of three. Null (the default) disables it.
  - `surge_action` 'monitor' | 'require_approval' | 'deny': What an open breaker does. "require_approval" (the default) holds the work for a human rather than killing the agent, so nothing irreversible happens and no context is lost. "monitor" records and alerts but changes no decision — use it to watch before you enforce. "deny" refuses outright.
  - `surge_multiplier` integer, nullable: Relative alternative to surge_per_hour, for when you do not know your own traffic: how many times normal is definitely wrong. The baseline is the median hourly volume over the last 7 days, computed for you, and a learned ceiling never drops below 30 so quiet effect types are not tripped by an ordinary busy afternoon. surge_per_hour wins if both are set.
  - `surge_cooldown_seconds` integer: How long a tripped breaker stays open before closing itself. Closing grants a fresh allowance, so a cooldown is a real second chance.

## Response `200`

Default Response

- object
  - `effect_type` string
  - `mode` 'allow' | 'require_approval' | 'deny'
  - `on_indeterminate` 'block' | 'retry' | 'probe'
  - `lease_seconds` integer
  - `max_attempts` integer
  - `max_cost_micros` integer, nullable
  - `daily_budget_micros` integer, nullable
  - `retention_days` integer
  - `require_cost` boolean
  - `reconcile_every_hours` integer, nullable: How often this effect type should be compared against the vendor's own record. Ratchet cannot perform that comparison — it keeps the calendar and says when one is overdue, via the reconciliation.due event.
  - `approval_above_micros` integer, nullable: Declared cost at or above which begin returns approval_required instead of execute. Raises the decision only — it never turns an approval or a denial back into an allow. Null disables it.
  - `structuring_threshold_micros` integer, nullable: A line watched but never enforced. Nothing is refused for exceeding it; the structuring analysis measures how closely declared amounts crowd it.
  - `required_dimensions` string[]: Dimensions a caller must declare on begin, or the call is refused. Without this a ceiling keyed on a dimension is evaded by simply not declaring it.
  - `dimension_limits` object: Per-dimension daily ceilings, e.g. {"counterparty":{"daily_micros":200000000,"daily_count":20}} for "no more than $200 and no more than 20 effects to any one counterparty per UTC day". daily_count applies even to effects that declare no cost, which is what makes it usable for outbound messaging.
  - `surge_per_hour` integer, nullable: New effects of this type per hour above which the circuit breaker opens. Null disables surge containment, which is the default.
  - `surge_action` 'monitor' | 'require_approval' | 'deny'
  - `surge_cooldown_seconds` integer
  - `surge_multiplier` integer, nullable: Relative surge threshold: how many times normal is definitely wrong. Use when you do not know your own traffic. Does nothing until enough history exists to compute a baseline.
  - `surge_baseline_per_hour` integer, nullable: Median hourly volume over the last 7 days. Computed for you.
  - `surge_effective_ceiling` integer, nullable: The hourly ceiling actually in force, whichever rule produced it.
  - `surge_ceiling_source` 'absolute' | 'learned' | 'null', nullable
  - `is_default` boolean

## Other responses

- `400`: Invalid request.
- `401`: Missing or invalid API key.
- `402`: Insufficient prepaid credit.
- `403`: Scope, policy, or budget refusal.
- `404`: Not found.
- `409`: Conflicting state or idempotency-key reuse.
- `413`: Payload too large.
- `429`: Rate limit exceeded.

---

[API](https://skmtc.dev/ratchetgate/apis/ratchet-api.md) · [All operations](https://skmtc.dev/ratchetgate/apis/ratchet-api/llms.txt) · [OpenAPI document](https://skmtc.dev/ratchetgate/apis/ratchet-api/revisions/5712f33c692f?raw)
