Why a refusal is the cheapest thing the bakery does
Every sandbox costs the bakery requests, rows and queue operations, and a script could ask for thousands. This is the line of checks a request for one passes, why they sit in that order, what each costs the one who is turned away, and what the bakery gives up so that a refusal stays cheap.
A script at the door
Picture a loop that posts to /v1/sandboxes as fast as the network allows. Each sandbox it is given means a seed of about 2,500 rows written and as many deleted at the wipe (docs/adr/0005, estimate as of 2026-11-04). A design that says yes to the first thousand has spent the money before it learns that the thousand were one script. A design that says no to everything has no demo. The bakery says yes to a bounded number a day, and makes the no cost almost nothing.
What one check does, and where it breaks
The simple design is one gate: a counter, and a refusal when it is full. It is correct and it is expensive, because the counter lives in one place and every request, from a person or a loop, has to reach it and be counted. When the loop is the traffic, the gate’s own cost is the attack. It also fails the person: a full counter shuts the door on the visitor who arrived second after the script arrived first. No lab page shows this one yet, so the bakery explains it itself; the lab’s index is where the lab covers the family of techniques, and as read on 2026-11-06 it has five labs, none on admission.
Four checks, each ahead of what the next would cost
Nygard’s Release It! asks a system to shed load early, and to keep one failure from using up what another needs. The bakery’s checks run in the order of what they cost to run.
The rate limit. The router asks a Rate Limiting binding before any handler runs: one sandbox a minute per hashed address, 60 commands a minute per session, 6 socket upgrades a minute (workers/api/wrangler.jsonc). Cloudflare’s documentation, read on 2026-11-06, says those counts are kept per location and are eventually consistent, so the limit is approximate. The bakery treats it as a courtesy and never as a budget, and when the binding is unavailable the code lets the request through and logs rate_limit_unavailable once.
The human check. A guest’s request, and a demo account’s, then needs a Turnstile token, which workers/api/src/http/outbound.rs sends to Siteverify. Without a token it is refused turnstile-missing; if Siteverify cannot be reached, that is a refusal too, never a pass. Cloudflare’s documentation, read on 2026-11-06, makes a token valid for 300 seconds and good for one check. Until launch the page uses Cloudflare’s always-pass test key, so this layer is built and does not yet turn anyone away.
The ledger. Then the router asks Control, the one object that counts the day. Its admit runs in one transaction: a retry with the same Idempotency-Key gets the first answer, without taking a second place, even when the day has filled in between. Otherwise the pure decision in crates/app/src/admission.rs checks the flag, the day’s cap of 300, and for a guest the share of 100. A yes bumps the counts and writes the sandbox’s row. A no writes nothing, so a refused request leaves the ledger as it found it. Demo accounts have a cap of 100, and an account may make 3 sandboxes a day.
The token’s own limit. A script that has a sandbox can still lean on it, so every request an API token makes spends TOKEN_LIMIT, 60 a minute, before the Sandbox is asked. Simulated traffic has its own ledger in Control: 30 minutes a sandbox a UTC day, taken before the run starts and returned if it stops early.
A refusal that says when to come back
The layers are cheap in different currencies. A Turnstile pass costs the visitor a moment at the sign-in screen. A rate-limit refusal costs the Worker one binding call. A cap refusal costs one request to Control and a few reads. The order follows from that: the free refusals come before the one that touches an object.
Each answer says what to do. A rate limit is a 429 with Retry-After: 60. A cap is a 503 whose Retry-After counts the seconds to the next 00:00 UTC, and the page turns it into words such as “about 3 hours”. A flag set to off or read-only has no known end and carries none. A refusal that gave no time would send the visitor back at once, and a loop would not wait at all.
What it costs
- A visitor turned away on a busy day. The cap is 300 sandboxes a UTC day, 100 of them for guests. A guest who arrives after the hundredth is refused with “come back after 00:00 UTC”, though the other 200 places are not open to guests, and may sign in for one of their own.
- A widget on the creation screens. Every guest and demo sign-up loads Turnstile’s script from Cloudflare and spends a moment on a check. The script comes from Cloudflare, and no essay loads it.
- A rate limit that is not exact. Because it is approximate, it bounds one sender’s churn and nothing more; the daily count is exact only because one object keeps it.
- A breaker that is not built.
docs/cost.mddesigns a cron that reads real usage and setsControlto read-only. As of 2026-11-06 the cron holds no breaker, and the counts above are what stands between the account and a flood. At those caps cost.md works the worst month out at $5.29, 5.3% of the $100 ceiling, with limits as of 2026-11-04.
That is the trade docs/security.md names: the cap turns a denial of wallet into a denial of service. The money stays bounded, and the visitors who lose are the ones who arrive after the day is full.
Try it
Open sign-in and continue as a guest. Then ask GET /v1/status on the API, or read the counters on the status page, and compare today’s sandboxes with the caps. In the console, start traffic and stop it early: the minutes you did not use are returned to the day’s ledger.
Sources
- Release It!, Michael T. Nygard, 2nd edition, 2018: bulkheads and the shedding of load, as the bakery’s
docs/architecture.mdcites them. The book was not read on 2026-11-06. - Turnstile, Cloudflare’s Turnstile documentation, read on 2026-11-06: a check that a visitor is a person, which works without showing a CAPTCHA.
- Server-side validation, Cloudflare’s Turnstile documentation, read on 2026-11-06: a token valid for 300 seconds, validated once, and a widget alone that protects nothing without the Siteverify call.
- Testing, Cloudflare’s Turnstile documentation, read on 2026-11-06: dummy keys that always pass or always fail, for a build with no real keys.
- Rate Limiting, Cloudflare’s Workers documentation, read on 2026-11-06: a period of 10 or 60 seconds, counts kept in each location and eventually consistent, so a limit that is approximate.
- The lab, the lab index on ihaveasegway.com, read on 2026-11-06: five lab pages, and none on rate limits, caps or admission.
Back to the admission principle on How it’s built, to the decisions that chose it, ADR 0005 and ADR 0010, or to the lab.