Rate Limits
Per-plan quotas, response headers, 429 handling, and recommended client behaviour.
The Lynkist Public API enforces per-tenant rate limits on a fixed-window counter, with two
windows running at once: a per-minute budget and a per-day budget. Hitting either returns
429 Too Many Requests.
Plan tiers
Your tenant's plan determines your budgets. The current tiers:
| Plan | Per-minute requests | Per-day requests |
|---|---|---|
free | 0 | 0 |
starter | 10 | 100 |
premium | 100 | 5,000 |
enterprise | 1,000 | 50,000 |
free tenants cannot call the Public API at all — every request returns
403 API access is not enabled for your plan. Upgrade to starter or higher to enable
access.
Plan-to-limit resolution is not wired to billing yet — every tenant currently gets the
default (60 per-minute, 1,000 per-day) budget rather than the tier above. Track
Changelog for when this flips to live plan resolution.
Response headers
Every response — 2xx or 4xx — carries the same five headers, so you can pace yourself
without polling a separate endpoint:
| Header | Value |
|---|---|
X-RateLimit-Limit-Minute | Your per-minute budget for this window |
X-RateLimit-Remaining-Minute | Requests left in this minute |
X-RateLimit-Limit-Day | Your per-day budget for this window |
X-RateLimit-Remaining-Day | Requests left in this day |
X-RateLimit-Reset | Unix epoch seconds when the tighter window resets |
Example:
HTTP/1.1 200 OK
X-RateLimit-Limit-Minute: 60
X-RateLimit-Remaining-Minute: 23
X-RateLimit-Limit-Day: 1000
X-RateLimit-Remaining-Day: 871
X-RateLimit-Reset: 1748678400The reset value reflects the window that will trip first (usually the minute window).
The 429 response
HTTP/1.1 429 Too Many Requests
Retry-After: 17
X-RateLimit-Limit-Minute: 60
X-RateLimit-Remaining-Minute: 0
X-RateLimit-Limit-Day: 1000
X-RateLimit-Remaining-Day: 132
X-RateLimit-Reset: 1748678400
{ "detail": "Rate limit exceeded. Please slow down." }Retry-After is seconds until the budget refreshes. Honouring it is the simplest correct
client behaviour.
Window semantics
- Fixed window, per tenant. Both windows reset at the boundary (minute or UTC day), not on a rolling basis. A burst of 60 requests at 12:00:59 followed by 60 at 12:01:01 is fine even though it's 120 requests in two seconds.
- The first refused request returns 429. Counters are increment-then-check: by the time you
see
Remaining: 0, the next request will 429. The current one still goes through. - All requests count. Including the ones that 4xx out. Validation failures, scope denials, and 404s all consume budget — be careful with retry loops on bad payloads.
Recommended client behaviour
Reactive: honour Retry-After
Simplest correct pattern. On 429, sleep Retry-After seconds, then retry. Couple this with
an Idempotency-Key so retried mutations don't double-write.
import time, httpx
def call(method, url, **kwargs):
while True:
r = httpx.request(method, url, **kwargs)
if r.status_code != 429:
return r
time.sleep(int(r.headers.get("Retry-After", "1")))Proactive: watch the headers
Slightly more elegant. Pace yourself off X-RateLimit-Remaining-Minute so you never hit 429
in the first place — useful inside batch jobs.
import time, httpx
def paced_call(method, url, **kwargs):
r = httpx.request(method, url, **kwargs)
if int(r.headers.get("X-RateLimit-Remaining-Minute", "1")) <= 1:
time.sleep(max(0, int(r.headers["X-RateLimit-Reset"]) - int(time.time())))
return rBulk-writes: prefer /contacts/bulk
Importing a CSV of contacts? POST /contacts/bulk carries up to 100 contacts per request —
that's 100x the throughput of a per-contact loop with the same rate-limit cost.
Graceful degradation
If Redis (the rate-limit backend) is unavailable, the API degrades open — requests are
allowed through without limit-checking, and the response headers are simply omitted. So if you
ever see a response with no X-RateLimit-* headers, that is what happened — not a sign that
limits have been removed.
What's not rate-limited
- Webhook deliveries to your endpoints. Outgoing webhooks have no per-tenant rate limit — they're paced by the retry curve instead.
- Read-after-write on idempotent retries. If you replay a request with the same
Idempotency-Keywithin 24 hours, the response is replayed from cache without consuming a fresh slot.
Asking for a higher limit
For enterprise customers we provision custom quotas. Open a support ticket with a recent
X-Request-Id and the burst pattern you need to handle — concrete numbers (requests / minute,
duration of burst, frequency) help us give you a useful answer faster than "we need more."