> ## Documentation Index
> Fetch the complete documentation index at: https://docs.publishbuddy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> How PublishBuddy throttles API requests and how to handle 429 responses gracefully.

The API rate-limits per token to keep things responsive for all customers. There are two tiers, applied to different endpoint groups:

| Tier        | Limit                   | Applied to                                |
| ----------- | ----------------------- | ----------------------------------------- |
| Standard    | 60 requests / minute    | Workspaces, profiles, analytics, hashtags |
| High-volume | 1,000 requests / minute | Posts, media library, folders             |

The high-volume tier covers the endpoints you're most likely to hammer during bulk imports.

## Headers

Every response includes the current rate-limit state:

| Header                  | Meaning                                                      |
| ----------------------- | ------------------------------------------------------------ |
| `X-RateLimit-Limit`     | The cap for the current window.                              |
| `X-RateLimit-Remaining` | How many requests you have left in the current window.       |
| `Retry-After`           | (Only on `429` responses) — seconds to wait before retrying. |

```http theme={null}
HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 947
```

## When you hit the limit

The API returns `429 Too Many Requests`:

```json theme={null}
{
  "message": "Too Many Attempts."
}
```

With:

```http theme={null}
Retry-After: 23
```

Wait the suggested number of seconds and retry. Don't keep hammering — repeated `429`s during a single window can extend the cool-off.

## Patterns for staying inside the limit

<AccordionGroup>
  <Accordion title="Concurrency limiting" icon="layer-group">
    For bulk operations, cap concurrent in-flight requests instead of firing everything at once. A `p-limit(20)` in Node, or `ThreadPoolExecutor(max_workers=20)` in Python, keeps you well inside the 1,000/min tier without coordination overhead.
  </Accordion>

  <Accordion title="Exponential backoff with jitter" icon="rotate">
    On 429, wait `Retry-After`. On 5xx, retry with exponential backoff (e.g. 1s → 2s → 4s) plus 10–20% jitter to avoid thundering-herd retries from multiple workers.
  </Accordion>

  <Accordion title="Batch where you can" icon="boxes-stacked">
    The media upload endpoints accept up to 10 files per request — one `presign` call plus one `confirm` call covers a batch of 10 and counts as just 2 requests against the rate limit. The `PUT` uploads themselves go directly to storage and don't count against the API rate limit at all.
  </Accordion>

  <Accordion title="Cache list responses" icon="database">
    If your code repeatedly fetches the same workspace's profile list (e.g. in a polling loop), cache it for a few minutes instead of refetching every cycle. Profile membership rarely changes.
  </Accordion>
</AccordionGroup>

## Reference retry helper

```javascript theme={null}
async function fetchWithRetry(url, options, maxRetries = 5) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const res = await fetch(url, options);
    if (res.status !== 429 && res.status < 500) return res;

    const retryAfter =
      Number(res.headers.get('Retry-After')) ||
      Math.min(60, 2 ** attempt) + Math.random();
    await new Promise((r) => setTimeout(r, retryAfter * 1000));
  }
  throw new Error('Exceeded retry budget');
}
```

## Asking for more

If your production workload genuinely needs higher limits (e.g. you're a large agency syncing thousands of profiles), email [support@publishbuddy.com](mailto:support@publishbuddy.com) with your use case and average request profile. We can raise limits on a per-account basis.
