Skip to content

Production checklist

আসল user-রা Tokens API-র ওপর নির্ভর করার আগে যা ঠিক করে রাখা দরকার: retry ও backoff, timeout, Retry-After, request id, প্রতি environment-এ আলাদা key, spend cap, alert, key rotation, আর 402 ও 429 এলে কী করবেন।

Markdown-এ দেখুন
এই পাতায়

laptop-এ চলা script production-এ যেভাবে ভাঙে, সেগুলো আগে থেকেই বোঝা যায়: হঠাৎ traffic এসে rate limit ছুঁয়ে ফেলে, লম্বা উত্তর timeout পার করে যায়, রাতে ব্যালান্স শেষ হয়ে যায়, key ফাঁস হয়। প্রতিটার সমাধান এখন সেট করতে লাগে কয়েক মিনিট, আর outage-এর মধ্যে লাগে কয়েক ঘণ্টা। এই পেজে সেই তালিকাটা আছে, গুরুত্বের ক্রমে, কারণসহ।

প্রতিটা error-এর বিস্তারিত আছে errors আর rate limits পেজে। এই পেজ বলে, চালু system-এ সেগুলো এলে কী করবেন।

Checklist#

হয়েছে?বিষয়
[ ]API key আছে environment variable বা secret store-এ, কখনো code বা client bundle-এ নয়
[ ]প্রতি environment-এর (development, staging, production) জন্য আলাদা key, প্রতিটায় spend cap
[ ]production key শুধু সেই model-গুলোতে সীমিত, যেগুলো service ব্যবহার করে
[ ]প্রতিটা request-এ max_tokens দেওয়া আছে
[ ]client timeout নিজে ঠিক করা, আর লম্বা বা reasoning request-এর জন্য আরও বড়
[ ]exponential backoff আর jitter দিয়ে retry, শুধু সেই error-এ যেগুলোতে retry করা সার্থক
[ ]Retry-After মেনে চলা হয়
[ ]402 আর window_exhausted কখনো loop-এ retry করা হয় না
[ ]একসাথে চলা request আপনার plan-এর concurrency সীমার নিচে বাঁধা
[ ]প্রতিটা request-এর x-tokens-request-id log হয়, সফল হোক বা না হোক
[ ]usage alert চালু আছে, আর কেউ সেগুলো পড়ে
[ ]key rotation-এর একটা পরিকল্পনা আছে, আর অন্তত একবার চালিয়ে দেখা হয়েছে
[ ]model পাওয়া না গেলে আপনার product কী করবে, সেটা ঠিক করা আছে

Key: প্রতি environment-এ একটা, cap দেওয়া#

প্রতিটা environment আর প্রতিটা service-এর জন্য আলাদা key বানান। কিছু গোলমাল হলে usage page আর key-এর তালিকায় key-এর নাম দেখে বুঝবেন খরচটা কোথা থেকে এসেছে, আর একটা key revoke করলেও বাকিগুলো বন্ধ হবে না।

key বানানোর সময়ই একটা monthly spend cap দিন। cap থাকলে কোনো runaway loop, retry-র ঝড় বা ফাঁস হওয়া key বিল না হয়ে error হয়ে থামে। cap পরে বদলানো যায় না: বদলাতে হলে নতুন key বানাতে হয়। সাথে allowed models-এর তালিকাও দিন, যাতে কোনো bug আপনার পরীক্ষা করা model-এর চেয়ে দামি model-কে call করতে না পারে।

প্রতি অ্যাকাউন্টে key-এর সংখ্যা plan অনুযায়ী সীমিত (default হলো 3টা active key)। development, staging, production আর CI job-এর জন্য একটা, এই ভাগ চাইলে layout ঠিক করার আগে pricing পেজে নিজের plan-এর সীমাটা দেখে নিন। local development-এ আপনার tool যে key আগে থেকে ব্যবহার করছে সেটাই চালাতে পারেন, শুধু তাতে cap থাকতে হবে।

key রাখবেন না source control-এ, container image-এ, বা client-side code-এ। আপনার user browser বা ফোন থেকে চালালে দেখুন browser and mobile। পুরো গাইড API keys পেজে।

প্রতিটা request-এ max_tokens দিন#

request চলার আগে gateway তার সবচেয়ে খারাপ অবস্থার খরচটা আপনার plan বা Wallet থেকে আলাদা করে ধরে রাখে। ওই হিসাবের output অংশে আপনার max_tokens (বা max_completion_tokens) ধরা হয়, আর না দিলে ধরা হয় 8,192। এর দুটো ফল:

  • max_tokens বড় বা না দেওয়া থাকলে key-এর spend cap-এর কাছাকাছি, বা ব্যালান্স কম থাকলে, request প্রত্যাখ্যান হতে পারে, যদিও আসল উত্তর ছোটই হতো।
  • ব্যালান্স পুরো ধরে রাখা অঙ্কটা মেটাতে না পারলে gateway max_tokens কমিয়ে ব্যালান্সে যতটা কুলায় ততটা করে দেয়, সর্বনিম্ন 16 পর্যন্ত। তখন উত্তর finish_reason: "length" নিয়ে আগেই থেমে যায়। টাকা কম থাকলে উত্তর কাটা পড়ছে দেখলে কারণ এটাই।

এমন একটা সীমা বেছে নিন, যেটা আপনার চাওয়া সবচেয়ে লম্বা উত্তরের জন্য যথেষ্ট। model যত বড় দিতে দেয় তত বড় নয়।

Retry#

যে error নিজে থেকে মিটে যেতে পারে সেগুলোতেই retry করুন, আর কোনোটায় নয়।

Retry করবেন?Errorকীভাবে
হ্যাঁ, Retry-After পেরোনোর পর429 rate_limited, concurrency_limit, rate_limit_exceededheader-এ যত সেকেন্ড লেখা তত সেকেন্ড অপেক্ষা করুন, সাথে একটু random jitter
হ্যাঁ, exponential backoff দিয়ে500, 502, 503, 504, আর আপনার দিকের connection error বা timeoutপ্রায় 1 সেকেন্ড দিয়ে শুরু, প্রতিবার দ্বিগুণ, 30 সেকেন্ডে থামুন, 4 বা 5 বার চেষ্টা
শুধু reset-এর পরে429 window_exhausted, model_limit_reachedloop-এ নয়। Retry-After ঘণ্টা বা দিনও হতে পারে। job থামিয়ে রাখুন
না, আগে কিছু ঠিক করুন400, 401, 402, 403, 404, 413এগুলো বারবার একই হবে। কোনো মানুষকে alert করুন

তিনটা খুঁটিনাটি ঠিক করে retry কাজে লাগবে, না ক্ষতি করবে:

  • retry মানে নতুন request। idempotency key বলে কিছু নেই। প্রথম request gateway-তে আসলে শেষ হয়ে গিয়েছিল কিন্তু আপনার client তার আগেই timeout করেছে, এমন হলে প্রথমটার বিল হয়েছে, আর retry-র বিলও আলাদা হবে। চেষ্টার সংখ্যা কম রাখুন, আর লম্বা prompt নিয়ে সাবধান থাকুন।
  • আগে SDK দেখে নিন। OpenAI আর Anthropic-এর SDK কিছু error-এ নিজেই retry করে (max_retries)। তার ওপর নিজের loop বসালে চেষ্টা গুণ হয়ে যায়। হয় SDK-র retry বন্ধ করুন, যেমন rate limits-এর উদাহরণে করা হয়েছে, নয়তো SDK-র ওপর ভরসা করুন আর নিজে কিছু যোগ করবেন না।
  • যে stream শুরু হয়ে কাজে লেগে গেছে, সেটা retry করবেন না। উত্তর মাঝপথে ভাঙলে retry করলে পুরো prompt আবার যায়, আবার বিল হয়। আংশিক text যথেষ্ট ভালো কি না আগে ঠিক করুন। শুধু সেই stream-ই retry করুন, যেটা কোনো content আসার আগে fail করেছে। দেখুন streaming।

যেসব model-এর একাধিক provider আছে, সেগুলোর ক্ষেত্রে gateway আপনাকে error ফেরত দেওয়ার আগেই provider বদলে চেষ্টা করে দেখে। তাই 5xx দেখলে বুঝে নিন gateway চেষ্টা করেই এসেছে।

একটা request wrapper#

এই wrapper chat completions call করে timeout সহ, প্রতি চেষ্টায় নতুন request id দিয়ে, Retry-After মেনে backoff করে, যে code-এ মানুষ লাগে সেগুলোয় retry না করে, আর প্রতিটা ব্যর্থতার জন্য একটা log লাইন লেখে। এখানে Node.js 18 বা তার পরের version-এর global fetch ব্যবহার হয়েছে।

tokens-client.ts
const BASE_URL = "https://tokens.bd/v1";
const RETRY_STATUS = new Set([429, 500, 502, 503, 504]);
const WAIT_FOR_RESET = new Set(["window_exhausted", "model_limit_reached"]);

export type TokensResult =
  | { ok: true; data: unknown; requestId: string | null }
  | { ok: false; status: number; code: string; requestId: string | null };

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

export async function chat(
  body: Record<string, unknown>,
  maxAttempts = 4
): Promise<TokensResult> {
  let delay = 1000;
  for (let attempt = 1; ; attempt++) {
    const clientRequestId = crypto.randomUUID();
    let status = 0; // 0 means no response: timeout or connection error
    let code = "network_error";
    let requestId: string | null = null;
    let wait = delay;

    try {
      const res = await fetch(`${BASE_URL}/chat/completions`, {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.TOKENS_API_KEY}`,
          "Content-Type": "application/json",
          "x-request-id": clientRequestId,
        },
        body: JSON.stringify(body),
        signal: AbortSignal.timeout(180_000),
      });
      requestId = res.headers.get("x-tokens-request-id");
      if (res.ok) return { ok: true, data: await res.json(), requestId };

      status = res.status;
      const err = (await res.json().catch(() => null)) as { error?: { code?: string } } | null;
      code = err?.error?.code ?? "unknown";
      const retryAfter = Number(res.headers.get("retry-after"));
      if (retryAfter > 0) wait = retryAfter * 1000;
    } catch {
      // Timeout or connection error: handled below like a 5xx.
    }

    console.warn(
      JSON.stringify({ event: "tokens_call_failed", attempt, status, code, requestId, clientRequestId })
    );
    const retryable = status === 0 || RETRY_STATUS.has(status);
    if (!retryable || WAIT_FOR_RESET.has(code) || attempt >= maxAttempts) {
      return { ok: false, status, code, requestId };
    }
    await sleep(Math.min(wait, 60_000) + Math.random() * 500);
    delay = Math.min(delay * 2, 30_000);
  }
}

180 সেকেন্ডের timeout মাঝারি max_tokens সহ non-streaming call-এর জন্য মানানসই। লম্বা request-এর জন্য পরের অংশ দেখুন।

Timeout#

timeout ভেবেচিন্তে ঠিক করুন। library-র default model call-এর জন্য প্রায় কখনোই ঠিক হয় না।

  • gateway ধৈর্যশীল। যে model-এর provider একটাই, তার জন্য প্রথম byte আসার আগে সে 600 সেকেন্ড পর্যন্ত অপেক্ষা করে, আর stream চালু হওয়ার পর দুটো chunk-এর মাঝের নীরবতায় 300 সেকেন্ড পর্যন্ত। reasoning model কিছু বলার আগে কয়েক মিনিট ভাবতে পারে। provider সময়মতো শুরু না করলে আপনি পান 504 upstream_timeout।
  • আপনার client-কে অন্তত request-এর দরকার অনুযায়ী ধৈর্যশীল হতে হবে। Node.js-এর built-in fetch default-এ response header আসার আগে 300 সেকেন্ড পর হাল ছেড়ে দেয়, আর body-র দুটো chunk-এর মাঝেও 300 সেকেন্ড। এটা এর পেছনের HTTP client undici-র documentation অনুযায়ী। যে request-এ এর চেয়ে বেশি সময় লাগে, তার জন্য বড় সীমা দিতে হবে।
  • যা লম্বা চলতে পারে তা stream করুন। non-streaming request পুরো উত্তর তৈরি না হওয়া পর্যন্ত কিছুই পাঠায় না, তাই আপনার app-এর সামনের proxy বা load balancer সেটাকে idle ভেবে কেটে দিতে পারে। stream চলতে চলতে data পাঠায়। লম্বা উত্তর আর লম্বা agent কাজে stream: true দিন।
  • নিজের infrastructure-ও খেয়াল রাখুন। reverse proxy, serverless platform আর mobile network-এ প্রায়ই idle বা মোট সময়ের সীমা থাকে 30 থেকে 120 সেকেন্ড। আপনার user আর Tokens-এর মাঝের প্রতিটা ধাপ দেখে নিন।
  • user চলে গেলে cancel করুন। একটা abort signal পাঠান। gateway তখন upstream request থামিয়ে দেয়, আর যতটুকু তৈরি হয়েছে শুধু ততটুকুর বিল করে।
  • সবকিছুর জন্য একটাই timeout রাখবেন না। ছোট একটা summary আর tool সহ agent-এর একটা turn-এর জন্য আলাদা সীমা দরকার।

কোনো model-এর backup provider থাকলে stream-এ প্রথম token ছাড়া প্রায় 30 সেকেন্ড পার হলে gateway সেটায় চলে যায় (non-streaming request-এ 120 সেকেন্ড)। এই বদল আপনি টেরও পান না। দেখুন streaming।

Retry-After#

Retry-After হলো Tokens-এর পাঠানো একমাত্র rate-limit header। এটা সেকেন্ডে, আর আসে 429 response-এ। X-RateLimit-* header নেই। এর মানে নির্ভর করে code-এর ওপর:

CodeRetry-AfterRetry করবেন?
rate_limitedপরের মিনিট শুরু হতে যত সেকেন্ড বাকি (সর্বোচ্চ 60)হ্যাঁ
concurrency_limit2হ্যাঁ, parallelism কমিয়ে
rate_limit_exceededprovider থেকে এলে সেটাই পাস করা হয়হ্যাঁ, backoff সহ
window_exhaustedwindow reset হতে যত সেকেন্ড বাকি, প্রায়ই কয়েক ঘণ্টানা। job থামান বা user-কে জানান
model_limit_reachedbilling period reset হতে যত সেকেন্ড বাকিনা। অন্য model ব্যবহার করুন বা অপেক্ষা করুন

অন্তত ততক্ষণ অপেক্ষা করুন, আর সাথে একটু random jitter যোগ করুন, যাতে অনেক client একসাথে জেগে না ওঠে। সব সময় আগে code পড়ুন, তারপর header।

Concurrency সীমার নিচে থাকুন#

প্রতি মিনিটের request আর একসাথে চলা request, দুটোই আপনার পুরো অ্যাকাউন্টের সীমা, সব key মিলিয়ে ভাগ হয়। streaming request শেষ না হওয়া পর্যন্ত একটা slot ধরে রাখে। আপনার service একসাথে অনেক request ছড়িয়ে দিলে সব পাঠিয়ে 429 retry না করে, plan-এর concurrency সীমার একটু নিচে মাপা semaphore বা queue দিয়ে parallelism বেঁধে দিন। বেশি key বানালে সীমা বাড়ে না। ফেলে রাখা stream শেষ না হওয়া পর্যন্ত slot আটকে রাখে, তাই সেগুলো বন্ধ করুন।

Request id#

প্রতিটা call-এর x-tokens-request-id log করুন, সফল হোক বা ব্যর্থ। আপনার request খুঁজে পেতে Support-এর শুধু এই একটা মানই লাগে। নিজের x-request-id-ও পাঠান, যাতে আপনার log-এর একটা লাইন Tokens-এর id-র সাথে মেলানো যায়। call আপনার দিকে timeout করলে আপনার হাতে কোনো response বা Tokens-এর id থাকে না, তাই call-এর আগেই নিজের id log করে রাখুন। পুরো গাইড request ids and debugging পেজে।

Alert আর খরচ#

  • usage alert চালু করুন। Dashboard-এর Notifications-এ গিয়ে: plan-এর সীমার 50, 75 ও 90 শতাংশে সতর্কবার্তা, আর Wallet-এর ব্যালান্স $5-এর নিচে নামলে low-balance ইমেইল। প্রতিটা threshold-এ একবারই যায়। দেখুন usage and alerts।
  • renewal reminder চালু করুন। plan নিজে থেকে renew হয় না। চুপচাপ মেয়াদ শেষ হয়ে যাওয়া plan দেখতে ঠিক 402-এর মতোই।
  • লম্বা job-এর আগে দেখে নিন। GET /v1/tokens/usage আপনার window, Wallet-এর ব্যালান্স আর key-এর cap ফেরত দেয়। এটার বিল হয় না, আর per-minute সীমাতেও গোনা হয় না। তাই batch job শুরুর আগে আর প্রতি ধাপের মাঝে এটা দেখে নিতে পারে।
  • প্রতিটা release-এর পর usage page দেখুন। prompt-এর size বদলালে, retry-র bug থাকলে, বা নতুন agent এলে প্রতি request-এর token বা খরচ হঠাৎ লাফিয়ে ওঠে। দেখুন Usage।
bash
curl -s https://tokens.bd/v1/tokens/usage -H "Authorization: Bearer $TOKENS_API_KEY" \
  | jq '{wallet: .wallet.balanceUsd, windows: [.windows[] | {type, percentUsed, resetsAt}]}'

402 আর 429 সামলানো#

402 (insufficient_credits, no_funding, outstanding_debt, member_cap_reached)। আপনার plan বা Wallet request-এর দাম মেটাতে পারছে না। retry করে এটা ঠিক হয় না, আর প্রত্যাখ্যাত প্রতিটা request-ও আপনার per-minute সীমায় গোনা হয়। আপনার code-এ করণীয়:

  1. ওই key-তে পাঠানো বন্ধ করুন। circuit খুলে দিন, যাতে service-এর বাকি অংশ চেষ্টা চালিয়ে না যায়।
  2. যিনি টাকা যোগ করতে পারেন তাঁকে request id সহ alert করুন।
  3. নিজের user-দের ছোট করে কিছু বলুন, যেমন "service সাময়িকভাবে পাওয়া যাচ্ছে না"। তাদের billing-এর বার্তা দেখাবেন না।
  4. billing থেকে টাকা যোগ করার পর circuit বন্ধ করুন।

429। code দেখে ভাগ করুন:

  • rate_limited, concurrency_limit, rate_limit_exceeded: Retry-After পর্যন্ত অপেক্ষা করে retry করুন। ঘন ঘন হলে আপনি ক্ষমতার বেশি চালাচ্ছেন: request queue করুন বা parallelism বাঁধুন।
  • window_exhausted: আপনার plan-এর 5-hour, weekly বা monthly usage শেষ। loop চালাবেন না। reset-এর সময় দেখান, job থামিয়ে রাখুন, বা Wallet ব্যবহার করলে ব্যালান্স আছে এমন Wallet-এ চলে যান।
  • model_limit_reached: এই একটা model-এর ওই সময়ের ভাগ শেষ। অন্য model-গুলো চলবে। অন্য model-এ যান বা অপেক্ষা করুন।

monthly_spend_cap_exceeded (403) হলো key-এর নিজের cap। কয়েক সেকেন্ড অপেক্ষায় লাভ নেই। অন্য key ব্যবহার করুন, বা পরের মাসের জন্য অপেক্ষা করুন।

Graceful degradation#

Tokens বা কোনো একটা model পাওয়া না গেলে আপনার product কী করবে, সেটা এখনই ঠিক করুন।

  • দ্বিতীয় একটা model রাখুন। GET /v1/models থেকে আপনার কাজের জন্য যথেষ্ট একটা বেছে নিন। retry-র পরেও 5xx আসলে, model_not_available এলে, আর model_limit_reached এলে সেটায় fallback করুন। আগে পরীক্ষা করে নিন, কারণ tool calling আর context size-এ model-এ model-এ তফাত থাকে। দেখুন choosing a model।
  • যে error অন্য model-এ ঠিক হবে না, সেখানে fallback করবেন না: 401, 402, 403 আর বেশিরভাগ 400 হয় request, key বা অ্যাকাউন্টের সমস্যা।
  • পরিষ্কারভাবে ব্যর্থ হোন। user-কে সৎ একটা বার্তা দিন আর আবার চেষ্টার উপায় রাখুন। background কাজ queue করে পরে চালান।
  • সীমিত অবস্থায় load কমান। ছোট prompt, ছোট max_tokens, বা automatic summary-র মতো ঐচ্ছিক feature বন্ধ করে।
  • status page দেখুন। অনেক কিছু একসাথে fail করলে নিজের code ঘাঁটার আগে status দেখে নিন।

ওপরের wrapper-টাই, এবার environment থেকে নেওয়া fallback model সহ:

answer.ts
import { chat } from "./tokens-client";

type Messages = { role: "system" | "user" | "assistant"; content: string }[];

export async function answer(messages: Messages) {
  const primary = await chat({ model: "deepseek/deepseek-v4.1-flash", messages, max_tokens: 800 });
  if (primary.ok) return primary;

  const fallbackModel = process.env.FALLBACK_MODEL;
  const worthFallingBack =
    primary.status === 0 ||
    primary.status >= 500 ||
    primary.code === "model_not_available" ||
    primary.code === "model_limit_reached";
  if (!fallbackModel || !worthFallingBack) return primary;

  return chat({ model: fallbackModel, messages, max_tokens: 800 });
}

Key rotate করা#

দরকার পড়ার আগেই rotation-এর পরিকল্পনা করুন, আর সবকিছু ঠিকঠাক থাকতে একবার চালিয়ে দেখুন।

  • নিয়মিত rotate করুন, আর key ফাঁস বা কেউ টিম ছাড়লে তো অবশ্যই। ফাঁস হওয়া key তখনই revoke বা rotate করুন।
  • key rotate করলে তার secret সাথে সাথে বদলে যায়। পুরোনো secret আর কাজ করে না, কোনো grace period নেই, আর যে client তখনও সেটা ব্যবহার করছে সে পায় 401 invalid_api_key। key-এর নাম, cap, allowed models আর history একই থাকে।
  • downtime ছাড়া করতে হলে আগে দ্বিতীয় একটা key বানান। সেটা deploy করুন, usage page-এ দেখুন traffic সেটা দিয়ে যাচ্ছে, তারপর পুরোনো key revoke করুন। এর জন্য আপনার plan-এ একটা key slot খালি থাকতে হবে।
  • secret এক জায়গায় রাখুন (secret manager বা আপনার platform-এর environment settings), যাতে বদলানো মানে একটা update আর একটা restart, repository ঘেঁটে বেড়ানো নয়।
  • ব্যর্থতাটাও পরীক্ষা করুন। staging-এ staging key revoke করে দেখুন আপনার service alert দেয় কি না, আর নতুন key বসানোর পর আবার ঠিক হয় কি না।

Launch-এর আগে#

শুরুর টেবিলটা একবার মিলিয়ে নিন। তারপর কয়েকটা test request পাঠান: ইচ্ছে করে ভুল key দিয়ে, আপনার key-র অনুমতি নেই এমন model দিয়ে, আর অতিরিক্ত বড় prompt দিয়ে। দেখুন আপনার service request id log করছে কি না, retry করছে না কি না, আর user-কে একটা যুক্তিসঙ্গত বার্তা দেখাচ্ছে কি না। আসল traffic এই অবস্থাগুলো ঠিকই খুঁজে নেবে, আপনি আগে দেখুন বা না দেখুন।

এই পাতাটা কি কাজে লেগেছে?

এখনো আটকে আছেন? Support ticket খুলুন

আপনার agent set up করতে সাহায্য লাগবে?

Connection tester দিয়ে সংযোগ পরীক্ষা করে নিন, অথবা একটা API key তৈরি করুন।