model-এর কাছে পৌঁছানোর আগেই চার ধরনের limit আপনার request আটকে দিতে পারে: মিনিটে request, একসাথে চলা request, plan-এর usage window আর key-র মাসিক spend cap। এর ওপর আবার upstream provider-দের নিজস্ব rate limit আছে। এই পেজে প্রতিটার কাজ, কী error আসে আর client-এর কী করা উচিত, সবই পাবেন।
এক নজরে rate limit#
| Limit | কোথায় খাটে | Default | Error | Retry-After |
|---|---|---|---|---|
| মিনিটে request | অ্যাকাউন্ট (সব key মিলিয়ে) | 60 RPM, বা আপনার plan-এর মান | 429 rate_limited | পরের মিনিট শুরু হতে যত সেকেন্ড বাকি |
| একসাথে চলা request | অ্যাকাউন্ট | plan-এর limit; plan থাকলে 10, না থাকলে 3 | 429 concurrency_limit | 2 |
| Usage window | সাবস্ক্রিপশন | plan ঠিক করে দেয় | 429 window_exhausted | window reset হতে যত সেকেন্ড বাকি |
| মাসিক spend cap | একটা key | key বানানোর সময় না দিলে নেই | 403 monthly_spend_cap_exceeded | পাঠানো হয় না |
| Upstream provider-এর limit | Provider | provider ঠিক করে | 429 rate_limit_exceeded | provider পাঠালে সেটাই সরাসরি দেওয়া হয় |
আপনার plan-এর ঠিক সংখ্যাগুলো billing-এ দেখা যায়, আর plans and wallet পেজে বোঝানো আছে।
মিনিটে request#
মিনিটের limit প্রতি অ্যাকাউন্টের request গোনে, ঘড়ির মিনিট ধরে ধরে এক মিনিটের নির্দিষ্ট bucket-এ। অ্যাকাউন্টের সব key একই bucket ভাগ করে নেয়, তাই বেশি key বানালে limit বাড়ে না। Dashboard-এর playground-এর আলাদা limit আছে, 10 RPM, আর এটা আপনার API বরাদ্দ খরচ করে না।
limit পার হলে পান 429 rate_limited। Retry-After-এ থাকে চলতি মিনিটের বাকি সেকেন্ড, তাই অপেক্ষা কখনো 60 সেকেন্ডের বেশি হয় না। error message-এ "this key" লেখা থাকে, কিন্তু limit আসলে অ্যাকাউন্টের।
টাকার অভাবে (402 insufficient_credits বা no_funding) কিংবা usage window শেষ হওয়ায় ফেরানো request-ও ওই মিনিটের হিসাবে গোনা হয়। তাই 402 পেয়ে tight loop-এ retry করলে per-minute limitও ধরবে। 402 কখনোই retry করবেন না।
GET /v1/models আর GET /v1/tokens/usage গোনা হয় না।
Concurrency limit#
Concurrency মানে আপনার অ্যাকাউন্টে একই সময়ে চলতে থাকা request-এর সংখ্যা। streaming request admission থেকে stream শেষ হওয়া পর্যন্ত একটা slot দখল করে রাখে। তাই যে coding agent একসাথে কয়েকটা stream খোলে, বা যে script asyncio.gather দিয়ে অনেকগুলো request ছড়িয়ে দেয়, সে মিনিটের limit-এর আগেই এখানে আটকে যেতে পারে।
429 concurrency_limit response-এ আসে Retry-After: 2। এর সমাধান সাধারণত আরও জোরে retry করা নয়, নিজের দিকে parallelism সীমিত করা, যেমন plan-এর limit-এর চেয়ে কম মানের একটা semaphore বসানো। যে stream আর লাগবে না সেটা close বা abort করুন। ফেলে রাখা stream শেষ না হওয়া পর্যন্ত তার slot ধরে রাখে।
Usage window#
সাবস্ক্রিপশন plan-এ usage window থাকতে পারে। প্রতিটা window-র সীমা credit-এ (USD-তে মাপা) অথবা request-এর সংখ্যায়:
| Window | API-তে type | কখন reset হয় |
|---|---|---|
| 5-hour session | session_5h | যে request window খুলেছিল তার 5 ঘণ্টা পর |
| Weekly | weekly | প্রতি সোমবার 00:00 UTC-তে |
| Monthly | monthly | সাবস্ক্রিপশন period-এর শেষে |
যেকোনো একটা window শেষ হয়ে গেলে request-এ আসে 429 window_exhausted, আর Retry-After হলো ওই window reset হতে বাকি সেকেন্ড। এটা কয়েক ঘণ্টাও হতে পারে, তাই নিজে নিজে retry করাবেন না। user-কে reset-এর সময়টা দেখান, নয়তো কাজটা থামিয়ে দিন। লম্বা agent চালানোর আগে কতটা বাকি আছে দেখে নিন:
curl -s https://tokens.bd/v1/tokens/usage -H "Authorization: Bearer $TOKENS_API_KEY"response-এর গড়ন models and usage পেজে আছে। 50, 75, 90 আর 100 শতাংশে alert কীভাবে আসে, সেটা usage and alerts পেজে পাবেন।
Key-ভিত্তিক মাসিক spend cap#
একটা key-র USD-তে মাসিক spend cap থাকতে পারে, যেটা key বানানোর সময়ই ঠিক করতে হয়। খরচ গোনা হয় ক্যালেন্ডার মাস (UTC) ধরে। প্রতিটা request-এর আগে gateway দেখে key-র এ পর্যন্ত খরচ, তার সাথে নতুন request-এর সবচেয়ে খারাপ ক্ষেত্রের খরচ যোগ করে। এই হিসাব হয় request-এর max_tokens ধরে, দেওয়া না থাকলে 8,192 output token ধরে। তাই cap-এর কাছাকাছি গেলে বড় max_tokens-ওয়ালা request ফিরিয়ে দেওয়া হতে পারে, অথচ ছোটটা পার হয়ে যায়।
error আসে 403 monthly_spend_cap_exceeded, 429 নয়, কারণ কয়েক সেকেন্ড অপেক্ষা করলে কিছু বদলায় না। বানানোর পর cap edit করা যায় না। বেশি দরকার হলে নতুন key বানাতে হবে। যে key shared CI system-এ বা আপনি নজর রাখেন না এমন agent-এ যাবে, তাতে একটা মাত্র setting দিলে সেটা হোক spend cap। আরও জানতে API keys দেখুন।
Retry-After আর rate limit header#
X-RateLimit-Limit বা X-RateLimit-Remaining header নেই। rate limit-এর একমাত্র header হলো Retry-After, সেকেন্ডে, যা 429 response-এ আসে। আগে থেকে দেখতে চান কতটা বাকি আছে? GET /v1/tokens/usage poll করুন।
Client-side backoff-এর উদাহরণ#
OpenAI আর Anthropic SDK কিছু 429 আর 5xx error নিজে নিজেই retry করে (max_retries, default 2)। নিচের উদাহরণে সেটা বন্ধ করে নিজের হাতে সামলানো হয়েছে, যাতে window_exhausted retry না হয় আর Retry-After মানা হয়।
import os
import random
import time
import openai
from openai import OpenAI
client = OpenAI(
base_url="https://tokens.bd/v1",
api_key=os.environ["TOKENS_API_KEY"],
max_retries=0,
)
RETRYABLE = {429, 500, 502, 503, 504}
def create_with_backoff(max_attempts: int = 5, **kwargs):
delay = 1.0
for attempt in range(1, max_attempts + 1):
try:
return client.chat.completions.create(**kwargs)
except openai.APIStatusError as e:
if (
e.status_code not in RETRYABLE
or e.code == "window_exhausted"
or attempt == max_attempts
):
raise
try:
wait = float(e.response.headers.get("retry-after", delay))
except ValueError:
wait = delay
request_id = e.response.headers.get("x-tokens-request-id")
print(f"{e.status_code} {e.code} (request {request_id}), retrying in {wait:.1f}s")
except openai.APIConnectionError:
if attempt == max_attempts:
raise
wait = delay
time.sleep(min(wait, 60) + random.uniform(0, 0.5 * delay))
delay = min(delay * 2, 30)
resp = create_with_backoff(
model="deepseek/deepseek-v4.1-flash",
messages=[{"role": "user", "content": "Say hello."}],
max_tokens=50,
)
print(resp.choices[0].message.content)import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://tokens.bd/v1",
apiKey: process.env.TOKENS_API_KEY,
maxRetries: 0,
});
const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
function retryAfterMs(err: InstanceType<typeof OpenAI.APIError>): number | undefined {
const h: unknown = err.headers; // a Headers object or a plain record, depending on SDK version
const value =
h instanceof Headers
? h.get("retry-after")
: (h as Record<string, string | undefined> | undefined)?.["retry-after"];
return value ? Number(value) * 1000 : undefined;
}
async function createWithBackoff(
body: OpenAI.Chat.ChatCompletionCreateParamsNonStreaming,
maxAttempts = 5
) {
let delay = 1000;
for (let attempt = 1; ; attempt++) {
try {
return await client.chat.completions.create(body);
} catch (err) {
if (!(err instanceof OpenAI.APIError) || attempt >= maxAttempts) throw err;
const connectionError = err instanceof OpenAI.APIConnectionError;
if (
!connectionError &&
(!RETRYABLE.has(err.status ?? 0) || err.code === "window_exhausted")
) {
throw err;
}
const wait = (!connectionError && retryAfterMs(err)) || delay;
await sleep(Math.min(wait, 60_000) + Math.random() * 0.5 * delay);
delay = Math.min(delay * 2, 30_000);
}
}
}
const resp = await createWithBackoff({
model: "deepseek/deepseek-v4.1-flash",
messages: [{ role: "user", content: "Say hello." }],
max_tokens: 50,
});
console.log(resp.choices[0].message.content);সফল হওয়া প্রতিটা retry-ও একটা billed request, তাই চেষ্টার সংখ্যা কম রাখুন। কোন code retry করা যাবে আর কোনটা যাবে না, তার পুরো তালিকা errors পেজে আছে।