{"site":{"name":"Koji","description":"AI-native customer research platform that helps teams conduct, analyze, and synthesize customer interviews at scale.","url":"https://www.koji.so","contentTypes":["blog","documentation"],"lastUpdated":"2026-05-18T13:49:04.245Z"},"content":[{"type":"documentation","id":"5ef076ae-c4ed-405f-aaef-438b5fa9d76c","slug":"rate-limits-and-cors","title":"Rate Limits and CORS","url":"https://www.koji.so/docs/rate-limits-and-cors","summary":"Koji API rate limiting uses a 1-minute sliding window with a default of 60 requests per minute per API key. Response headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset enable proactive rate management. CORS configuration controls browser-based access. API access is available on all plans.","content":"# Rate Limits and CORS\n\nKoji'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.\n\n---\n\n## Rate Limiting\n\nRate limits protect the API from abuse and ensure consistent performance for all users. Every API request you make counts toward your rate limit allocation.\n\n### How Rate Limits Work\n\nRate 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.\n\nAPI access and rate limiting are available on all Koji plans, including the free tier.\n\n### Rate Limit Headers\n\nEvery API response includes headers that tell you your current rate limit status:\n\n| Header | Description |\n|---|---|\n| `X-RateLimit-Limit` | The maximum number of requests allowed in the current window |\n| `X-RateLimit-Remaining` | How many requests you have left in the current window |\n| `X-RateLimit-Reset` | Unix timestamp when the current window resets |\n\nUse these headers to implement intelligent rate limit handling in your application.\n\n### Example Response Headers\n\n```\nHTTP/1.1 200 OK\nX-RateLimit-Limit: 60\nX-RateLimit-Remaining: 47\nX-RateLimit-Reset: 1705312860\n```\n\nIn this example, you have 47 requests remaining out of 60 for this window. The window resets at the Unix timestamp 1705312860.\n\n### Handling 429 Responses\n\nWhen you hit the rate limit, the API returns:\n\n```\nHTTP/1.1 429 Too Many Requests\nX-RateLimit-Limit: 60\nX-RateLimit-Remaining: 0\nX-RateLimit-Reset: 1705312860\n```\n\n```json\n{\n  \"error\": \"rate_limited\",\n  \"message\": \"Rate limit exceeded. Please retry after the window resets.\"\n}\n```\n\nUse 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.\n\n### Implementing Backoff\n\nThe recommended approach is exponential backoff with jitter:\n\n```javascript\nasync function apiRequestWithRetry(url, options, maxRetries = 3) {\n  for (let attempt = 0; attempt <= maxRetries; attempt++) {\n    const response = await fetch(url, options);\n\n    if (response.status !== 429) {\n      return response;\n    }\n\n    if (attempt === maxRetries) {\n      throw new Error('Rate limit exceeded after max retries');\n    }\n\n    const resetAt = parseInt(\n      response.headers.get('X-RateLimit-Reset') || '0',\n      10\n    );\n    const now = Math.floor(Date.now() / 1000);\n    const waitSeconds = Math.max(resetAt - now, 1);\n    const jitter = Math.random() * 1000;\n    const delay = (waitSeconds * 1000) + jitter;\n\n    await new Promise(resolve => setTimeout(resolve, delay));\n  }\n}\n```\n\nThe jitter prevents the \"thundering herd\" problem where multiple clients retry simultaneously after a rate limit window resets.\n\n### Proactive Rate Limit Management\n\nRather than waiting for 429 errors, monitor the `X-RateLimit-Remaining` header and slow down as you approach the limit:\n\n```javascript\nasync function managedApiRequest(url, options) {\n  const response = await fetch(url, options);\n  const remaining = parseInt(\n    response.headers.get('X-RateLimit-Remaining') || '60',\n    10\n  );\n\n  if (remaining < 10) {\n    // Getting close to the limit - add a delay\n    await new Promise(resolve => setTimeout(resolve, 1000));\n  }\n\n  return response;\n}\n```\n\n---\n\n## CORS (Cross-Origin Resource Sharing)\n\nCORS 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).\n\n### How CORS Works in Koji\n\nKoji 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.\n\n### Configuring Allowed Origins\n\n1. Go to your project's **Settings > Integrations** page.\n2. Find the **CORS Origins** section.\n3. Add each domain that needs to make browser-based requests.\n4. Include the full origin with protocol (e.g., `https://yourapp.com`).\n5. Click **Save**.\n\n### CORS Configuration Guidelines\n\n- **Be specific.** List exact origins rather than using wildcards.\n- **Include all environments.** If you have staging and production domains, add both.\n- **Protocol matters.** `https://yourapp.com` and `http://yourapp.com` are different origins.\n- **No trailing slashes.** Use `https://yourapp.com`, not `https://yourapp.com/`.\n- **Subdomains are separate.** `https://api.yourapp.com` and `https://yourapp.com` are different origins.\n\n### Localhost for Development\n\nDuring 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.\n\n### When CORS Does Not Apply\n\nCORS 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.\n\n---\n\n## Server-Side vs. Client-Side Requests\n\nWe strongly recommend making all API calls from your server rather than from client-side JavaScript:\n\n| Concern | Client-Side | Server-Side |\n|---|---|---|\n| API key exposure | Key visible in browser | Key stays on your server |\n| CORS configuration | Required | Not needed |\n| Rate limit control | Hard to manage | Easy to manage |\n| Security | Less secure | More secure |\n\nIf you must make client-side requests, use the [embed widget](/docs/embed-widget-reference) instead — it handles authentication and CORS internally.\n\n---\n\n## Preflight Requests\n\nBrowsers 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.\n\n---\n\n## Troubleshooting\n\n### \"CORS policy: No 'Access-Control-Allow-Origin' header\"\n\nThis means your domain is not in the allowed origins list. Add it in Settings > Integrations > CORS Origins.\n\n### \"429 Too Many Requests\"\n\nYou 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.\n\n### Preflight requests failing\n\nEnsure your allowed origin exactly matches the Origin header the browser sends. Check protocol, domain, and port.\n\n---\n\n## Next Steps\n\n- [Set up API authentication](/docs/api-authentication)\n- [Start making API calls](/docs/starting-interviews-via-api)\n- [Explore the headless API overview](/docs/headless-api-overview)","category":"API Reference","lastModified":"2026-04-25T19:14:08.521275+00:00","metaTitle":"Rate Limits and CORS — Koji Docs","metaDescription":"Understand Koji API rate limits, response headers, backoff strategies, and CORS origin configuration.","keywords":["rate limiting","cors","api limits","rate limit headers","cross origin"],"aiSummary":"Koji API rate limiting uses a 1-minute sliding window with a default of 60 requests per minute per API key. Response headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset enable proactive rate management. CORS configuration controls browser-based access. API access is available on all plans.","aiPrerequisites":["api-authentication"],"aiLearningOutcomes":["Read and use rate limit headers","Implement exponential backoff with jitter","Configure CORS origins","Choose between client-side and server-side API calls"],"aiDifficulty":"intermediate","aiEstimatedTime":"7 min read"}],"pagination":{"total":1,"returned":1,"offset":0}}