rate_limited

Too many requests. Back off and retry using the Retry-After header.

HTTP 429You have hit a rate limit.

When it fires

You have exceeded the request quota for the workspace's plan. Limits are tracked separately by:

  • Per-key requests per minute — protects against runaway loops.
  • Per-workspace requests per month — your plan cap.
  • Per-workspace AI requests per day — capped separately so a runaway agent loop does not eat your monthly quota.

The scope field on the error tells you which limit you hit, and resets_at tells you when it clears.

Response shape

Every Repull error follows the same envelope. The code is stable and safe to switch on.

{
  "error": {
    "code": "rate_limited",
    "message": "<human-readable explanation of what went wrong>",
    "docs_url": "https://repull.dev/docs/errors/rate_limited"
  }
}

How to fix

  1. Check the `Retry-After` response header — it tells you how many seconds to wait before retrying.
  2. Read `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` on every response so you can throttle proactively, before you hit the cap.
  3. Implement exponential backoff with jitter: wait `Retry-After * 2^attempt + random(0,1)s`, capped at 60 seconds.
  4. If you are batching, switch to a single request — the AI endpoints in particular have batch shapes that cost one quota slot for many records.
  5. If you are consistently bumping the cap, upgrade your plan from /dashboard/billing.

Common gotchas

  • Daily AI quota is separate. If scopecomes back as daily_ai, you are not out of monthly requests — you are out of AI calls for today specifically. Other endpoints will still work.
  • Retry-After is in seconds, not milliseconds. Doubling it accidentally is a common JS bug.
  • Retries against an idempotency-key never count against the rate limit twice — Repull dedupes before counting. Use Idempotency Keys on every retryable request.
  • The error code returned today is rate_limit_exceeded in some surfaces — it is being unified to rate_limited across all endpoints. Treat both as the same signal.

Examples

curl

# A 429 response includes Retry-After
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2026-05-02T12:34:56Z

{
  "error": {
    "code": "rate_limited",
    "message": "You have exceeded the per-minute request quota.",
    "scope": "minute",
    "limit": 60,
    "used": 60,
    "resets_at": "2026-05-02T12:34:56Z"
  }
}

# Recovery
sleep 12
curl https://api.repull.dev/v1/listings -H "Authorization: Bearer sk_test_YOUR_KEY"

TypeScript

import { Repull } from '@repull/sdk'

const repull = new Repull({ apiKey: process.env.REPULL_KEY! })

async function withBackoff<T>(fn: () => Promise<T>, maxAttempts = 4): Promise<T> {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await fn()
    } catch (err: any) {
      if (err.code !== 'rate_limited' || attempt === maxAttempts - 1) throw err
      const retryAfter = Number(err.headers?.['retry-after'] ?? 1)
      const delayMs = Math.min(60_000, retryAfter * 1000 * Math.pow(2, attempt) + Math.random() * 1000)
      await new Promise(r => setTimeout(r, delayMs))
    }
  }
  throw new Error('unreachable')
}

const listings = await withBackoff(() => repull.listings.list())

If you're an AI agent

You are sending requests faster than the workspace's plan allows. STOP and wait Retry-After seconds (from the response headers) before retrying. If you keep hitting it, batch your calls or pause the loop entirely until X-RateLimit-Reset.

Hit an error that isn't covered? Email hello@repull.dev with the request id from the response headers.

AI