Skip to main content
To keep the platform reliable for everyone, the Murmo API enforces a per-key request rate limit. Understanding the limit — and applying a few design patterns — lets you build a bot or agent that never has to slow down unintentionally.

The limit

1,200 requests / 60 seconds per API key

Measured on a rolling 60-second window. Check your current budget at any time with GET /api/v1/me — the response includes a rateLimit object showing your remaining allowance.
When you exceed the limit, the API returns:
HTTP 429 Too Many Requests
{
  "statusCode": 429,
  "code": "RATE_LIMITED",
  "message": "Rate limit exceeded. Please slow down.",
  "retryable": true,
  "details": {},
  "errorId": "..."
}
The response includes a Retry-After header telling you exactly how many seconds to wait before your next request. Always read this header — it is the fastest path back into your budget.

Three strategies to stay under the limit

1

Stream instead of poll

For live prices and group chat, use WebSockets. A single subscription delivers real-time updates as they happen without consuming any of your REST budget. A tight polling loop that fires every second costs 3,600 requests per hour; a WebSocket subscription costs zero.
2

Batch your reads

Prefer aggregate endpoints over per-resource loops. GET /api/v1/positions returns all open positions across perps, spot, and predictions in one call. GET /api/v1/groups/{id}/proposals returns spot and prediction proposals for a group together. One call beats ten.
3

Back off on 429

When a 429 does occur, apply exponential backoff with jitter — don’t hammer the API. Repeated 429 responses waste the remainder of your window rather than spending it on useful work. Always honor Retry-After first, then layer your own backoff on top.

Backoff example

The examples below implement a simple retry wrapper with exponential backoff and jitter. Backoff starts at 250 ms and caps at 8 seconds; the Retry-After header takes priority when present.
async function withRetry(fn, { tries = 5 } = {}) {
  for (let i = 0; i < tries; i++) {
    const res = await fn();

    if (res.status !== 429) return res;

    // Honor Retry-After if the server sends it
    const retryAfterSec = res.headers.get("Retry-After");
    const serverWait    = retryAfterSec ? Number(retryAfterSec) * 1000 : 0;

    // Exponential backoff with jitter
    const backoff = Math.min(2 ** i * 250, 8000) + Math.random() * 250;
    const wait    = Math.max(serverWait, backoff);

    console.warn(`Rate limited. Waiting ${Math.round(wait)}ms (attempt ${i + 1}/${tries})`);
    await new Promise((r) => setTimeout(r, wait));
  }

  throw new Error("Rate limited: gave up after maximum retries.");
}

// Usage
const res = await withRetry(() =>
  fetch(`${BASE}/api/v1/positions`, {
    headers: { Authorization: `Bearer ${KEY}` },
  })
);
Log X-Request-Id from every response alongside your own request metadata. If you need to report a 429 storm to support, that header lets the Murmo team trace exactly what happened on the server side.