Skip to page content
Docs
Make Agent Fast documentation

Rate limits

Design for the exact per-key plan limits, response headers, and safe retry behavior.

At a glance

Design for the exact per-key plan limits, response headers, and safe retry behavior.

API request pathScoped key → versioned resource → signed response
App / SDKBearer keymaf_live_…/api/v1/…ResourceJSON
Per keyOne minuteRetry-After

The public API applies a rolling one-minute request bucket to each API key or OAuth access token. Creating more keys to evade a limit is unsupported and can lead to credential or account restrictions.

Plan limits#

Active planRequests per key per minute
Starter120
Pro600
Studio1,800

A credential whose account has no active paid plan receives 403 subscription_required before rate-limit capacity is granted. Make Agent Fast can introduce lower endpoint-specific limits for unusually expensive operations; when that happens, the endpoint reference will identify them explicitly.

A rate-limited response#

When the bucket is exhausted, the API returns 429 rate_limit_exceeded with:

HTTP/1.1 429 Too Many Requests
Retry-After: 17
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-Request-Id: 6c0b2f2e-...
Content-Type: application/json

Retry-After is the minimum number of seconds to wait. Every authenticated response also includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (a Unix timestamp), so applications can throttle before a 429.

Retry implementation#

Use exponential backoff with random jitter, honor a larger Retry-After, and cap the total number of attempts.

async function requestWithRetry(url: string, init: RequestInit, attempts = 4) {
  for (let attempt = 0; attempt < attempts; attempt += 1) {
    const response = await fetch(url, init);
    if (response.status !== 429 && response.status < 500) return response;

    if (attempt === attempts - 1) return response;
    const retryAfter = Number(response.headers.get("retry-after") ?? 0) * 1_000;
    const exponential = 500 * 2 ** attempt;
    const jitter = Math.random() * 250;
    await new Promise((resolve) =>
      setTimeout(resolve, Math.max(retryAfter, exponential + jitter)),
    );
  }
  throw new Error("unreachable");
}

For POST, PATCH, and DELETE, keep the same valid Idempotency-Key across every network or server retry. A new idempotency key can repeat a completed side effect.

Reduce request volume#

  • Cache account, site, and configuration reads that do not need real-time freshness.
  • Request up to 100 records per page instead of repeatedly requesting very small pages.
  • Process webhook events instead of polling conversations, leads, domain state, or broadcasts.
  • Bound worker concurrency per API key; a burst from many serverless invocations shares the same bucket.
  • Use separate keys for separate services for isolation and auditability, not to multiply one workload's capacity.

Retry and do-not-retry table#

ResponseRetry?Condition
429YesWait for Retry-After, add jitter, and cap attempts
500SometimesSafe reads or mutations protected by the same idempotency key
409 processing conflictYesWait for its Retry-After: 2 header and keep the same idempotency key
Other 409Not immediatelyRead the current resource state and resolve the conflict
400, 401, 403, 404, 415NoCorrect input, credentials, entitlement, path, or content type first

If a sustained workload legitimately exceeds its plan bucket, reduce polling, batch work, or contact support with representative request IDs and expected traffic rather than adding an unbounded retry loop.