Rate limits are the API's self-defense — and the client's most important contract. Every production integration eventually meets a 429; the difference between a robust integration and a flaky one is how it behaves at that moment. This guide explains the budgets, the response format, and the client patterns that make limits invisible.
The budgets
| Endpoint | Limit | Window |
|---|---|---|
| Link creation (per user) | 60 | 1 minute |
| Link creation (per IP) | 120 | 1 minute |
| Login (per IP) | 15 | 1 minute |
| Registration (per IP) | 20 | 1 minute |
| Contact form (per IP) | 5 | 1 minute |
| Newsletter (per IP) | 10 | 1 minute |
| Password gate (per link per IP) | 10 | 1 minute |
The dual user/IP limits matter for shared environments: an office NAT (one public IP, many users) gets more headroom, while a single user can't starve others by bursting.
The 429 contract
Limits speak RFC 9457 problem+json:
{
"type": "https://yas.sh/docs/errors#rate_limited",
"title": "Rate Limited",
"status": 429,
"code": "RATE_LIMITED",
"detail": "Too many requests",
"retryAfter": 37
}
Two things to honor: the status itself (429) and retryAfter (seconds). A client that sleeps exactly retryAfter and retries is compliant; a client that hammers through 429s makes the problem worse for everyone.
The client pattern: budget + jitter + backoff
The pattern that never sees a 429:
1. Budget: one create per second (60/min) — pace, don't burst
2. On 429: wait retryAfter seconds (never less)
3. On network error: exponential backoff 1s → 2s → 4s → 8s (cap 30s) + jitter
4. Retries are idempotent (Idempotency-Key per request)
async function createLinkWithRetry(body: object, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch("/api/v1/links", {
method: "POST",
headers: { "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify(body),
});
if (res.status === 429) {
const data = await res.json().catch(() => ({}));
await sleep((data.retryAfter ?? 5) * 1000 + Math.random() * 500);
continue;
}
return res;
}
throw new Error("rate limited after retries");
}
The jitter matters: synchronized retries from a batch create thundering-herd spikes. Randomize the delay by a small margin.
Why limits exist (and what they protect)
The same limits that protect the platform protect your data: a runaway loop that creates 10,000 duplicate links is cheaper to stop at 60/min than after the fact. The limits are generous for humans (1 per second sustained is far beyond interactive use) and deliberately tight for abuse paths (login, registration, contact).
Building dashboards against 429s
If your internal tooling integrates the API, surface rate-limit state: log 429s with their retryAfter, and alert if any single key exceeds 10% 429 rate — that's the signature of a misbehaving loop, not an active user.
Reading the response headers
Beyond the JSON body, the HTTP response headers carry the rate-limit state you can rely on without parsing. A production client should read and respect the standard header set:
RateLimit-Limit— the maximum number of requests allowed in the current window.RateLimit-Remaining— how many requests are left before you hit the limit.RateLimit-Reset— the Unix timestamp (or seconds remaining, depending on the convention) when the window resets.Retry-After— the number of seconds to wait before your next attempt, also returned on429.
Reading RateLimit-Remaining proactively lets you slow down before you trip the limit, rather than only reacting after a 429. A well-behaved client that respects Retry-After and paces toward RateLimit-Reset keeps integrations running smoothly even on shared or heavily loaded endpoints.
Bursting vs sustained throughput
Two different shapes of load behave very differently against the same limit:
- Bursts — a sudden spike of many requests in a short window. These trip the
429quickly because the window is usually one minute. Burst traffic should be spaced with a small delay (the "budget" pattern already shown). - Sustained throughput — a steady rate over minutes or hours. This is governed by the per-minute window repeated, and a steady sub-window pace (e.g. one request per second) sails through indefinitely.
The practical implication: if you have 1,000 links to create, do not fire them in 60 parallel requests all at once. Space them at roughly one per second, which lands well inside the per-minute budget while finishing in about 17 minutes. For longer jobs, add a simple progress check that measures the effective throughput and, if it starts degrading (headers show Remaining shrinking faster than expected), inserts a small backoff.
Common integration mistakes
Three mistakes come up repeatedly in support:
- Ignoring
Retry-Afterand retrying immediately on429. This guarantees a second429and makes the situation worse. Always sleep at least the returned value. - Retrying on
4xxerrors that are not rate limits. A422validation error will not succeed on retry — retry only idempotent-safe cases and rate/network errors. - Parallel bursts from a batch. If your script fires 50 requests at once, the first few succeed and the rest get
429s. Add a delay or a concurrency cap, and treat429as a signal to slow down, not to give up.
A disciplined client treats the rate limit as a contract to cooperate with, not an obstacle to fight.
Testing your client against the limit
You should test rate-limit handling before production, not during an incident. Make a small script that issues requests faster than the limit allows on a throwaway account, and confirm your client: reads the 429, sleeps Retry-After, and eventually succeeds. Also test the idempotency behavior by sending the same request twice with the same Idempotency-Key and confirming you get one resource, not two. These two tests cover the failure modes that most often break real integrations under load.
Key numbers to remember
The limits that matter most day to day: link creation is 60 per minute per user and 120 per minute per IP, login is 15 per minute per IP, registration 20 per IP, and the contact and newsletter forms are 5 and 10 per minute per IP respectively. The practical takeaway is that interactive humans will never hit these, while automation that does not pace itself will. If you remember one rule, it is this: sleep on Retry-After, pace writes at roughly one per second, and keep every retry idempotent.
In short, cooperate with the contract: pace writes, honor Retry-After, keep retries idempotent, and read the header state so you slow down before you are limited rather than after. Integrations that treat the budget as a partner run smoothly under load.
Conclusion
Rate limits are the API's contract for shared infrastructure: explicit budgets, a precise 429 response, and retry semantics. Build clients that pace, sleep on retryAfter, back off with jitter, and stay idempotent — and the limits will never touch you. The API reference documents every limit alongside each endpoint.
