Rate Limits & Polling Best Practices
Upload-Post applies rate limits at multiple levels to protect the service and the social media platforms. This guide explains each limit and how to design your integration to work within them.
API Rate Limits
Every authenticated API response includes rate limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum requests allowed in the current window |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp when the window resets |
When you exceed the limit, the API returns HTTP 429 Too Many Requests. Wait until the X-RateLimit-Reset timestamp before retrying.
Limits per plan
The per-minute window depends on your plan, and grows with the number of profiles on the account (+2 requests/min and +10 requests/10 min per profile), so multi-brand accounts are not capped like a single-profile one:
| Plan | Requests / minute | Requests / 10 minutes |
|---|---|---|
| Free | 60 | 300 |
| Basic | 60 | 300 |
| Professional | 100 | 500 |
| Advanced | 200 | 1,000 |
| Business | 500 | 2,500 |
Example: an Advanced account with 100 profiles gets 200 + 100 × 2 = 400 requests/min. Hard ceiling: 1,000 requests/min and 5,000 requests/10 min. Requests made from the dashboard (web session) use a separate, higher allowance.
The exact limit that applies to your key is always in the X-RateLimit-Limit header of every response. The first request after a long idle period may report the Free/Basic tier while the API resolves your plan; the following requests apply your tier.
Upload Status Polling
When using async_upload=true, you need to poll the Upload Status endpoint to get the result. Here are the recommended intervals:
Polling Strategy
1. Submit upload → get request_id
2. Wait 5 seconds (initial delay)
3. Poll GET /api/uploadposts/status?request_id=<ID>
4. If status is "pending", "queued", or "processing" → wait 10 seconds → repeat step 3
5. If status is "completed" or "failed" → done
Recommended Polling Intervals
| Scenario | Poll Interval | Max Wait |
|---|---|---|
| Photo uploads | Every 5–10 seconds | 2 minutes |
| Video uploads (small, < 50 MB) | Every 10 seconds | 5 minutes |
| Video uploads (large, > 50 MB) | Every 15 seconds | 10 minutes |
| Scheduled posts (after scheduled time) | Every 30 seconds | 10 minutes |
| Queued posts | Every 60 seconds | Until next queue slot |
Status Cache TTLs
The status endpoint uses internal caching. Here is how often the status value is refreshed:
| Status | Cache TTL | Meaning |
|---|---|---|
queued | 2 seconds | The upload is waiting for a worker |
pending | 2 seconds | Accepted but not yet started |
processing | 3 seconds | At least one platform is actively uploading |
completed | 5 minutes | All platforms finished successfully |
failed | 5 minutes | All platforms failed |
Key takeaway: Polling faster than every 5 seconds for non-terminal states is unnecessary — the cache refreshes every 2–3 seconds. For terminal states (completed/failed), the result is cached for 5 minutes, so subsequent polls are fast.
Read Caches (comments and post analytics)
Other read endpoints that would otherwise call the social platform on every request are cached per account and query string. Only successful responses are cached:
| Endpoint | Cache TTL | Notes |
|---|---|---|
GET /api/uploadposts/comments | 60 seconds | Cleared when the same account creates, deletes or moderates a comment through Upload-Post, so your own writes show up at once. On X, the mentions page behind it is also kept 120 seconds per X account. |
GET /api/uploadposts/post-analytics?platform_post_id= | 5 minutes | X posts older than 7 days are refreshed from X at most once a week. |
Polling these faster than their TTL returns the same response and only spends your rate limit.
Alternative: Use Webhooks
Instead of polling, configure a webhook to receive a POST notification when the upload completes. This is more efficient for high-volume integrations:
{
"channels": { "webhook": true },
"webhook_url": "https://your-server.com/webhook"
}
The webhook fires once per platform per upload, so you get immediate notification without polling.
Upload Rate Limits
Per-Request Duplicate Protection
The API prevents duplicate uploads using idempotency keys and rate limiting:
- Same content to same platform: If you submit an identical upload (same user, platform, and content hash) within a short window, the API returns the existing request instead of creating a duplicate.
- Idempotency-Key header: Send a unique
Idempotency-KeyorX-Idempotency-Keyheader to ensure exactly-once processing, even across retries.- The key is scoped to your account and the upload type (video, photo or text) and lasts 24 hours from the first request.
- Resending the same key returns the original job (
idempotent_replay), even if the body changed. - If two requests with the same key arrive at the same time, only one creates the upload. The other waits briefly for it and, if the first request is still being processed, gets
409with codeIDEMPOTENCY_KEY_IN_USE. Retry it after a few seconds and it returns the original job.
Daily Upload Caps (per platform per account)
Each social account has a daily hard cap based on platform limits. Exceeding these returns HTTP 429:
| Platform | Max posts / 24h |
|---|---|
| 50 | |
| TikTok | 15 |
| 150 | |
| YouTube | 30 |
| 25 | |
| X (Twitter) | Per plan: Free/Basic 10 · Professional 20 · Advanced/Business 30 |
| Threads | 50 |
| 20 | |
unavailable (reddit_unavailable) | |
| Bluesky | 25 videos / 10 GB (video quota) |
These are rolling 24-hour windows per social account, not per API key.
Pending Scheduled Posts
Each account can keep a limited number of scheduled posts waiting to be published: Free 5,000 · Basic 20,000 · Professional 30,000 · Advanced 40,000 · Business 50,000. When the limit is reached, uploads with scheduled_date (or add_to_queue) and job_id retries with a future date return HTTP 429 with error_code: "SCHEDULED_POSTS_LIMIT", pending_scheduled_posts and max_pending_scheduled_posts. Only posts due more than 15 minutes ahead count; immediate uploads are never refused by it. Retrying does not help until some posts publish or you cancel the ones you no longer need. See Pending Scheduled Posts Limit.
X Replies, Edits and Reposts
Writes to X outside of uploads — replies with comments/create, posts/edit and posts/repost — share one counter per X account and UTC day:
| Plan | Replies + edits + reposts per X account / day |
|---|---|
| Free | 20 |
| Paid plans | 200 |
Over the cap they return HTTP 429 with error_code: "x_daily_write_limit", limit, retry_after and a Retry-After header (seconds until 00:00 UTC). A write that X refuses is not counted. This counter is separate from the upload caps above: replying a lot never blocks your scheduled posts.
Reading X post analytics by tweet ID also has a daily budget (5,000 distinct X posts per account per UTC day; 429 daily_limit). See Post analytics by platform ID.
Monthly API Usage
Your plan determines how many API upload calls you can make per month:
| Plan | Monthly uploads |
|---|---|
| Free | 10 |
| Paid plans | See pricing |
Check your current usage with GET /api/uploadposts/me — the response includes api_usage.count.
API Key Brute-Force Protection
Failed authentication attempts (invalid API keys) are rate-limited per IP:
- After 10 consecutive failed attempts with the same invalid key, the IP is blocked for 5 minutes.
- The API returns HTTP 429 during the block period.
Best Practices
- Use webhooks instead of polling when possible — they're instant and don't consume rate limit budget.
- Respect
X-RateLimit-Remaining— stop sending requests when it reaches 0. - Use exponential backoff on 429 responses — don't hammer the API after hitting a limit.
- Set
async_upload=truefor all uploads — synchronous uploads timeout after 59 seconds anyway. - Send
Idempotency-Keyfor all upload requests — this protects you from duplicate posts if your HTTP client retries on timeout. - Poll at the recommended intervals — faster polling just returns cached results and wastes your rate limit budget.