Inbound rate limits¶
The hub rate-limits its expensive endpoints — the ones that fan out to an LLM — per tenant, in a fixed 60-second window. Everything else (CRUD, views, ingest) is unlimited.
What is limited¶
| Bucket | Endpoint | Default rpm |
|---|---|---|
view-generate |
POST /api/views/generate |
10 |
platform-chat |
POST /api/platform/rag/chat |
60 |
user-chat |
POST /api/rag/chat |
60 |
platform-search |
POST /api/platform/rag/search |
60 |
view-generate gets its own tighter limit because one call = one full LLM
manifest-drafting round. The other three fall back to lumnik.ratelimit.default.rpm
(60).
Counting is per (bucket, tenant): one tenant hammering chat cannot exhaust
another tenant's budget, and the buckets are independent — burning user-chat does
not touch platform-search. Requests with no resolved tenant count under a shared
system key.
The 429 contract¶
When a window is exhausted the request is refused before the resource runs:
HTTP/1.1 429 Too Many Requests
Retry-After: <seconds until the window resets>
{"error":"rate limit exceeded"}
Retry-After is exact, not advisory — the window is a fixed 60s clock, so waiting
that many seconds is guaranteed to land in a fresh window. lm surfaces this
verbatim — rate limited (429): rate limit exceeded, retry in Ns — rather than a
generic error.
Tuning¶
Property form (per bucket, falling back to default):
lumnik.ratelimit.default.rpm=60
lumnik.ratelimit.view-generate.rpm=10
Env form for a deployed container — MicroProfile mapping, non-alphanumerics become underscores:
LUMNIK_RATELIMIT_DEFAULT_RPM=120
LUMNIK_RATELIMIT_VIEW_GENERATE_RPM=20
LUMNIK_RATELIMIT_USER_CHAT_RPM=0 # 0 disables that bucket entirely
0 disables — no counting, no 429s for that bucket. Restart the hub after changing
either form; limits are read per request from config, but env vars only reach the
process at boot.
Dev mode has NO limits¶
%dev sets both knobs to 0 — a local mvn quarkus:dev never returns 429, no
matter how hard you hit it. If you are trying to reproduce a customer's 429 locally,
you must set the properties back to a non-zero value; out of the box you can't.
(%test inherits the prod defaults, which is why the filter is testable.)
Single-node caveat¶
The counters live in memory in the hub process. Both shipped deploy paths (selfhost compose, Helm chart) run a single hub replica, so today this is exact. If you ever scale the hub to N replicas behind a balancer, each replica counts independently — the effective limit becomes up to N× the configured rpm. That is a known, deliberate trade (no Redis dependency for a single-replica product); revisit it when a multi-replica deploy is real, not before.
Counters also reset on every hub restart — a restart forgives the current window.
Not to be confused: the OTHER 429¶
The quickstart's troubleshooting table mentions 429 Too Many Requests from the
vendor — that one is a different animal. It is the source system (Stripe, Siebel,
whatever your REST connector pulls from) throttling lumnik's outbound calls
during a connector run; you tune it with default_rate_limit in the connector YAML,
not with any lumnik.ratelimit.* knob. This page's 429 is the hub throttling
inbound API callers (chat, search, view generation). Same status code, opposite
directions: one protects the vendor from lumnik, the other protects lumnik from its
own users.