Lynkist Developers

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:

PlanPer-minute requestsPer-day requests
free00
starter10100
premium1005,000
enterprise1,00050,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:

HeaderValue
X-RateLimit-Limit-MinuteYour per-minute budget for this window
X-RateLimit-Remaining-MinuteRequests left in this minute
X-RateLimit-Limit-DayYour per-day budget for this window
X-RateLimit-Remaining-DayRequests left in this day
X-RateLimit-ResetUnix 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: 1748678400

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

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 r

Bulk-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-Key within 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."

Rate Limits — Lynkist Developers | Lynkist