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.
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.
