Rate Limits
Understanding and working within clanker’s rate limits.
Overview
Rate limits protect the platform and ensure fair usage for all users.
Limits that are keyed on the client’s address use the address as the
platform’s edge sees it. Forwarding headers a client sends itself (such as
X-Forwarded-For) are ignored, so they cannot be used to dodge a limit or to
spend someone else’s. IPv6 clients are grouped by their /56 prefix, because one
subscriber typically holds a whole prefix.
API Rate Limits
| Endpoint Type | Limit | Window |
|---|---|---|
| REST API | 100 requests | 1 minute |
| MCP tools | 60 requests | 1 minute |
| Skill execution | 30 requests | 1 minute |
Webhook signature verification (/api/v1/webhooks/verify) | 30 requests | 5 minutes |
Rate Limit Headers
Every response includes rate limit information:
x-ratelimit-limit: 100
x-ratelimit-remaining: 45
x-ratelimit-reset: 60
| Header | Description |
|---|---|
x-ratelimit-limit | Maximum requests per window |
x-ratelimit-remaining | Remaining requests in current window |
x-ratelimit-reset | Seconds until window resets |
MCP Rate Limits
MCP tools are rate-limited to 60 requests per minute per client address, counted separately from the REST API limit. Rate limit information is conveyed via HTTP response headers on the MCP transport endpoint (/mcp/), using the same x-ratelimit-* headers as the REST API.
Rate Limit Error
When you exceed the rate limit:
HTTP Response
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
{
"error": {
"type": "rate_limit_error",
"message": "Rate limit exceeded. Retry after the x-ratelimit-reset window."
}
}
Execution Limits
| Limit | Value | Description |
|---|---|---|
| Concurrent executions | 1 per user | One live execution per user at a time, across every workspace |
| Execution timeout | 1 hour by default | The sandbox lifetime cap; a deployment can raise it |
Concurrent Execution Error
Returned with HTTP 429 when you already have a running execution:
{
"error": {
"code": "EXECUTION_RUNNING",
"message": "An execution is already running. Wait for it to complete.",
"details": { "executionId": "exec_abc123", "serviceCode": "EXECUTION_RUNNING" }
}
}
Best Practices
1. Implement Exponential Backoff
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const resetIn = parseInt(response.headers.get('x-ratelimit-reset') || '60');
const delay = resetIn * 1000 * Math.pow(2, attempt);
console.log(`Rate limited. Waiting ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}
2. Check Remaining Before Requests
let rateLimitRemaining = 60;
let rateLimitReset = Date.now();
async function makeRequest(url, options) {
// Wait if no remaining requests
if (rateLimitRemaining <= 0 && Date.now() < rateLimitReset) {
const waitTime = rateLimitReset - Date.now();
console.log(`Waiting ${waitTime}ms for rate limit reset...`);
await new Promise(r => setTimeout(r, waitTime));
}
const response = await fetch(url, options);
// Update rate limit info
rateLimitRemaining = parseInt(response.headers.get('x-ratelimit-remaining') || '100');
rateLimitReset = Date.now() + parseInt(response.headers.get('x-ratelimit-reset') || '60') * 1000;
return response;
}
3. Cache Responses
const cache = new Map();
const CACHE_TTL = 60000; // 1 minute
async function cachedFetch(url, options) {
const cacheKey = `${options?.method || 'GET'}:${url}`;
const cached = cache.get(cacheKey);
if (cached && Date.now() < cached.expiresAt) {
return cached.data;
}
const response = await fetch(url, options);
const data = await response.json();
cache.set(cacheKey, {
data,
expiresAt: Date.now() + CACHE_TTL
});
return data;
}
5. Check Execution Status Before Starting
async function safeExecuteSkill(slug, input) {
// Check if execution is already running
const status = await getExecutionStatus();
if (status?.status === 'running') {
throw new Error('Please wait for current execution to complete');
}
return executeSkill(slug, input);
}
Rate Limits by Plan
The request limits above are the same on every plan, including MCP: the limiter keys on the client address (REST on the API key or user), not on the plan. What a plan changes is how many workspaces you can own and how many API keys you can hold (see Authentication), not how fast you may call.
Monitoring Usage
Check Rate Limit Status
curl -I https://next.clanker.engineer/api/v1/marketplace/skills \
-H "x-api-key: YOUR_API_KEY"
Response headers show current usage:
x-ratelimit-limit: 100
x-ratelimit-remaining: 98
x-ratelimit-reset: 60
In Your Application
let apiCalls = 0;
const windowStart = Date.now();
// Log usage periodically
setInterval(() => {
const elapsed = (Date.now() - windowStart) / 1000;
console.log(`API calls: ${apiCalls} in ${elapsed}s`);
}, 30000);
Getting Help
If you consistently hit rate limits:
- Review your request patterns
- Implement caching and batching
- Check for request loops or bugs
- Contact support for usage optimization advice