Skip to content

Rate Limits & Quotas

BVE Gateway enforces per-key rate limits using a Cloudflare Durable Object (ApiKeyLimiter). Each API key gets its own DO instance, so limits are applied independently per key.

LimitScopeDefaultConfigurableEnforced
RPMPer minute60Yes (per key)Yes
RPDPer day10,000Yes (per key)Yes
Monthly requestsPer calendar monthNoneYes (per key)Yes
Monthly tokensPer calendar monthNoneYes (per key)Yes

RPM, RPD, and monthly request limits are enforced by the ApiKeyLimiter Durable Object. The monthly_token_limit field is enforced as a pre-flight D1 check before the DO call: if the key’s accumulated total_tokens for the current month meets or exceeds the limit, the request is rejected with 429 rate_limit_exceeded.

Defaults are set at key creation and can be customized via the rpm_limit, rpd_limit, monthly_limit, and monthly_token_limit fields in POST /admin/api-keys.

  1. On each request, the auth middleware resolves the API key from D1.
  2. The quota middleware calls ApiKeyLimiter.checkAndIncrement() on the DO instance for that key.
  3. If any limit is exceeded, the request is rejected with 429.
  4. If the DO call fails, the request is allowed (fail-open) to avoid blocking legitimate traffic.
WindowResets
MinuteRolling — each DO instance has a resetAt timestamp set 60 seconds ahead at window start
DayUTC midnight
MonthFirst day of the next UTC month

When a limit is exceeded, the response is:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 42
{
"error": {
"message": "Rate limit exceeded: requests per minute",
"type": "rate_limit_error",
"param": null,
"code": "rate_limit_exceeded"
}
}

All 429 responses include a Retry-After header with the number of seconds until the rate-limit window resets. Clients should wait at least this many seconds before retrying.

Possible error messages:

  • "Rate limit exceeded: requests per minute" — RPM limit hit
  • "Rate limit exceeded: requests per day" — RPD limit hit
  • "Monthly request limit exceeded" — monthly request cap hit
  • "Monthly token limit exceeded" — key’s monthly_token_limit reached

In addition to per-key limits, the Worker has global request caps configured in wrangler.jsonc:

ThresholdValue
Soft cap8,500,000 req/month
Hard cap9,500,000 req/month

These thresholds are enforced by the globalRateLimiter middleware. Each Worker isolate maintains its own request counter; enforcement is per-isolate (not cluster-wide across all Cloudflare edge nodes). At the soft cap a warning is logged but the request continues. At the hard cap the middleware returns 503 capacity_exceeded immediately without forwarding to Fuelix.

BVE Gateway adds the following headers to every allowed API response. They reflect the per-key limits configured in BVE Gateway, not the upstream Fuelix account capacity.

HeaderExampleDescription
X-RateLimit-Limit-Requests60The key’s rpm_limit — max requests per minute
X-RateLimit-Remaining-Requests57Requests remaining in the current minute window
X-RateLimit-Reset-Requests42sSeconds until the minute window resets
HeaderExampleDescription
X-RateLimit-Limit-Day10000The key’s rpd_limit — max requests per day
X-RateLimit-Remaining-Day9843Requests remaining until UTC midnight
X-RateLimit-Reset-Day38412sSeconds until the next UTC midnight (day window reset)

These headers are only present when the key has a monthly_limit configured (the per-month request cap set at key creation). They reflect the count before the current request is counted, consistent with how all other X-RateLimit-Remaining-* headers behave.

HeaderExampleDescription
X-RateLimit-Limit-Month1000The key’s monthly_limit — max requests per calendar month
X-RateLimit-Remaining-Month748Requests remaining before the monthly request cap is hit
X-RateLimit-Reset-Month604800sSeconds until the first day of the next UTC calendar month (when the counter resets)

These headers are absent for keys with no monthly_limit set. When the monthly request limit is hit, the gateway returns 429 rate_limit_exceeded with message "Monthly request limit exceeded" and a Retry-After pointing to the start of next month.

These headers are only present when the key has a monthly_token_limit configured. They reflect the pre-request token total — the current request’s tokens are counted asynchronously after the upstream response, so the remaining value is approximate (the same approach used by OpenAI’s own token rate-limit headers).

HeaderExampleDescription
X-RateLimit-Limit-Tokens50000The key’s monthly_token_limit — max tokens per calendar month
X-RateLimit-Remaining-Tokens47832Tokens remaining before the monthly token limit is hit (pre-request count)
X-RateLimit-Reset-Tokens691200sSeconds until the start of the next UTC calendar month (when the counter resets)

These headers are absent for keys with no monthly_token_limit set. When the monthly token limit is hit, the gateway returns 429 rate_limit_exceeded with Retry-After pointing to the start of next month.

These headers are only set on allowed (non-429) responses. If the Durable Object fails (fail-open path), no X-RateLimit-* headers are set.

All headers are exposed via CORS (Access-Control-Expose-Headers) so browser-based clients (e.g. OpenAI TypeScript SDK in a SPA) can read them.

import OpenAI from 'openai';
const client = new OpenAI({
apiKey: 'sk-bve-YOUR_KEY',
baseURL: 'https://api.bve.me/v1',
});
// Use the raw fetch override to capture response headers
const rawClient = new OpenAI({
apiKey: 'sk-bve-YOUR_KEY',
baseURL: 'https://api.bve.me/v1',
fetch: async (url, init) => {
const res = await globalThis.fetch(url, init);
console.log('RPM remaining:', res.headers.get('x-ratelimit-remaining-requests'));
console.log('RPD remaining:', res.headers.get('x-ratelimit-remaining-day') ?? '(no day header)');
console.log('Reset in:', res.headers.get('x-ratelimit-reset-requests'));
return res;
},
});
await rawClient.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'hi' }],
});

All 429 responses include a Retry-After header (integer seconds). Use it to back off before retrying rather than using a fixed sleep.

import OpenAI from 'openai';
const client = new OpenAI({
apiKey: 'sk-bve-YOUR_KEY',
baseURL: 'https://api.bve.me/v1',
maxRetries: 0, // handle retries manually to respect Retry-After
});
async function chatWithRetry(prompt: string, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await client.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: prompt }],
});
} catch (err) {
if (err instanceof OpenAI.RateLimitError) {
const retryAfter = Number(err.headers?.['retry-after'] ?? 60);
console.log(`Rate limited. Retrying in ${retryAfter}s (attempt ${attempt}/${maxAttempts})`);
if (attempt === maxAttempts) throw err;
await new Promise(r => setTimeout(r, retryAfter * 1000));
} else {
throw err;
}
}
}
}
const result = await chatWithRetry('Hello!');
console.log(result.choices[0].message.content);

Checking remaining quota (response headers from Fuelix)

Section titled “Checking remaining quota (response headers from Fuelix)”

If Fuelix includes quota headers in its response, they are forwarded:

HeaderDescription
x-quota-allowedWhether quota was allowed (from Fuelix)
x-quota-availableRemaining quota (from Fuelix)
x-quota-resetQuota reset time (from Fuelix)

The following headers are forwarded from Fuelix when present. They reflect the aggregate upstream Fuelix account limits, not the per-key BVE Gateway RPM/RPD/monthly limits documented above.

HeaderDescription
x-ratelimit-limit-requestsUpstream request rate limit
x-ratelimit-limit-tokensUpstream token rate limit
x-ratelimit-remaining-requestsUpstream requests remaining in window
x-ratelimit-remaining-tokensUpstream tokens remaining in window
x-ratelimit-reset-requestsTime until upstream request window resets
x-ratelimit-reset-tokensTime until upstream token window resets
retry-afterSeconds to wait before retrying (forwarded from upstream 429/503)

These headers are also exposed via CORS, so browser-based SDK clients (OpenAI TypeScript SDK in a SPA) can read them for pre-emptive backoff without waiting for a 429.

The following provider-native headers are forwarded from Fuelix when present. They are useful for provider-specific SDK integrations and request tracking.

These headers are returned by Anthropic’s API and forwarded to clients calling /v1/messages. They reflect the Fuelix account’s aggregate Anthropic quota, not per-key BVE Gateway limits.

HeaderDescription
anthropic-ratelimit-requests-limitAnthropic account’s requests-per-minute limit
anthropic-ratelimit-requests-remainingRequests remaining in the current Anthropic window
anthropic-ratelimit-requests-resetISO 8601 timestamp when the Anthropic request window resets
anthropic-ratelimit-tokens-limitAnthropic account’s combined (input + output) tokens-per-minute limit
anthropic-ratelimit-tokens-remainingCombined tokens remaining in the current Anthropic window
anthropic-ratelimit-tokens-resetISO 8601 timestamp when the Anthropic token window resets

Anthropic also exposes separate limits for input and output tokens. These are forwarded when present:

HeaderDescription
anthropic-ratelimit-input-tokens-limitAnthropic account’s input (prompt) tokens-per-minute limit
anthropic-ratelimit-input-tokens-remainingInput tokens remaining in the current window
anthropic-ratelimit-input-tokens-resetISO 8601 timestamp when the input token window resets
anthropic-ratelimit-output-tokens-limitAnthropic account’s output (completion) tokens-per-minute limit
anthropic-ratelimit-output-tokens-remainingOutput tokens remaining in the current window
anthropic-ratelimit-output-tokens-resetISO 8601 timestamp when the output token window resets

Use the per-direction headers to distinguish input vs. output budget exhaustion — critical for accurate pre-emptive backoff in rate-limited Anthropic SDK apps (@anthropic-ai/sdk). A request that is heavy on output tokens may still have ample input token budget remaining, so acting on only the aggregate header can cause unnecessary backoff.

HeaderDescription
x-groq-request-idGroq’s internal request ID — use for support requests and log correlation on Groq’s side
openai-processing-msOpenAI’s server-side processing time in milliseconds — useful for client-side latency attribution

These headers are returned by OpenRouter when your request is routed through it and are forwarded to the client.

HeaderDescription
x-openrouter-modelThe actual model ID selected by OpenRouter after provider routing (e.g. openai/gpt-4o). Useful for confirming which model served a request when using OpenRouter’s automatic model selection.
x-or-cache-statusOpenRouter semantic cache result: HIT if the response came from OpenRouter’s semantic cache, MISS if a fresh inference was run. Cache hits save tokens and reduce latency.
x-or-remaining-tokensToken budget remaining in the current OpenRouter rate-limit window. Analogous to x-ratelimit-remaining-tokens but specific to the x-or-* header family.

All provider-specific headers are exposed via CORS (Access-Control-Expose-Headers) so browser-based SDK clients can read them from cross-origin JavaScript.