What it means
A rate limit is a ceiling on requests per unit of time, enforced per caller. It exists for three reasons that are worth separating, because they imply different limits.
- Capacity. Shared infrastructure has a finite throughput, and one caller in a tight loop can consume all of it.
- Cost. Some calls spend money downstream. On a data API, a live platform read costs real upstream capacity that a cached catalogue read does not.
- Fairness and abuse. Limits are also what stop a credential from being used to bulk-scrape a corpus it was sold access to on different terms.
When you cross a limit, the correct status is 429 Too Many Requests, defined in RFC 6585 section 4. It is worth being precise about the neighbours: 403 means you are not allowed to make this call at all, 402 means the plan behind the key cannot pay for it, and 503 means the server itself is unwell. Only 429 means "you, specifically, are going too fast".
The algorithms, and why the choice is visible to you
Five implementations dominate, and each has a signature you can feel from the client side.
- Fixed window. Count requests per calendar minute, reset at the boundary. Cheap, and wrong at the edges: a 600-per-minute limit permits 600 requests at 11:59:59 and another 600 at 12:00:00, so 1,200 land inside one second. That boundary burst is why nobody protects a fragile backend with a fixed window alone.
- Sliding window log. Store a timestamp per request and count the ones inside the trailing window. Exact, and expensive in memory for a busy key.
- Sliding window counter. Weight the previous window's count by how much of it still overlaps. Close to exact at a fraction of the storage. This is what most gateways ship.
- Token bucket. A bucket of capacity C refills at R tokens per second; each request takes one token. Allows a burst of up to C and then settles to R. This is the model that matches how a published limit like "600 a minute" usually behaves in practice.
- Leaky bucket. Requests queue and drain at a fixed rate. Smooths output rather than permitting bursts. Common in front of hardware and message pipelines.
The token bucket formula is worth carrying in your head because you can implement the client-side mirror of it in five lines: tokens = min(capacity, tokens + elapsed_seconds * refill_rate), then take one token per call and sleep when the bucket is empty.
Limits stack, and each one has its own remedy
Real APIs rarely enforce a single number. The Pinlyx Data API stacks four independent ceilings, and a request can be refused by any of them:
- Burst. Requests per minute, from your tier. Refusal: 429 with
rate_limited. Clears within the minute. - Daily quota. Requests per day, from your tier. Refusal: 429 with
quota_exceeded. Clears at 00:00 UTC and not a second earlier. - Live reads. A separate, much smaller per-minute ceiling for calls that go to the platform right now rather than to the catalogue.
- Plan budget. Requests included in the billing period. Refusal: 402
payment_required. Waiting never helps; theX-Plan-*headers say how far past the line you are.
The tier table below makes a point that surprises most people the first time they do the arithmetic: on every tier except the top one, the daily quota binds long before the per-minute ceiling does. On the free tier, 30 requests a minute sustained would be 43,200 a day against a 1,000-a-day quota, so a full-speed job exhausts its day in a little over half an hour. The per-minute number is a burst allowance, not a budget.
Limits that are not yours
A 429 does not always mean you were greedy. Live reads on the five platforms scraped directly draw on an upstream budget shared across every caller, and the capability endpoint publishes each source's ceiling up front rather than making you discover it in production. LinkedIn company pages are capped at four reads a minute across the entire system, because the pages are heavy and LinkedIn blocks anything faster. No plan upgrade moves that number.
This is the case where retrying harder is actively counterproductive, and it is why a good API tells you which ceiling you hit rather than returning a bare 429. Design your client to read the code and the message, not just the status.
Reading the headers
Every response, successful or not, carries the accounting. That is deliberate: it lets a client slow down before it hits the wall rather than after. A worker that checks X-RateLimit-Remaining and adds a small sleep once it drops under ten percent of the limit will essentially never see a 429, and will finish sooner than one that sprints, bounces, backs off and retries.
Designing the client side
Four habits separate an integration that lives comfortably inside a limit from one that fights it constantly.
- Mirror the limit locally. A shared token bucket in front of your HTTP client, sized to the published ceiling, turns bursts into a queue instead of a wall of 429s.
- Cap concurrency separately from rate. Ten requests a second with unbounded concurrency can still open two hundred sockets during a slow patch.
- Honour Retry-After, then add jitter. Every client that was refused at the same moment will otherwise wake at the same moment.
- Page wide. Metering counts HTTP calls, not rows. Walking a list at 500 rows per page instead of 50 costs a tenth of the budget for the same data. See cursor pagination.
Related concepts
- Retry-After: the header that tells you exactly how long to wait.
- Exponential backoff: what to do when there is no Retry-After.
- Jitter: why a synchronised retry is a second outage.
- Flood wait: Telegram's own rate-limit response, with the same shape and a different name.
- MTProto: the protocol whose limits sit behind every Telegram send.
How Pinlyx handles it
Every Data API response publishes its own accounting, so nothing runs out silently: burst and daily limits from your tier, plan budget from what you bought, and a Retry-After on every 429. The two 429 codes are separated on purpose so a client can tell a one-minute pause from a wait until midnight. Inside the product, the same discipline applies to outbound messaging, where a per-account rate limiter paces sends and handles platform flood waits with automatic backoff.