Rate Limits and CORS
Understand how Koji's API rate limiting works and how to configure CORS origins for your integration.
Rate Limits and CORS
Koji's API uses rate limiting to ensure fair usage and platform stability, and CORS validation to control which domains can make browser-based requests. This article explains how both systems work and how to handle them in your integration.
Rate Limiting
Rate limits protect the API from abuse and ensure consistent performance for all users. Every API request you make counts toward your rate limit allocation.
How Rate Limits Work
Rate limits are applied per API key using a 1-minute sliding window. Each key has a default allowance of 60 requests per minute. When you exceed the limit, the API returns a 429 Too Many Requests response until the window resets.
API access and rate limiting are available on all Koji plans, including the free tier.
Rate Limit Headers
Every API response includes headers that tell you your current rate limit status:
| Header | Description |
|---|---|
X-RateLimit-Limit | The maximum number of requests allowed in the current window |
X-RateLimit-Remaining | How many requests you have left in the current window |
X-RateLimit-Reset | Unix timestamp when the current window resets |
Use these headers to implement intelligent rate limit handling in your application.
Example Response Headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1705312860
In this example, you have 47 requests remaining out of 60 for this window. The window resets at the Unix timestamp 1705312860.
Handling 429 Responses
When you hit the rate limit, the API returns:
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705312860
{
"error": "rate_limited",
"message": "Rate limit exceeded. Please retry after the window resets."
}
Use the X-RateLimit-Reset header to determine when you can resume making requests. Calculate the wait time by subtracting the current Unix timestamp from the reset value.
Implementing Backoff
The recommended approach is exponential backoff with jitter:
async function apiRequestWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
if (attempt === maxRetries) {
throw new Error('Rate limit exceeded after max retries');
}
const resetAt = parseInt(
response.headers.get('X-RateLimit-Reset') || '0',
10
);
const now = Math.floor(Date.now() / 1000);
const waitSeconds = Math.max(resetAt - now, 1);
const jitter = Math.random() * 1000;
const delay = (waitSeconds * 1000) + jitter;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
The jitter prevents the "thundering herd" problem where multiple clients retry simultaneously after a rate limit window resets.
Proactive Rate Limit Management
Rather than waiting for 429 errors, monitor the X-RateLimit-Remaining header and slow down as you approach the limit:
async function managedApiRequest(url, options) {
const response = await fetch(url, options);
const remaining = parseInt(
response.headers.get('X-RateLimit-Remaining') || '60',
10
);
if (remaining < 10) {
// Getting close to the limit - add a delay
await new Promise(resolve => setTimeout(resolve, 1000));
}
return response;
}
CORS (Cross-Origin Resource Sharing)
CORS controls which domains are allowed to make browser-based requests to the Koji API. This is relevant if you are making API calls directly from client-side JavaScript (which we generally discourage for security reasons).
How CORS Works in Koji
Koji validates the Origin header on incoming requests against a list of allowed origins configured in your project settings. If the origin is not in the allowed list, the browser blocks the response.
Configuring Allowed Origins
- Go to your project's Settings > Integrations page.
- Find the CORS Origins section.
- Add each domain that needs to make browser-based requests.
- Include the full origin with protocol (e.g.,
https://yourapp.com). - Click Save.
CORS Configuration Guidelines
- Be specific. List exact origins rather than using wildcards.
- Include all environments. If you have staging and production domains, add both.
- Protocol matters.
https://yourapp.comandhttp://yourapp.comare different origins. - No trailing slashes. Use
https://yourapp.com, nothttps://yourapp.com/. - Subdomains are separate.
https://api.yourapp.comandhttps://yourapp.comare different origins.
Localhost for Development
During development, you can add http://localhost:3000 (or whatever port your dev server uses) to the allowed origins. Remove localhost origins before going to production.
When CORS Does Not Apply
CORS only affects browser-based requests. Server-to-server API calls (from your backend) are not subject to CORS restrictions. This is another reason to make API calls from your backend rather than your frontend.
Server-Side vs. Client-Side Requests
We strongly recommend making all API calls from your server rather than from client-side JavaScript:
| Concern | Client-Side | Server-Side |
|---|---|---|
| API key exposure | Key visible in browser | Key stays on your server |
| CORS configuration | Required | Not needed |
| Rate limit control | Hard to manage | Easy to manage |
| Security | Less secure | More secure |
If you must make client-side requests, use the embed widget instead — it handles authentication and CORS internally.
Preflight Requests
Browsers send an OPTIONS preflight request before making certain cross-origin requests. Koji handles preflight requests automatically based on your CORS configuration. Preflight requests do not count toward your rate limit.
Troubleshooting
"CORS policy: No 'Access-Control-Allow-Origin' header"
This means your domain is not in the allowed origins list. Add it in Settings > Integrations > CORS Origins.
"429 Too Many Requests"
You have exceeded your rate limit of 60 requests per minute. Implement backoff logic and use the X-RateLimit-Reset header to determine when the window resets.
Preflight requests failing
Ensure your allowed origin exactly matches the Origin header the browser sends. Check protocol, domain, and port.
Next Steps
Related Articles
API Authentication
Learn how to authenticate with the Koji API using API keys and Bearer tokens.
Headless API Overview
Manage interviews programmatically with the Koji REST API — start, message, and complete interviews from your own code.
Send Research Insights to Slack: Real-Time Customer Interview Notifications via Webhooks
Pipe customer interview insights from Koji into your Slack workspace in real time. Use Koji webhooks to notify a #research channel the moment an interview completes, post quote highlights to #product-feedback, or alert #cs-alerts when a churn signal is detected. Step-by-step setup with a working Slack incoming webhook recipe.
Connect Koji to Zapier: Automate Customer Research Workflows in Minutes
Route every completed AI customer interview from Koji into 6,000+ Zapier apps — including Notion, Linear, Salesforce, Airtable, and Gmail. A step-by-step integration guide.
User Research API: Embed AI Interviews into Any Product or Workflow
How to use Koji's User Research API to run AI-moderated interviews from your own backend. Covers REST endpoints, the embed widget, webhooks, authentication, rate limits, and headless interview patterns.
Sync Koji Customer Interviews to HubSpot: Live Insights on Every Contact
Push Koji interview transcripts, themes, and quality scores onto HubSpot contact and company records in real time using webhooks and the HubSpot API.