Skip to content

Rate Limits

IndepAI uses rate limiting to ensure fair usage and protect the API from abuse. This page explains how rate limits work and how to handle them in your application.

Rate limits are based on your subscription tier:

TierDaily LimitAI Coach LimitBurst Limit
Starter1,000 requests50 requests60 req/minute
Pro10,000 requests500 requests300 req/minute

Unauthenticated requests are rate-limited by IP address:

  • 100 requests per day across all public endpoints
  • 10 requests per minute burst limit

Every API response includes rate limit headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1705312800
X-RateLimit-Resource: api
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the period
X-RateLimit-RemainingRequests remaining in current period
X-RateLimit-ResetUnix timestamp when the limit resets
X-RateLimit-ResourceThe resource being rate-limited

When you exceed the rate limit, the API returns:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 3600
{
"success": false,
"error": "Rate limit exceeded",
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Upgrade to increase your API limit.",
"retryAfter": 3600
}

The Retry-After header tells you how many seconds to wait:

async function makeRequest() {
const response = await fetch("/api/v1/calculator", {
method: "POST",
body: JSON.stringify(data),
});
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After") || 60;
console.log(`Rate limited. Retry in ${retryAfter} seconds`);
// Wait and retry
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
return makeRequest();
}
return response.json();
}

For production applications, use exponential backoff:

async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
const retryAfter = parseInt(response.headers.get("Retry-After") || "60");
const backoff = Math.min(retryAfter, Math.pow(2, attempt) * 1000);
console.log(`Rate limited. Retrying in ${backoff}ms (attempt ${attempt + 1})`);
await new Promise((resolve) => setTimeout(resolve, backoff));
}
throw new Error("Max retries exceeded");
}

Track your remaining requests and plan accordingly:

let remainingRequests = 100;
async function makeAPICall() {
const response = await fetch("/api/v1/calculator", { method: "POST", body });
// Update tracking
remainingRequests = parseInt(response.headers.get("X-RateLimit-Remaining") || "0");
if (remainingRequests < 10) {
console.warn(`Low on API requests: ${remainingRequests} remaining`);
}
return response.json();
}

Avoid unnecessary API calls by caching:

const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
async function getCachedResult(key: string, fetcher: () => Promise<any>) {
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
return cached.data;
}
const data = await fetcher();
cache.set(key, { data, timestamp: Date.now() });
return data;
}

Where possible, batch multiple operations:

// Instead of multiple individual requests
const results = await Promise.all(
assets.map((a) =>
fetch("/api/v1/assets", {
method: "POST",
body: JSON.stringify(a),
})
)
);
// Consider using batch endpoints when available
const result = await fetch("/api/v1/assets/batch", {
method: "POST",
body: JSON.stringify({ assets }),
});

Some endpoints have additional restrictions:

EndpointAdditional Limit
/api/v1/ai/*5-500/day by tier
/api/v1/geo/recommendations20/hour
/api/v1/tax/calculate50/hour

In development, you can test rate limit handling:

Terminal window
# Make 101 requests quickly to trigger limit
for i in {1..101}; do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST http://localhost:3000/api/v1/calculator \
-H "Content-Type: application/json" \
-d '{"currentAge":30,"annualIncome":80000,"annualExpenses":40000,"currentSavings":100000}'
done

If you need higher limits, upgrade your plan:

  • Starter - Standard API access for personal use
  • Pro - Higher limits for power users and integrations

See current pricing and upgrade at indepai.app/dashboard/settings.

Each HTTP request to an API endpoint counts as one request, regardless of the response status.

Yes, all requests count including 4xx and 5xx errors.

Daily limits reset at midnight UTC. The exact reset time is in the X-RateLimit-Reset header.

Contact support@indepai.app to discuss options.