MisarMisar Docs
MisarMailMisarBlogMisarReachMisarPostMisarDevMisarCoderMisarSEOMisar PlatformMisar SSO
API Reference

Rate Limits

Per-route, per-user rate limits across the MisarSEO API, and which endpoints have none.

MisarSEO rate limits per user, per route. Each route has its own independent fixed window — spending your budget on keyword research does not affect your crawl budget.

Exceeding a limit returns 429 with code RATE_LIMITED and a Retry-After header in seconds.

Per user, not per IP

The limit key is the resolved caller. Sharing an API key across machines shares the budget; using separate keys for the same account does not increase it.

Limits by route

RouteMethodLimit
/seo/projectsPOST30 / 60 s
/seo/projects/:projectIdPATCH, DELETE30 / 60 s
/seo/crawl/startPOST5 / 60 s
/seo/crawl/:jobId/streamGET10 / 60 s
/seo/keywords/researchPOST10 / 60 s
/seo/keywords/savedPOST30 / 60 s
/seo/rank-tracking/configsPOST, PATCH, DELETE30 / 60 s
/seo/rank-tracking/keywordsPOST, DELETE30 / 60 s
/seo/rank-tracking/runsGET60 / 60 s
/seo/domain/overviewGET20 / 60 s
/seo/backlinksGET20 / 60 s
/seo/competitorsPOST10 / 60 s
/seo/ai/brand-lookupPOST5 / 60 s
/seo/ai/prompt-explorerPOST10 / 60 s
/seo/gsc/performancePOST20 / 60 s
/seo/gsc/inspect-urlsPOST10 / 60 s
/seo/local/business-listingsPOST20 / 60 s
/seo/local/questions-answersPOST20 / 60 s
/seo/account/exportGET5 / 60 s
/seo/account/deleteDELETE, POST3 / 3600 s

Routes with no rate limit

These are cheap reads or utility endpoints and are not throttled. They are still subject to plan quotas.

  • GET /seo/projects
  • GET /seo/projects/:projectId
  • GET /seo/crawl/:jobId/status
  • GET /seo/keywords/saved
  • GET /seo/rank-tracking/configs
  • GET /seo/rank-tracking/keywords
  • GET /seo/ai/brand-lookup/:jobId/status
  • GET /seo/gsc/connect
  • GET /api/health
  • GET, POST, DELETE /api/keys
  • POST /seo/citations

Poll status, do not poll the stream

GET /seo/crawl/:jobId/status and GET /seo/ai/brand-lookup/:jobId/status are unthrottled precisely so they can be polled. The SSE stream is capped at 10 opens per minute — open one and hold it, rather than reconnecting in a loop.

Plan quotas are separate

A 429 can also mean a plan quota, not a request-rate limit:

CodeMeaning
RATE_LIMITEDToo many requests in the window. Wait out Retry-After.
PLAN_LIMIT_REACHEDA plan allowance is exhausted, e.g. tracked-keyword count. Waiting does not help — upgrade or free capacity.
AUDIT_CAPACITY_REACHEDToo many audits queued or running. Wait for one to finish.

Always read code before deciding whether to retry.

Handling

retry.ts
async function withRetry(run: () => Promise<Response>, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    const res = await run();
    if (res.status !== 429) return res;

    const body = await res.clone().json().catch(() => ({}));
    // Waiting never clears a plan quota.
    if (body.code === "PLAN_LIMIT_REACHED") return res;

    const wait = Number(res.headers.get("Retry-After") ?? 5);
    await new Promise((r) => setTimeout(r, wait * 1000));
  }
  return run();
}

See Errors for the full code table.