# Rate limits

Requests per minute by connected account count, the per-second window on analytics, posting velocity caps, and how to handle a 429.

You get 60, 600 or 1,200 requests per minute, set by how many accounts your team has connected. The window slides, so capacity frees continuously.

## Read your remaining requests

Every response carries the limit headers (`-i` prints them). Read them rather than hard-coding the tables below; your limit may be higher.

```bash
curl -i "https://zernio.com/api/v1/accounts" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"
```

```text
HTTP/2 200
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1798761660
Content-Type: application/json

{ "accounts": [ ... ] }
```

| Header | Description |
|--------|-------------|
| `X-RateLimit-Limit` | The limit for this endpoint's window (per minute, or per second on analytics endpoints) |
| `X-RateLimit-Remaining` | Requests remaining in the current window |
| `X-RateLimit-Reset` | Unix timestamp (seconds) when the next slot frees up |
| `Retry-After` | `429` responses only: seconds to wait before retrying |

## Know your request limit

One key covers any volume, even a [multi-tenant integration](/multi-tenant). [Scoped keys](/multi-tenant#scoped-api-keys) are for access control, not throughput.

| Connected accounts | Requests per minute | Requests per second (analytics) |
|---|---|---|
| 0 to 2 (free) | 60 | 6 |
| 3 to 2,000 | 600 | 10 |
| 2,001 and more | 1,200 | 20 |

- **Counted team-wide:** owner plus invited members.
- **Legacy AppSumo lifetime accounts:** a flat 600 requests per minute, and 10 per second on analytics.

### Per-second limits for analytics endpoints

Analytics endpoints use a 1-second window. The floor of 6 keeps a dashboard fanning out several requests per render from being throttled:

```
requests_per_second = max(6, requests_per_minute / 60)
```

The per-second window covers `GET /api/v1/analytics`, `GET /api/v1/analytics/delta`, `GET /api/v1/analytics/best-time`, `GET /api/v1/analytics/content-decay`, `GET /api/v1/analytics/daily-metrics`, `GET /api/v1/analytics/format-breakdown`, `GET /api/v1/analytics/post-timeline` and `GET /api/v1/analytics/posting-frequency`. Every other endpoint uses 1 minute.

## Posting velocity limits

Each account has publishing caps so platforms do not throttle or flag it. A post over one returns `429` with the cooldown in the message.

| Limit | Value |
|-------|-------|
| Velocity cap | 25 posts per hour per account |
| Daily caps | Instagram 100, Facebook 100, Threads 250, X 50, Pinterest 25, TikTok 15 videos and 15 photos on Business app connections, 50 for every other platform |

- **TikTok:** the 2 counts are separate, over a rolling 24 hours. A post past the cap is held until a slot frees, not rejected ([daily posting caps](/platforms/tiktok/posts#daily-posting-caps)).
- **AppSumo:** legacy accounts have lower daily caps (5, 8 or 20 posts per account per day), and the lower cap wins. An Instagram daily-limit error quoting one is the AppSumo cap.

## How it behaves

### Zernio retries platform limits 3 times

- Platforms add their own limits, some shared by every customer because Zernio is one application.
- A platform `429` or `5xx` is retried up to 3 times. A `429` whose reset is under 5 minutes away waits for it plus 1 second.
- A later reset or an exhausted quota fails immediately with the platform's message.

### Reddit's budget is shared

- Reddit limits per OAuth application, and Zernio is one: low volume on your side does not guarantee headroom.
- At `x-ratelimit-remaining` 0 the request fails without retry: `Quota resets in Ns`, from Reddit's `x-ratelimit-reset` (seconds, not a timestamp).
- Reddit's `RATELIMIT` on posting and messaging returns `429` with `Reddit rate limit reached... Retry in Ns.` when Reddit states a wait. Treat those seconds as `Retry-After`.

### Google Ads draws on one developer token

- One token, with Standard access (no daily operations cap), serves every connected Google Ads account. A `429` means Google throttled the token or your ad account.
- The `429` has `code: "rate_limited"`, `platform: "google"`, `details.quotaExhausted: true` (Google's quota, so keep calling other platforms), `details.resetsAt` and a `Retry-After` counting down to it.
- `details.quotaScope`, when Google names it: `DEVELOPER` is the shared token (nothing on your side changes it), `ACCOUNT` is your ad account.
- Not retried by Zernio; retrying before `Retry-After` cannot succeed. Cached reads served meanwhile carry `stale: true`.
- **Connecting** while throttled redirects with `error=google_ads_quota_exhausted`. The token exchange succeeded; retry the connect later.

### Upstream `429`s keep the `rate_limited` code

- When a platform or upstream service answers `429` to a direct request (not a scheduled publish), Zernio returns `429` with `code: "rate_limited"`, plus `Retry-After` whenever the upstream said how long.
- Covers platform quotas (Reddit's on inbox replies) and the billing backend behind `/v1/usage` and `/v1/billing/*`. Wait `Retry-After` seconds and retry.

### TikTok caps direct posting per application

- TikTok caps distinct accounts direct-posting through one application per rolling 24 hours, shared across Zernio's developer app. Business app accounts (every new connection) are exempt.
- Error text, priority rules and the draft workaround: [TikTok page](/platforms/tiktok/posts#direct-posting-at-capacity).

### Analytics responses are cached

- Post analytics are cached for 60 minutes; a stale-cache request triggers a background refresh.
- Follower stats refresh once a day. YouTube daily views arrive 2 to 3 days late from YouTube's Analytics API ([analytics endpoints](/analytics/get-analytics)).

## If it fails

A full window returns `429`:

```json
{
  "error": "Rate limit exceeded. Please retry after 12 seconds.",
  "details": { "currentCount": 601, "limit": 600, "retryAfterSeconds": 12 }
}
```

| Error | Cause | Fix |
|---|---|---|
| `429` with `error` and `details` only | Your window is full. No `code`: branch on the status. | Wait `Retry-After` seconds (relative, so clock skew is harmless; `details.retryAfterSeconds` matches), then retry. |
| `429` with `type: "rate_limit_error"` and `code: "rate_limited"` | An endpoint-specific quota, such as Google Ads. | See [How it behaves](#how-it-behaves). |
| Hitting the limit often | Too much polling. | Use [webhooks](/webhooks) for status, [bulk upload](/posts/bulk-upload-posts) for many posts, and `limit` and `skip` to page lists. |

## Related

- [Error handling](/guides/error-handling): the `429` envelope and `rate_limit_error`.
- [Idempotency & safe retries](/guides/idempotency): retry a create without double-posting.
- [Multi-tenant](/multi-tenant): scoped keys and per-customer queues.
- [Analytics](/analytics/get-analytics): the endpoints on the per-second window.