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 TypeLimitWindow
REST API100 requests1 minute
MCP tools60 requests1 minute
Skill execution30 requests1 minute
Webhook signature verification (/api/v1/webhooks/verify)30 requests5 minutes

Rate Limit Headers

Every response includes rate limit information:

x-ratelimit-limit: 100
x-ratelimit-remaining: 45
x-ratelimit-reset: 60
HeaderDescription
x-ratelimit-limitMaximum requests per window
x-ratelimit-remainingRemaining requests in current window
x-ratelimit-resetSeconds 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

LimitValueDescription
Concurrent executions1 per userOne live execution per user at a time, across every workspace
Execution timeout1 hour by defaultThe 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:

  1. Review your request patterns
  2. Implement caching and batching
  3. Check for request loops or bugs
  4. Contact support for usage optimization advice