Rate limits
Mosaic rate-limits to keep the graph responsive and the unit economics sane. Limits apply equally to the MCP server and the HTTP API — they share quota. The numbers below match what the middleware enforces today; planned tightening is flagged explicitly in the "Roadmap" section.
Request quotas (live)
Every request passes a per-IP token bucket. Authenticated requests additionally pass a per-key bucket sized by tier. Both are sliding 60-second windows.
| Tier | Scope | Rate | Tool surface |
|---|---|---|---|
| Unauthenticated | Per IP | 60 / minute | Public endpoints only |
| Free | Per key | 60 / minute | Lookup tools only (see below) |
| Pro | Per key | 300 / minute | Full tool surface |
| Enterprise | Per key | 1000 / minute | Full + private deployment |
Tier and current usage are visible at /account. Upgrade at /pricing.
Free-tier tool gating
The free tier covers every 1st-order lookup the KG exposes — enough to evaluate Mosaic against your own workflow before paying. KG-native multi-hop tools (resistance bypass, synthetic-lethal whitespace, talent migration, modality gaps, emerging signals) require Pro. Calling a paid tool on a free key returns a structured PaidTierRequired error with the list of free-tier tools and an upgrade link.
Free-tier tools also have per-call result caps (e.g. 10 compounds/papers/patents per response, 5 analogs); Pro raises these to 50, Enterprise to 200. See tools reference for per-tool limits.
When you're throttled
You'll receive a 429 Too Many Requests with this header:
Retry-After— seconds to wait before the bucket refills enough for the next call. Honor this directly; it's the simplest correct backoff.
The response body is JSON: {"detail": "Rate limit exceeded. Try again later."} for per-IP throttles, or {"detail": "API key rate limit exceeded."} for per-key throttles.
Backoff that doesn't thrash
Tight retry loops on a 429 will keep you throttled. The minimum-viable client behaviour:
import time, random, httpx
def call(url, headers, payload, max_attempts=5):
for attempt in range(max_attempts):
r = httpx.post(url, json=payload, headers=headers, timeout=30)
if r.status_code != 429:
r.raise_for_status()
return r.json()
# Honor Retry-After if present, else exponential with jitter.
wait = float(r.headers.get("Retry-After", 0)) or (2 ** attempt + random.random())
time.sleep(wait)
raise RuntimeError("rate limited beyond max_attempts")MCP clients (Claude Desktop, Cursor, Claude Code) handle backoff for you — this code is for direct HTTP API consumers.
Daily cost ceiling
Each paid-tier key carries a per-day budget against the downstream-cost ledger (LLM tokens, third-party API spend incurred fulfilling your request). Free $5/day, Pro $50/day, Enterprise $500/day. This is a cost cap, not a request cap — most workflows will hit the rate limit long before the budget. You can monitor consumption at /account.
Roadmap
Controls planned but not yet shipped — listed so client authors can choose whether to anticipate them:
- Daily request quotas — a per-key daily request count on top of the per-minute bucket (Free ~500/day, Pro ~5,000/day, Enterprise custom). Today the per-minute bucket is the only request-count cap.
X-RateLimit-*response headers —X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reseton every authenticated response, so clients can pace without waiting for a 429. Today onlyRetry-After(on 429) is emitted.- Bulk-export Pro add-on — a long-poll job surface that exempts large export queries from the per-minute cap in favour of a per-day throughput limit. Not yet implemented.
See authentication for key issuance and rotation.