Rate Limits, Pagination & Idempotency
TokPortal API rate limits (120 req/min), pagination with cursor-based offsets, and idempotency best practices.
Rate Limits & Pagination
Rate Limits
The TokPortal API allows 120 requests per minute per API key. The limit uses a token bucket algorithm that refills continuously, so you do not need to wait for a fixed window to reset.
Bundle capacity is a separate workspace-wide safeguard. Bundle creation and publication use rolling capacity windows shared by the workspace owner and every Team member. This capacity is not exposed as a live counter. A request is affected only when it would exceed the available capacity; it then returns HTTP 429 with capacity_cooldown and should be retried later.
Performance Max and API bulk creation are evaluated for the complete batch before credits are debited. If the batch would exceed capacity, the entire request is rejected rather than partially created. Do not split a rejected batch into parallel requests to work around the cooldown.
Authenticated responses include rate limit headers:
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 119
X-RateLimit-Reset: 1779724800
X-RateLimit-Reset is a Unix timestamp estimating when the token bucket will be full again.
Rate Limit Response
When you exceed the limit, the API returns a 429 Too Many Requests response:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1779724800
Retry-After: 1
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please slow down.",
"details": {
"retry_after_seconds": 2
}
}
}
Best Practices
- Read
Retry-Afterfirst on 429 responses. It is the canonical wait time before retrying. - Check
retry_after_secondsin the error response and wait before retrying. - Track
X-RateLimit-Remainingto smooth bursts before you hit the limit. - Use exponential backoff — if you receive consecutive 429s, increase the wait time between retries.
- Spread requests evenly rather than sending them in bursts. The token bucket refills at approximately 2 tokens per second, so a steady rate of 2 req/s is sustainable.
- Use separate API keys for independent services to isolate rate limits.
- Treat
capacity_cooldownseparately from API-key throttling. It is shared by the entire client workspace, so rotating keys or sending requests through a Team member does not create additional capacity.
Pagination
All list endpoints support pagination via query parameters.
Query Parameters
| Parameter | Default | Max | Description |
|---|---|---|---|
page | 1 | — | The page number to retrieve (1-indexed). |
per_page | 25 | 100 | Number of items per page. |
Example Request
curl -X GET "https://app.tokportal.com/api/ext/bundles?page=2&per_page=50" \
-H "X-API-Key: sk_xxx"
Response Format
Paginated responses include a pagination object alongside data:
{
"data": [
{ "id": "bnd_abc123", "status": "published" },
{ "id": "bnd_def456", "status": "configuring" }
],
"pagination": {
"page": 2,
"per_page": 50,
"total": 137,
"total_pages": 3
}
}
Iterating Through All Pages
async function fetchAllBundles(apiKey: string) {
const results = [];
let page = 1;
while (true) {
const res = await fetch(
`https://app.tokportal.com/api/ext/bundles?page=${page}&per_page=100`,
{ headers: { "X-API-Key": apiKey } }
);
const { data, pagination } = await res.json();
results.push(...data);
if (page >= pagination.total_pages) break;
page++;
}
return results;
}
Idempotency
For mutating requests (POST, PUT, PATCH, and DELETE) that document idempotency support, you can include an Idempotency-Key header to safely retry requests without creating duplicate resources or double-charging credits.
Do not send this header to an endpoint that can return a credential, verification code, webhook signing secret, signed upload URL, or private analytics report artifact. TokPortal rejects it before the operation runs with 400 IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE and details.retry_without_idempotency_key: true. This prevents secret-bearing responses from being stored in the replay ledger. The affected operations are credential reveal, verification-code retrieval, webhook endpoint creation, presigned video or image upload creation, and analytics report creation/export. Follow the recovery instructions on the endpoint page instead.
Usage
curl -X POST https://app.tokportal.com/api/ext/bundles \
-H "X-API-Key: sk_xxx" \
-H "Idempotency-Key: my-unique-request-id-12345" \
-H "Content-Type: application/json" \
-d '{ "platform": "tiktok", "country": "US", "videos": 10 }'
How It Works
- The first request with a given
Idempotency-Keyis processed normally. After its response is durably stored ascompleted, exact retries return that response for 24 hours without executing the operation again. - Subsequent requests with the same key, method, path, and request body return the completed original response with
Idempotent-Replayed: true. - Replayed responses include the
Idempotent-Replayed: trueheader. - Reusing the same key with a different request body returns
IDEMPOTENCY_KEY_REUSED. - Retrying while the first request is still running returns
IDEMPOTENCY_KEY_IN_PROGRESS. Retry the exact request after a short delay. - If
IDEMPOTENCY_KEY_IN_PROGRESSpersists beyond the normal request duration, stop automated retries and do not switch to a new key. The mutation may have committed before TokPortal stored its response. Theprocessingclaim deliberately does not expire automatically because executing again could duplicate a resource or debit. - Persistent
processingclaims require TokPortal support/operator reconciliation against request, resource, and financial logs. ProvideX-TokPortal-Request-ID, method, path, idempotency key, timestamp, and your non-secret correlation reference. Never send API keys, credentials, or verification codes. - Idempotency keys are scoped to your API key. Only
completedresponses expire after 24 hours from completion; unresolvedprocessingclaims remain fail-closed until reconciled. - Use a unique value per logical operation (e.g., a UUID or a meaningful identifier from your system).
When to Use Idempotency Keys
Idempotency keys are recommended for any operation that:
- Creates a resource (bundles, video slots, edit slots)
- Debits credits
- Publishes, deletes, or updates state
- Could be retried due to network timeouts or uncertain responses
This prevents accidental double-charges or duplicate resource creation.
Idempotency Errors
| Code | HTTP Status | Meaning |
|---|---|---|
IDEMPOTENCY_KEY_TOO_LONG | 400 | The key is longer than 255 characters. |
IDEMPOTENCY_KEY_NOT_ALLOWED_FOR_SENSITIVE_RESPONSE | 400 | The endpoint can return sensitive material and rejected the key before execution. Remove the header and retry once. |
IDEMPOTENCY_KEY_REUSED | 409 | The key already exists for a different request body. |
IDEMPOTENCY_KEY_IN_PROGRESS | 409 | The claim is still processing. Retry the exact request briefly; if it persists, stop and request reconciliation. Never retry with a different key. |
IDEMPOTENCY_UNAVAILABLE | 503 | The ledger could not be claimed, so no mutation ran. Retry the exact request with the same key. |