Rate Limits & Quotas
ContentHub enforces two types of limits: generation quotas (based on your plan) and request rate limits (per API key). Understanding these prevents unexpected failures in automated integrations.
Generation quotas
Each workspace has a monthly limit on the number of AI-generated content pieces. This applies equally whether generation is triggered via the dashboard or the REST API (POST /api/v1/content/:id/generate).
| Plan | Content pieces / month | Projects |
|---|---|---|
| Free Trial | 5 | 1 |
| VIP Trial | 30 | 3 |
| Plus | 150 | Unlimited |
| Pro | 500 | Unlimited |
| Enterprise | Custom | Custom |
Quotas reset at the start of each calendar month. The current usage is visible in your workspace dashboard sidebar. When the limit is reached, generation attempts return:
HTTP/1.1 429 Too Many Requests
{
"error": {
"code": "QUOTA_EXCEEDED",
"message": "Monthly content limit reached (30 pieces). Please upgrade your plan."
}
}API request rate limits
ContentHub enforces per-minute token-bucket rate limits on every REST endpoint, checked both per API key and per workspace (so multiple keys on one workspace can't collectively exceed the workspace-wide cap). Limits are tiered by endpoint cost:
| Tier | Examples | Per key / min | Per workspace / min |
|---|---|---|---|
| read | All GET endpoints | 300 | 900 |
| write | Content/settings/project mutations, archive/unarchive | 60 | 180 |
| expensive | generate, publish, create-post, regenerate | 10 | 20 |
| egress | Connection test, integration sync | 20 | 40 |
Exceeding a limit returns 429 with error.code: "RATE_LIMITED" and a Retry-After header (seconds until the current 1-minute window resets). This is distinct from QUOTA_EXCEEDED above — rate limiting means "you're calling too fast," quota means "you're out of monthly generation budget."
HTTP/1.1 429 Too Many Requests
Retry-After: 42
{
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after 42 second(s)."
}
}Handling 429 responses
When you receive a 429 response, follow this strategy:
- Read the
error.codefield to distinguishQUOTA_EXCEEDED(monthly limit) fromRATE_LIMITED(calling too fast) - For
QUOTA_EXCEEDED: pause generation calls until the next month reset — non-generate endpoints are unaffected - For
RATE_LIMITED: wait the duration inRetry-Afterseconds before retrying - Use exponential backoff with jitter for retries to avoid thundering-herd issues
async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const res = await fetch(url, options)
if (res.status !== 429) return res
const retryAfter = res.headers.get("Retry-After")
const delay = retryAfter
? parseInt(retryAfter, 10) * 1000
: Math.pow(2, attempt) * 1000 + Math.random() * 500
if (attempt < maxRetries) await new Promise(r => setTimeout(r, delay))
}
throw new Error("Max retries exceeded")
}Pagination and large datasets
All list endpoints return paginated results. The default page size is 20; the maximum is 100. For bulk operations over large catalogs, iterate through pages sequentially rather than issuing many parallel requests:
async function fetchAllProducts(apiKey: string) {
const products = []
let page = 1
let pages = 1
while (page <= pages) {
const res = await fetch(`/api/v1/products?page=${page}&limit=100`, {
headers: { Authorization: `Bearer ${apiKey}` },
})
const { data, meta } = await res.json()
products.push(...data)
pages = meta.pages
page++
}
return products
}Idempotency for write operations
POST and PATCH operations on the ContentHub REST API are not currently idempotent. If you retry a failed POST /api/v1/products request, you may create duplicate products. To avoid this:
- Check for existing records with
GET /api/v1/productsbefore creating - Prefer
PATCHoverPOSTwhen updating existing records - Only retry on network errors (timeouts, connection resets) — not on 4xx responses
Next steps
- API Reference — explore all endpoints with live examples
- Authentication — scope reference and key rotation