Documentation

Integrate in one call

Ratchet has one important endpoint. Everything else exists to support it.

Machine-readable: OpenAPI · llms.txt · capability manifest · MCP tool schemas

1. Get a key

One request creates a workspace, seeds starter policies, and returns a key. The key is shown once and stored only as a keyed hash.

Or use the console if you'd rather click.

2. Ask before you act

Branch on decision. This is the whole contract.

DecisionMeaningWhat you must do
execute You hold the lease. Perform the action, then report.
duplicate Already completed. Use result. Do not act.
in_flight Someone else holds a live lease. Wait retry_after_seconds, ask again.
blocked A prior attempt's outcome is unknown. Verify at the vendor, then resolve.
approval_required Operator decision pending. Poll, or wait for the webhook.
denied Policy, budget, or rejection. Report the reason. Do not work around it.

3. Report what happened

The one rule that matters. Report failed only when you know the action did not reach the outside world. If you are unsure — a timeout, a dropped connection, an ambiguous error — report nothing. The lease lapses and Ratchet records an honest indeterminate. A false failed is worse than silence, because it licenses a duplicate.

Designing idempotency keys

The key is the entire mechanism. Derive it from the work, deterministically, so a retry of the same logical action produces the same key.

Good

  • welcome-email:user_123
  • invoice:2026-08:acct_88123
  • pr:acme/api:feature-auth
  • refund:order_551:full

Broken

  • uuid4()
  • "send-" + Date.now()
  • str(random.random())
  • f"job-{attempt_number}"

A key that changes on every attempt makes every retry look like new work. The gate can only recognise a duplicate if the duplicate says the same thing.

Reusing one key for genuinely different arguments is rejected with idempotency_key_reuse — that collision would otherwise hide a real, distinct action behind an unrelated record.

When the recorded result is sensitive, blind the key

Everything above is deterministic, which is required: the gate can only recognise a duplicate if the duplicate says the same thing. Deterministic also means guessable, and a duplicate decision returns the recorded result of the earlier attempt — that is what makes replay work. So anyone holding a key for your workspace who can guess refund:order_551:full can read what that refund recorded.

You do not have to choose between the two. Hash the meaningful key with a secret only your system holds:

key = "refund:order_551:full"
idempotency_key = hmac_sha256(YOUR_SECRET, key).hexdigest()[:32]

Still deterministic — the same action produces the same key on every retry, so duplicate detection is unchanged. No longer enumerable, because guessing it requires the secret. Worth doing wherever the result you report contains anything you would not want a compromised agent key to read back. Rate limits slow enumeration; they do not prevent it.

Policy: what happens when things go wrong

Configured per effect type. The default for an unconfigured type is the safe one.

FieldValuesEffect
modeallow · require_approval · deny Whether the type may run at all, and whether a human gates it.
on_indeterminateblock · retry · probe The important one. What a later caller may do when a prior attempt's outcome is unknown. Defaults to block.
lease_seconds5–3600 How long a holder has to report before the effect goes indeterminate.
max_attempts1–50 Ceiling on attempts for one key. Exceeding it denies the effect.
max_cost_microsinteger · null Refuse a single effect declaring more than this.
approval_above_microsinteger · null Hold a single effect for a human when it declares this much or more. Sits beneath max_cost_micros, which refuses outright — allow, hold, refuse. Raises the decision only: an amount below it never turns an approval or a denial back into an allow. Setting it makes a declared cost mandatory, since an effect declaring nothing would otherwise pass the line untested.
daily_budget_microsinteger · null Daily external-spend ceiling for this type.
reconcile_every_hours1–8760 · null Remind me to compare this effect type against the vendor's own record this often. Ratchet cannot fetch those records — it has no credentials at your vendors. It keeps the calendar and emits reconciliation.due when a comparison is overdue; you still post the records to POST /v1/reconcile.
require_costboolean Refuse a begin that declares no cost, so a spend ceiling cannot be bypassed by simply not declaring anything.
required_dimensionsstring[] · max 8 Dimensions a caller must declare on begin. Set this alongside any dimension_limits entry — a ceiling keyed on a dimension is otherwise evaded by simply not declaring it. Omitting one is refused, never allowed.
dimension_limitsobject · max 8 Per-destination ceilings: 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.
structuring_threshold_microsinteger · null A line to watch, never one to enforce — nothing is refused for exceeding it. Ratchet measures how closely declared amounts crowd it, which is how you point it at a threshold it does not own: a reporting limit, an internal review limit. Null falls back to max_cost_micros.
surge_per_hourinteger · null Surge containment. Absolute ceiling: new effects of this type per hour above which the breaker opens. Null (the default) is off.
surge_multiplierinteger ≥ 2 · null Relative ceiling, for when you don't know your traffic: how many times normal is definitely wrong. Normal is the median hourly volume over 7 days, computed for you. surge_per_hour wins if both are set.
surge_actionmonitor · require_approval · deny What an open breaker does. Defaults to require_approval.
surge_cooldown_seconds60–86400 How long a tripped breaker stays open before closing itself. Closing grants a fresh allowance measured from the count it cleared at, so a cooldown is a real second chance rather than an instant re-trip. A breaker you opened by hand ignores this and waits for you.
retention_days1–400 How long records and replayable results are kept.

Choose retry only when the underlying vendor is genuinely idempotent, or a duplicate is truly harmless. Ratchet will not assume that for you.

required_dimensions, dimension_limits and structuring_threshold_micros get a fuller treatment in the risk controls, including why a declaration can only ever tighten.

When an agent starts doing too much

Budget ceilings catch an agent spending too much. This catches one doing too much — which is a different failure, and usually a cheaper one.

A retry loop that sends five thousand emails instead of three may cost nothing at all. Every individual action is small, authorised, and indistinguishable from correct work. No per-effect limit sees it. Only the rate gives it away — and the gate is the one place that sees the rate, because every intended effect passes through it before it happens.

Two ways to set a threshold, and most people should reach for the second. surge_per_hour is an absolute ceiling, which requires knowing your own traffic. surge_multiplier asks a question you can actually answer — how many times normal is definitely wrong — and works out "normal" from your own history: the median hourly volume of that effect type over the last seven days.

A learned ceiling never drops below 30, so a quiet effect type is not tripped by one busy afternoon, and it does nothing at all until six hours of history exist — a brand new effect type has no normal to be a multiple of. The baseline is a median rather than a mean, so a single runaway hour cannot raise the ceiling meant to catch the next one.

ActionWhat happens when the breaker is open
monitor Records the trip and fires a circuit.tripped webhook. Changes no decision. Start here — watch before you enforce.
require_approval The default. The work waits for a human instead of the agent being killed.
deny Refused outright.

Why the default isn't a hard stop

A blocked agent loses its work and its context. A waiting agent keeps both while a human decides, and nothing irreversible happens in between. Killing the agent is available — it just isn't the default, because it's rarely what you actually wanted at 3am.

The emergency stop

One call halts every effect type in the workspace:

A breaker you open by hand has no cooldown. It stays open until you close it — a control you reached for in a panic must not quietly undo itself while you sleep. Closing gives the effect type a fresh allowance; it does not disarm the breaker, so a second surge trips it again.

Give your agent a key that can't undo this

Circuit routes need policies:write. The key you get at signup holds every scope, including that one — it's an operator key. Hand it to an agent and the agent can close its own breaker, which means it was never contained.

Two scopes are all a worker needs. This is worth doing whether or not you use surge containment: a key that can only ask and report cannot change policy, mint keys, read other effects, or spend beyond the ceiling you attach to it. Surge containment stays off until you set a threshold — GET /v1/circuits reports the volume you'd be setting one against.

Recovering an indeterminate effect

Check the vendor, then record what you found. This is what unblocks the key.

Subscribe to effect.indeterminate to be told the moment one appears, or poll GET /v1/effects?state=indeterminate.

Reversible effect groups

Rolling back a multi-step workflow

Declare several effects as one unit of work, and how to undo each. If the unit fails, Ratchet returns the exact compensation plan.

Then perform the plan

Each undo is itself gated. Use the suggested_idempotency_key and set compensates_effect_id. That is what stops a retried rollback from refunding twice — the failure that makes hand-rolled compensation dangerous.
Group stateMeaning
openAccepting steps.
committedEvery step succeeded. Can still be unwound later.
unwindingRolling back. Refuses new forward steps.
unwoundEverything reversible was reversed.
unwind_failedA step that succeeded had no compensation. Not a clean rollback, and it will not claim to be.

Read unresolved before acting on a plan. If any step's outcome is unknown, the rollback stops until you verify it — rolling back past a maybe is how a half-undone state is created.

Payments

Card, or crypto without custody

Subscribe with a card, or top up prepaid credit on-chain.

  • Non-custodial. Ratchet holds no private key. Payments go directly to an address the operator controls; Ratchet only watches the chain.
  • Quoted in USD. Credit granted is the USD amount, never the token amount, so a price move between quote and settlement cannot mint credit.
  • Underpayment is never rounded up. A short transfer credits nothing and waits for a human.
  • Volatile assets are refused unless a price oracle is configured. Quoting one without a live rate would mean inventing a price.

MCP

Nine tools over stdio or streamable HTTP, backed by the same code as the REST API.

Works today with Claude Code, Claude Desktop, Cursor, and any MCP client — see Get started for each. OpenAI-compatible tool loops and Gemini-style clients use the REST API directly.

Not listed in any provider directory. Ratchet is not submitted to, reviewed by, or endorsed by Anthropic, OpenAI, Google, or anyone else. One-click connector directories expect OAuth 2.1 with dynamic client registration; this endpoint uses a static bearer key, so installation is a config file you edit. That is honest about where it stands rather than implying a listing that does not exist.

Loading tool list…

Connecting

Errors

Every failure returns {"error": {"code", "message", "detail"}}. Branch on code; it is stable.

StatusCodeMeaningWorth retrying?
400invalid_requestSchema validation failed. detail.validation says where.do not Not until the request changes. The same body will fail identically.
400cost_requiredThis effect type requires a declared cost and none was sent.change first Send cost_micros, then retry.
400dimension_requiredA dimension the policy keys a ceiling on was not declared.change first Declare it — detail names which — then retry.
400invalid_paymentThe payment header is not base64-encoded JSON.do not Not until the payment payload is rebuilt.
400approval_threshold_above_ceilingapproval_above_micros sits above max_cost_micros, so it could never fire.do not Configuration, not traffic. Fix the policy.
401unauthorizedMissing, malformed, or revoked key.do not Not with this key. A revoked key never becomes valid again.
402insufficient_creditAllowance exhausted and balance too low. Replays still work.change first After a top-up. Retrying sooner returns the same answer.
402quota_exhaustedThe free allowance for this workspace is spent.change first After adding a payment method or on the next period.
403forbiddenMissing scope, or the plan does not carry the capability. detail.required names it.do not Not until the key's scopes or the plan change.
403budget_exceededA spend ceiling would be breached. detail.resets_at says when the window rolls.change first After the window resets, or after the ceiling is raised. Not before.
403cost_ceiling_exceededThe declared cost is above the per-effect maximum.do not Not at this cost. Lower it or raise the ceiling.
403run_budget_exceededThis run has spent its own wallet.do not Not in this run. A new run gets a new wallet.
404not_foundNo such record in this workspace. Cross-tenant lookups return this too, never a hint.do not Not unless the record is created.
409idempotency_key_reuseSame key, different payload fingerprint.do not Never unchanged — this is a bug in the caller. Send the original payload, or a new key for genuinely new work.
409invalid_stateThe transition is not legal from the current state.do not Re-read the effect first. Retrying blind will fail the same way.
409lease_expiredThe lease expired, so the outcome is recorded as indeterminate.change first Not this lease — it is gone. Call begin again, and treat the earlier attempt's outcome as unknown.
409lease_lostA newer attempt superseded your lease.do not Do not assume your work counted. Someone else holds the lease now.
409retry_requestThe effect record changed concurrently under you.retry Immediately. This is optimistic-concurrency loss, not a refusal.
409group_unwindingThe effect group is being compensated.do not Not into this group. Start a new one if the work is genuinely new.
409transaction_already_usedThat chain transaction was already submitted for another payment.do not Never. Reusing it is either a mistake or an attempt to pay twice.
409mfa_already_enabledTwo-factor is already on for this account.do not Nothing to do.
413payload_too_largeThe body or result exceeds the published limit.do not Not unchanged. Store the output elsewhere and record a reference.
429rate_limiteddetail.retry_after_seconds says how long to wait.retry After the interval given. Sooner is refused and still counts.
500internal_errorCarries a request id and nothing else, deliberately.retry With backoff. Quote the request id if it persists.
502payment_provider_errorThe payment provider failed upstream.retry With backoff. Nothing about your request was wrong.
503billing_unavailableNo live payment provider is configured.retry With backoff, though this usually needs an operator, not patience.