Rate Limits
Understanding and working with Chipp MCP Server rate limits
The Chipp MCP Server enforces rate limits to ensure fair usage and platform stability. Every tool call spends points from a per-minute budget. A cheap read costs 1 point. A heavy tool costs more. Limits vary by subscription tier, and no plan is unlimited.
These limits apply to every Chipp MCP endpoint: build.chipp.ai/mcp, mcp.chipp.ai/mcp, the /mcp/<area> sub-MCPs, and the software factory tools.
Rate Limits by Tier
| Plan | X-RateLimit-Tier | Points per minute, per user | Per token or key | Per organization | MCP access |
|---|---|---|---|---|---|
| Free | FREE | None | None | None | Not available |
| Builder | BUILDER | 30 | 30 | 150 | Full access |
| Builder | STUDIO | 60 | 60 | 300 | Full access |
| Studio Pro | STUDIO_PRO | 120 | 120 | 600 | Full access |
| Do It With You | ENTERPRISE | 1200 | 1200 | 6000 | Full access |
Each call spends its weight from three budgets at once: your user budget (shared by all your OAuth tokens and API keys), the budget of the token or key you used, and your organization’s budget. A call goes through only if all three have room.
MCP server access requires a Builder plan or higher. Free tier users cannot use the MCP server: requests return 403 with error code tier_blocked.
Enterprise plans have a high default, not unlimited access. If your contract names a different number, we store it for your organization and it replaces the default.
What Each Call Costs
| Tool | Points per call |
|---|---|
dispatch_ticket_batch | 10 |
export_conversations | 10 |
generate_image | 10 |
newsletter_import_contacts | 10 |
run_eval | 10 |
web_research | 10 |
ask_chief_of_staff | 5 |
bulk_update_app_settings | 5 |
chat_with_agent | 5 |
chat_with_app | 5 |
consult_agent | 5 |
dispatch_ticket | 5 |
generate_system_prompt | 5 |
generate_utterances | 5 |
get_app_analytics | 5 |
get_file_analytics | 5 |
get_model_usage | 5 |
get_moderation_stats | 5 |
get_monetization_stats | 5 |
get_response_time_analytics | 5 |
get_tag_analytics | 5 |
get_usage_by_app | 5 |
import_collection_tools | 5 |
import_tag_examples | 5 |
propose_platform_roll | 5 |
propose_ticket_batch | 5 |
run_nightly_ci | 5 |
run_ticket_ci | 5 |
spawn_subagent | 5 |
trigger_heartbeat | 5 |
add_project_files | 3 |
generate_suggestions | 3 |
get_nightly_ci_report | 3 |
get_release_digest | 3 |
get_usage | 3 |
publish_app | 3 |
query_database | 3 |
search_conversations | 3 |
search_messages | 3 |
web_search | 3 |
fetch_url | 2 |
get_ci_run_log | 2 |
search_kb_evidence | 2 |
| Every other tool | 1 |
wait_for_tickets, watch_deploy, watch_ci_run, watch_release | 0 points: they use a long-poll slot |
initialize, tools/list, ping, notifications | 0 points: they use a small separate budget of 60 per minute per token |
A batch request (a JSON-RPC array) costs the sum of its calls. A request that costs more than your whole per-minute budget is refused, so split it.
Other Limits
| Limit | Value |
|---|---|
| Requests in flight at once, per user | 10 |
| Requests in flight at once, per token or key | 8 |
| Long-poll calls open at once, per user | 3 |
| Long-poll calls open at once, per token or key | 2 |
| Long-poll calls open at once, per organization | 10 |
Handshake calls (initialize, tools/list), per token or key per minute | 60 |
| Requests per minute from one network address (all traffic) | 1200 |
| Failed sign-ins per minute from one network address | 30 |
| Repeated limit errors that block a token or address | 5 within 120 seconds |
| Block length, first to last repeat | 1 min, 5 min, 30 min, 2 h |
Long-poll tools (wait_for_tickets, watch_deploy, watch_ci_run, watch_release, and the live ticket stream) hold a request open. They do not spend points, so a client that watches tickets cannot use up its own budget. They take one of a small number of long-poll slots instead, and the slot is freed when the call finishes, fails, or your client disconnects.
Handshake calls (initialize, tools/list, ping) have their own small budget per token, so reconnecting often never uses the budget for your tool calls.
Rate Limit Headers
Every response carries rate limit information, on every plan:
| Header | Description |
|---|---|
X-RateLimit-Tier | Your subscription tier |
X-RateLimit-Limit | Points per minute in your user budget |
X-RateLimit-Remaining | Points left in the current window |
X-RateLimit-Used | Points used in the current window |
X-RateLimit-Cost | Points this request spent |
X-RateLimit-Reset | Unix timestamp when the window resets |
X-RateLimit-Concurrency-Limit | Requests you can have in flight at once |
Example response headers:
HTTP/1.1 200 OK
X-RateLimit-Tier: BUILDER
X-RateLimit-Limit: 30
X-RateLimit-Remaining: 20
X-RateLimit-Used: 10
X-RateLimit-Cost: 10
X-RateLimit-Reset: 1704067260
X-RateLimit-Concurrency-Limit: 10Handling Rate Limits (429 Errors)
When you exceed a limit, the server returns a 429 Too Many Requests response. The limit field names the limit you hit, so your client knows what to do:
{
"error": {
"code": "rate_limited",
"limit": "user",
"message": "Rate limit exceeded (limit: user): your per-minute budget (shared by all your tokens and keys). Retry after 45 seconds. Do not retry sooner.",
"retry_after_seconds": 45
}
}The same text is in the tool error, so an AI assistant that reads it will wait instead of retrying at once. The response also has X-RateLimit-Limit-Hit with the same value as limit.
limit | The limit you hit |
|---|---|
user | your per-minute budget (shared by all your tokens and keys) |
credential | the per-minute budget of this one token or key |
tenant | your organization’s per-minute budget |
ip | the per-minute request limit for your network address |
concurrency | the cap on requests in flight at once |
long_poll | the cap on long-running watch calls open at once |
global | the server-wide safety limit for this endpoint (not your fault, retry soon) |
Repeated 429s
A client that keeps sending requests after a 429 is blocked for a short time. The block grows each time it repeats (see “Other Limits”). While blocked, every request returns 429 with "blocked": true and a Retry-After for the end of the block. Always wait for Retry-After.
Retry-After Header
The Retry-After header tells you exactly when to retry:
HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit-Hit: user
X-RateLimit-Reset: 1704067260Implementing Retry Logic
Basic exponential backoff:
async function callWithRetry(makeRequest, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await makeRequest();
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await sleep(retryAfter * 1000);
continue;
}
return response;
}
throw new Error('Max retries exceeded');
}Respecting Retry-After:
import time
import requests
def call_mcp(tool_name, args):
response = requests.post(
"https://build.chipp.ai/mcp",
json={"method": "tools/call", "params": {"name": tool_name, "arguments": args}},
headers={"Authorization": f"Bearer {token}"}
)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 60))
print(f"Rate limited. Sleeping {retry_after}s...")
time.sleep(retry_after)
return call_mcp(tool_name, args) # Retry
return response.json()Best Practices
1. Batch Operations
Instead of calling tools one at a time, combine related operations:
# Inefficient: 10 separate calls
for app in apps:
update_app(app.id, {...})
# Better: Use bulk patterns where available
# Or space out calls to stay within limits2. Use Caching
Cache responses that don’t change frequently:
const appCache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async function getApp(appId) {
const cached = appCache.get(appId);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const app = await mcpCall('get_app', { app_id: appId });
appCache.set(appId, { data: app, timestamp: Date.now() });
return app;
}3. Monitor Your Usage
Check the remaining points before large operations:
// Check headers from any response
const remaining = parseInt(response.headers['x-ratelimit-remaining']);
const resetTime = parseInt(response.headers['x-ratelimit-reset']);
if (remaining < 5 && operations.length > 5) {
const waitTime = resetTime - Math.floor(Date.now() / 1000);
console.log(`Low on requests. Waiting ${waitTime}s before bulk operation...`);
await sleep(waitTime * 1000);
}4. Spread Requests Over Time
For non-urgent bulk operations, spread requests evenly:
async function bulkUpdate(apps, updateFn) {
const REQUESTS_PER_MINUTE = 25; // Stay under limit
const DELAY_MS = 60000 / REQUESTS_PER_MINUTE; // ~2.4 seconds
for (const app of apps) {
await updateFn(app);
await sleep(DELAY_MS);
}
}5. Use Pagination Wisely
Fetch only what you need:
# Good: Small pages, specific queries
list_apps(limit=10, offset=0)
search_conversations(app_id="xxx", query="refund", limit=20)
# Avoid: Large fetches you don't need
list_apps(limit=1000)
export_conversations(app_id="xxx", since="2026-01-01") # Use sparinglyUpgrading Your Limits
Need higher limits? Upgrade your plan. Each plan’s limit is in Rate Limits by Tier.
Upgrade Your Plan
Common Scenarios
Bulk App Creation
Problem: Creating 50 apps hits rate limit.
Solution (BUILDER tier):
// 30 requests/minute = ~2 seconds between requests
for (const appConfig of appConfigs) {
await createApp(appConfig);
await sleep(2000); // 2 second delay
}
// Total time: ~100 seconds for 50 appsAnalytics Dashboard
Problem: Fetching analytics for 20 apps exceeds limit.
Solution:
// Option 1: Fetch in batches
const batches = chunk(appIds, 5);
for (const batch of batches) {
await Promise.all(batch.map(id => getAppAnalytics(id)));
await sleep(10000); // Wait 10s between batches
}
// Option 2: Cache results
// Analytics don't change by the second - cache for 5+ minutesCI/CD Integration
Problem: Automated deployments need multiple tool calls.
Solution:
# In your CI pipeline
steps:
- name: Update Chipp App
run: |
# Use retry logic
./update-app.sh --retry-on-rate-limit
env:
CHIPP_RETRY_DELAY: 60
CHIPP_MAX_RETRIES: 3Rate Limit FAQ
Q: Do rate limits apply per user or per organization? A: Both, and per token. All of a user’s OAuth tokens and API keys share one user budget. Each token or key also has its own budget, and your organization has a budget shared by all its members. Each team member has their own user budget.
Q: Does reading consume the same rate as writing?
A: No. Each tool has a weight. A cheap read costs 1 point. Exports, bulk analytics, image generation, ticket dispatch and batches cost more (see “What Each Call Costs”). initialize and tools/list do not spend this budget: they use a small separate one. Watch tools such as wait_for_tickets spend no points and use long-poll slots.
Q: Why did I get a 429 when I have points left?
A: Another limit was hit. Read the limit field of the error: concurrency means too many requests in flight, long_poll means too many open watch calls, credential or tenant means that token’s or your organization’s budget is spent, ip means too many requests from your network address, and global is a server-wide safety limit that clears in seconds.
Q: Are unauthenticated or failed requests limited? A: Yes. Requests from one network address are counted whether or not they are signed in, and repeated failed sign-ins from one address are limited and then blocked for a short time.
Q: What happens if I’m rate limited mid-workflow? A: Implement retry logic with exponential backoff. Your MCP client should handle 429 responses gracefully.
Q: Is there an unlimited plan? A: No. Enterprise has a high default budget, and every response still carries the rate limit headers.
Q: Can I request higher limits for a specific use case? A: Enterprise customers can negotiate custom limits for their organization. Contact sales@chipp.ai for details.
Next Steps
- Tools Reference - All available tools
- Common Workflows - Efficient usage patterns
- Authentication - Token management