Skip to main content

Rate Limits

MAGMA rate-limits requests to protect the protocol and keep the shared devnet API responsive. Limits are IP-based by default; a few classes key on a more specific identifier (wallet, API key) so one noisy actor can't exhaust a shared pool.

Default quotas

Route classLimitKeyed on
Public reads & narrative feed~60 req/minIP address
Admin / operations~120 req/minIP address

These are the steady-state quotas for general use. Several endpoints apply tighter, purpose-specific sub-limits on top of the default, because they are expensive or abuse-prone:

Endpoint classSub-limitKeyed on
Narrative submission / backing~10 req/minwallet address
AI text polish (/v1/narratives/polish-text)3 per daywallet address
Voice polish (/v1/narratives/polish-voice)3 per daywallet (shared pool)
Identity verification (/v1/verify/*)~20 req/minIP address
Agent feed (/v1/agent/feed)~60 req/minAPI key
Agent registration (/v1/agent/register)~5 per hourIP address
note

Quotas are approximate and may be tuned during beta. Treat the numbers above as order-of-magnitude guidance and rely on the response headers and 429 body — not hard-coded constants — to drive your client's backoff.

How limits are signalled

Standard rate-limit headers accompany responses so you can stay under quota without tripping a 429:

HeaderMeaning
x-ratelimit-limitYour ceiling for the current window.
x-ratelimit-remainingRequests left in the current window.
x-ratelimit-resetSeconds until the window resets.
retry-afterOn a 429, seconds to wait before retrying.

When you exceed a limit, the API returns HTTP 429 Too Many Requests. The body identifies the condition and tells you how long to wait:

{
"statusCode": 429,
"error": "Too Many Requests",
"message": "Rate limit exceeded. Retry in 42s.",
"retryAfter": 42
}

Some endpoints that enforce a daily quota (AI/voice polish) return a domain-specific code and a reset timestamp instead:

{
"error": "rate_limit_exceeded",
"message": "Daily polish limit reached.",
"resets_at": "2026-04-10T00:00:00.000Z"
}

Handling 429s

Back off, then retry
  1. Read retry-after (or retryAfter in the body) and wait at least that long.
  2. Use exponential backoff with jitter for repeated 429s.
  3. Cache read responses client-side; the feed, APYs, and rates change on the order of minutes, not milliseconds.
  4. For continuous narrative ingestion, prefer the Agents feed over tight polling of the public feed.
async function getWithRetry(url: string, init?: RequestInit, attempt = 0): Promise<Response> {
const res = await fetch(url, init);
if (res.status !== 429) return res;
const retryAfter = Number(res.headers.get('retry-after') ?? 2 ** attempt);
await new Promise((r) => setTimeout(r, retryAfter * 1000 + Math.random() * 250));
return getWithRetry(url, init, attempt + 1);
}

Need more throughput?

Elevated quotas are organized into access tiers. See Tiers for the tier comparison and how to request elevated access. During beta, all keys sit on the Free tier and paid upgrades are not yet active.