Fundamentals

Rate limits & quotas

Two different mechanisms: rate limits stop a loop from hammering the API, quotas bound what a month can cost. They fail differently and you recover from them differently.

Rate limits

Counted per token, across the REST API and the MCP server together — using both does not double your allowance.

Scope of the limitAllowanceApplies to
Per token120 requests / minuteEverything
Per token20 replies / minuteComment replies specifically
Per IP60 failed authentications / minuteUnrecognised tokens

Exceeding one returns 429. Wait and retry — nothing is consumed and nothing is broken.

Why replies are stricter

A reply posts publicly and, on X, is billed by the platform. No human writes twenty replies in a minute, so the tighter limit only ever catches a loop — which is exactly what it is for.

Quotas

Quotas are monthly and belong to the subscriber — the workspace owner — not to a token or a workspace. A plan that includes several workspaces shares one pool between them, so moving work around does not reset anything.

PoolConsumed by
PostsEach publish that reaches at least one platform
X posts & repliesEach write to X — a tweet or a comment reply
X DMsDirect messages sent and received on X
X commentsComment data read from X
AI messagesEcho AI conversations in the app
Ads searchesFresh competitor ad searches

Publishing to Instagram, Facebook, YouTube, TikTok, LinkedIn, Threads, Bluesky, Pinterest and Google Business costs nothing on our side, so those posts are effectively unlimited. X is the one platform that charges per call, which is why it has pools of its own.

Hitting a quota

You get 429 with a message naming the pool and the numbers. Retrying does not help — the pool resets on the first of the month, or you upgrade the plan.

429 response
{
  "error": "Monthly X posts & replies quota reached (1000/1000). X is a paid API — upgrade your plan to keep replying there."
}

Reservations are refunded

Quota is reserved before the call and given back if the platform rejects it. A reply that fails to send does not count against you.

Graceful degradation

Reading comments from X does not stop dead at the limit. Past 90% of the pool, Echoia widens the interval between live checks to fifteen minutes; past 100% it stops calling X and keeps serving the comments already stored. The inbox stays usable, it just stops getting newer.

Handling limits in code

Treat 429 as two different situations, told apart by the message: a rate limit is worth retrying after a pause, a quota is not.

node
async function call(path, init, attempt = 0) {
  const res = await fetch(`https://app.echoia.io/api/v1${path}`, init);

  if (res.status === 429) {
    const { error } = await res.json();
    // A quota names the pool; a rate limit does not. Retrying a quota is futile.
    if (/quota/i.test(error)) throw new Error(error);
    if (attempt >= 3) throw new Error("Rate limited, gave up after 3 tries");
    await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
    return call(path, init, attempt + 1);
  }

  if (!res.ok) throw new Error((await res.json()).error);
  return res.json();
}

Seeing where you are

  • Settings → Billing shows every pool with what has been used this period.
  • Settings → Developers shows each token and when it was last used.
Was this page helpful?