Rate limits

Limits exist to keep the service responsive for everyone and to make abusive usage obvious in analytics. They are generous enough that a single engineer's workflow never hits them — and structured so real team workloads scale cleanly by spreading across multiple keys.

Per-key limit

  • 60 requests per minute per key. Applied on a 60-second rolling window.
  • All endpoints count — including GET /credits.
  • Limit exhausted → 429 rate_limited with Retry-After: 60 header.

Per-workspace quotas

  • 10 active API keys per workspace. Revoked keys don't count, so rotation is free.
  • Monthly credits are set by the workspace's plan — see pricing. Credits are shared across web + API.

How to scale beyond 60 rpm

If you're hitting the per-key limit, create additional keys and spread load across them:

// Create 3 keys in Settings → API Keys
const KEYS = [
  process.env.PLANMYSAAS_KEY_1,
  process.env.PLANMYSAAS_KEY_2,
  process.env.PLANMYSAAS_KEY_3,
]

function pickKey() {
  return KEYS[Math.floor(Math.random() * KEYS.length)]
}

Each key has its own 60 rpm bucket, so N keys = N × 60 rpm headroom.

Retry strategy

When you get a 429, honour the Retry-After header. Exponential backoff works well for transient 5xx responses:

async function callWithRetry(input, { maxAttempts = 4 } = {}) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const res = await fetch(URL, { method: "POST", headers: HEADERS, body: JSON.stringify(input) })
    if (res.ok) return res.json()
    if (res.status === 429) {
      const wait = Number(res.headers.get("Retry-After") ?? 60) * 1000
      await sleep(wait)
      continue
    }
    if (res.status >= 500 && attempt < maxAttempts) {
      await sleep(1000 * 2 ** (attempt - 1))  // 1s, 2s, 4s
      continue
    }
    throw new Error(`API failed: ${res.status}`)
  }
}

Retries that are always safe

Every generation endpoint is idempotent in the sense that retrying never causes a double-charge on a single failed request — failed generations do not deduct credits. A retried request is a fresh generation (you'll get a new, slightly different output).

Concurrency guidance

  • Small scripts (1-3 devs): single key is fine. You won't feel the limit.
  • CI / batch jobs: dedicated key per pipeline keeps the main dev key fast.
  • Agencies / resellers: one key per end-client for clean billing attribution.

Headers you'll see

HeaderMeaning
X-Request-Id Unique id for this request. Include in support tickets.
X-Credits-Charged Credits deducted on this call (0 on read / error).
X-Credits-RemainingWorkspace balance after the call.
Retry-After Seconds to wait before retrying (only on 429).

Abuse

Keys that generate abnormal traffic patterns (e.g. 100% failed requests, obvious credential stuffing, scraping patterns) may be staff-revoked. If your key is revoked for this reason, the Revoked reason field on the key will explain the trigger. Reach out at support@planmysaas.com if you think it was a false positive.