# Burst Desk > Paste your rate limits as configured and the traffic they face, and get what > each one actually admits - the fixed window that admits twice its limit at the > boundary, the token bucket whose burst is not its rate, and the per-key limit > that multiplies into something the backend cannot take. Site: https://burst-desk.skillsafe.ai/ ## What it is A single-page app for one sitting of rate-limit work. The work object is a LIMIT SHEET: what the service behind the limits can take, how many distinct keys exist, what a rejected caller does next, optionally which rule fronts all the traffic - then one rule per line with its algorithm, its limit, its window, its burst, its scope and the traffic offered to it. Everything the page computes it computes in the browser, free, before anything is spent: the worst window per algorithm against an exact sliding-window-log reference, the burst as `B + floor(r × elapsed)`, the per-key product `N × K` against the declared ceiling, the concurrency rate `c/S`, the debounce step, the reject share, the synchronised herd at the boundary and the retry multiplier. ## The one thing to know THE NUMBER IN THE CONFIG IS NOT THE NUMBER THAT GETS THROUGH, AND THE GAP OPENS IN FIVE PLACES. A FIXED WINDOW ADMITS EXACTLY 2× ITS LIMIT. Two adjacent windows each admit their full limit and a sliding window of the same length can contain the end of one and the start of the other, so 100 per 1m admits 200 in some window of 1m. Not a rounding error and not a tuning parameter - it is what two counters mean, and a cron job on a round schedule finds it every time. Only a SLIDING WINDOW LOG admits what it says (100 and never more), and it costs one timestamp per admitted request per key, for a whole window. A SLIDING COUNTER errs in BOTH directions by up to the limit itself. A TOKEN BUCKET ADMITS `B + floor(r × elapsed)`. A bucket of 50 refilling at 10/s admits 50 in ONE INSTANT and 60 over the first second against a stated 10 - 6× - falling to 1× over 1h. Every implementation defaults the capacity to the limit, which is what makes it invisible, and the span you plan capacity over decides whether it matters. PER-KEY LIMITS MULTIPLY BY `N × K`. 100 per 1m across 8,000 keys is 13,333.33/s against a backend that takes 800/s - 17× too loose to be protection. A faithful per-key limit would be 6 per 1m, which no caller could work with - so A PER-KEY LIMIT IS FAIRNESS BETWEEN CALLERS AND ONLY A RULE IN FRONT OF ALL THE TRAFFIC IS PROTECTION. A `GATE:` line names that rule and bounds the product. A CONCURRENCY LIMIT IS NOT A RATE: it is `c/S` per second and `S` is not in the config, so 5 in flight is 500/s at 10ms and 1/s at 5s - 500× the throughput from the same number. That is a feature (it tightens when the backend slows) and a trap for any capacity plan. A DEBOUNCE IS A STEP AT THE DELAY, NOT A RATIO: under a 500ms delay, 121 events 499ms apart fire ONCE, and at 500ms they all fire, with no region in between and no visible difference in the config. AND THE CALLERS ARE PART OF THE ARITHMETIC: under a fixed window every rejected caller's `Retry-After` points at the SAME INSTANT, so the herd is exactly the reject count and the limit clips it again; and at a reject share of `p` the load offered to the limiter is `1/(1−p)` times what the callers wanted. ## The lanes - **plan** — Turn an API and its traffic into a sheet. A limit is usually chosen from a number someone liked - a hundred a minute, ten a second - and then written in whatever algorithm the gateway offers, which is the one place the number stops meaning what it says. This takes the API as you describe it, the traffic it faces and what the backend can take, and writes the sheet the other four lanes read. - **read** — What each limit admits, as opposed to what it says. The paid read of the whole sheet. Every rule's worst window against its stated limit, the per-key multiplication against what the backend takes, what the rejected callers do next - and the one ordering that matters: the gap between the number in the config and the number that gets through, largest first. - **edges** — The edges: the boundary, the burst, and the exact reference. A fixed window admits its limit at the end of one window and again at the start of the next, so the most that ever passes in one window is exactly twice the number configured. A token bucket admits its capacity on top of its rate. A sliding counter is an estimate that errs in both directions. Only a sliding log admits what it says - so this puts every rule beside that reference and names the gap. - **keys** — The keys: what the multiplication puts in front of the backend. A limit of N per key with K keys admits N×K, and that is the number the service sees. A hundred a minute across eight thousand keys is thirteen thousand a second. This works out the product for every rule, compares it with what the backend can take, and says what would actually bound it - because a per-key limit is fairness between callers and only a rule in front of all the traffic is protection. - **decide** — Decide what changes: the algorithm, the number, or the caller. Sorts every gap into what a different algorithm closes, what a different number closes, what only a change on the caller's side closes, and what nothing closes because the property is the point. The last bucket is the honest one: a concurrency limit is not a rate and never will be, and a bucket's burst is the feature you asked for. ## The findings 26 findings across 5 scopes (sheet, edges, keys, shape, callers): 2 errors, 14 warnings, 10 notes. - `NOTHING-TO-ADMIT` (error) — The sheet declares no rules - `A-RULE-HAS-NO-LIMIT` (error) — A rule declares no limit - `THE-SHEET-HAS-LINES-THIS-PAGE-COULD-NOT-READ` (warning) — Some lines were not understood - `THE-SAME-RULE-TWICE` (warning) — A rule is declared twice - `THE-WINDOW-WAS-ASSUMED` (note) — The window was assumed - `THE-KEY-COUNT-WAS-ASSUMED` (note) — The key count was assumed to be one - `THE-OFFERED-TRAFFIC-WAS-NOT-DECLARED` (note) — Some rules declare no offered traffic - `A-FIXED-WINDOW-ADMITS-TWICE-ITS-LIMIT` (warning) — A fixed window admits exactly twice its limit at the boundary - `THE-COUNTER-IS-AN-ESTIMATE-IN-BOTH-DIRECTIONS` (warning) — A sliding counter mis-rejects in both directions - `ONLY-A-SLIDING-LOG-KEEPS-THE-NUMBER-IN-THE-CONFIG` (note) — One algorithm here is exact, and it is the expensive one - `THE-BURST-IS-NOT-THE-RATE` (warning) — A token bucket's burst is a separate number from its rate - `A-BUCKET-WITH-NO-DECLARED-BURST-STILL-HAS-ONE` (note) — An undeclared bucket capacity defaults to the limit - `THE-PER-KEY-LIMIT-MULTIPLIES-PAST-THE-BACKEND` (warning) — The per-key limits multiply to more than the backend can take - `THE-PER-KEY-LIMIT-IS-BOUNDED-BY-THE-GATE` (note) — The per-key limits multiply past the backend, and a gate bounds them - `A-FAITHFUL-PER-KEY-LIMIT-WOULD-BE-ABSURD` (note) — Sharing the backend across the keys gives an unusable per-key limit - `NOTHING-HERE-LIMITS-THE-TOTAL` (warning) — Every rule is per-key, so nothing limits the total - `A-CONCURRENCY-LIMIT-IS-NOT-A-RATE` (warning) — A concurrency limit is a different rate on every endpoint - `A-CONCURRENCY-LIMIT-WITH-NO-SERVICE-TIME` (warning) — A concurrency limit was declared with no service time - `A-DEBOUNCE-COLLAPSES-THE-STREAM-ENTIRELY` (warning) — A debounce below the event gap fires exactly once - `THE-REJECTS-ARRIVE-AS-ONE-HERD` (warning) — Every rejected caller retries at the same instant - `THE-RETRIES-ARE-OFFERED-LOAD` (warning) — The retries are load the limiter has to reject again - `A-RULE-DOES-NOT-BIND-AT-ALL` (note) — Some rules reject nothing - `A-RULE-REJECTS-MOST-OF-WHAT-IT-SEES` (warning) — Some rules reject most of the traffic offered to them - `WHAT-GETS-THROUGH-IS-NOT-WHAT-THE-CONFIG-SAYS` (warning) — The number that gets through is not the number in the config - `EVERY-GAP-NEEDS-A-TEST-AT-THE-BOUNDARY` (note) — Each gap needs a test that fires at the boundary, not in the middle - `NOTHING-CHANGES-FOR-THESE` (note) — Some rules admit exactly what they say Several are gated on a WITNESS rather than on a shape, because a warning that fires on every usable sheet carries no information: the per-key product has to exceed a declared backend ceiling, the burst has to be worth at least 25% of a window, the herd has to be at least 20% of the limit, and a rule counts as non-binding only when its limit is at least twice the traffic offered to it. And `THE-PER-KEY-LIMIT-IS-BOUNDED-BY-THE-GATE` is the same arithmetic as the warning above it at a lower severity, because a `GATE:` line does not change the product - it changes whether the product can reach the service. ## Sheet grammar A sheet has a header and a `LIMITS:` block. ``` BACKEND: 800/s what the service behind the limits can take KEYS: 8000 how many distinct keys exist (the multiplier) CLIENTS: retry what a rejected caller does: retry | drop | queue GATE: gateway the rule that fronts ALL the traffic, if there is one WINDOW: 1m a default window for rules that give a bare count SERVICE: 200ms a default service time for concurrency rules JOB: what this API is free text, for the report LIMITS: name algorithm limit=100/min [window=] [burst=] [rate=] [scope=key|ip|global|user|tenant|endpoint] [keys=] [service=] [delay=] [shape=steady|bursty|spiky|front|back] [offered=120/min] ``` **The rule line.** The name and the algorithm are required, and so is `limit=` for everything except `debounce` and `throttle`, which need `delay=` instead. Everything else is optional and every one of them changes the answer. - `limit=` takes `100/min`, `10/s`, `5/15min` or a bare count. **A bare count is a count, not a rate** - the window comes from `window=`, from the `WINDOW:` header, or from the default, and which one is reported. - `burst=` is a bucket's capacity. Leave it off and it defaults to the limit, which is what every implementation does and is why the burst gets forgotten. - `scope=` decides whether the limit multiplies. `global` does not; everything else does, by `keys=` or the `KEYS:` header. - `service=` turns a concurrency limit into a rate. Without it the row is undefined and is left out of every total. - `offered=` is the traffic that actually arrives. Without it the page can say what a rule admits but not what it rejects - no reject share, no herd, no verdict. - `shape=` says where in a window the traffic sits, which is what decides whether a sliding counter's error is in the direction that matters. **`GATE:` is the one field that changes a verdict rather than a figure.** A globally-scoped rule caps the traffic of that rule; four global rules on four endpoints sum, and this page sums them. A rule that fronts *all* the traffic is an upper bound on all of it - so naming one turns the per-key multiplication from unbounded into bounded, and the finding about it from a warning into a note. Anything unreadable becomes a stated problem rather than a guess: an unknown header key, a line outside a block, a rule with no algorithm, an unknown algorithm, a missing or unreadable limit, an unknown field and a duplicate name are each reported with their line number. ## Worked sheets - **public** — Five rules, and every one admits more than it says. 5 rules, 4 overshooting, worst 6×; 45,799.98/s against 800/s - 57× over. - **sized** — The same API, sized. 6 rules, 1 overshooting, worst 2×; 700/s against 800/s - inside. - **boundary** — One limit, four algorithms, four answers. 4 rules, 3 overshooting, worst 2×; 699/s against 500/s - 1.4× over. - **burst** — Buckets that are mostly capacity. 4 rules, 3 overshooting, worst 31×; 307/s against 200/s - 1.5× over. - **keys** — Modest limits, twenty-four thousand tenants. 4 rules, 0 overshooting, worst 1×; 312,033.33/s against 400/s - 780× over. - **shape** — The two knobs that are not rates. 6 rules, 0 overshooting, worst 1×; 540.87/s against 300/s - 1.8× over. - **herd** — Every Retry-After pointing at the same instant. 2 rules, 1 overshooting, worst 2×; 13.33/s against 2,000/s - inside. - **clean** — Designed to mean what it says, and does. 4 rules, 0 overshooting, worst 1×; 400/s against 1,000/s - inside. - **broken** — Lines this page could not use. 1 rules, 0 overshooting, worst 1×; 1.67/s against 900/s - inside. ## What it cannot do What this page cannot do. - **It reads a sheet, not a gateway.** It knows the rules and the traffic you type in. It generates no load, inspects no config and reads no metrics - so every figure is a consequence of the numbers you declared, and a key count or a backend ceiling that is out by a factor is a whole answer that is out by a factor. - **Worst-case is not typical-case.** "Admits 2N at the boundary" is a statement about the worst window, not the average one. A client that does not synchronise with your window edge will never see it; a cron fleet will see it every time. This page computes the ceiling because the ceiling is what a capacity plan needs, and it does not model your traffic's actual distribution. - **`offered=` is one number for a whole window.** Real traffic has a shape, and `shape=` is a hint rather than a distribution. The sliding counter's error direction in particular depends on where in the previous window the traffic sat, which one number cannot express. - **The key count is the load-bearing assumption.** Every multiplication rests on it, it is usually the number nobody owns, and "active keys" and "keys that exist" differ by an order of magnitude in most systems. The page states which it assumed and cannot tell you which is right. - **A gate is only a gate if it really fronts everything.** `GATE:` is your claim, not a measurement. If some traffic bypasses that rule - an internal caller, a different route, a cache miss path - the bound it provides is not real and the finding it downgraded should not have been downgraded. - **It does not model queuing, priority, fairness between keys under contention, or what happens downstream of admission.** A limiter that admits within the backend's ceiling can still produce a latency problem, and that is a different page. - **A clean sheet means these rules and this traffic.** It does not mean the API is protected, because the rules it checked are the ones you pasted. ## API `POST https://api.skillsafe.ai/v1/app-api/burst-desk/run` with an app session token. The body IS the input object - there is no `input` wrapper and no `X-App-Slug` header. Fields: `task` (one of plan, read, edges, keys, decide), `rules`, `prescan`, plus the lane's own fields. See https://burst-desk.skillsafe.ai/api.html for worked requests and https://burst-desk.skillsafe.ai/tokens.html for the token flow. ## Provenance Derived from the inngest-flow-control, rate-limiting, marketplace-rate-limiting and tanstack-pacer skills: the inngest-flow-control skill in github.com/inngest/inngest-skills, the rate-limiting skill in github.com/harperaa/secure-claude-skills, the marketplace-rate-limiting skill in github.com/vtex/skills, and the tanstack-pacer skill in github.com/tanstack-skills/tanstack-skills. Not affiliated with or endorsed by the authors of those skills.