Cookbook

Usage & Integration Guide

From first request to production deployment — copy-paste recipes for every common integration. Plus: Telegram bot and the browser-clickable GET /ask endpoint.

1. Setup

You need a running gateway (deployed or local) and at least one provider key configured on it. Keys are server-side environment variables — never shipped to clients.

  1. Copy the examples: cp .env.example .env (Node) or cp .dev.vars.example .dev.vars (wrangler).
  2. Fill in one of: OPENROUTER_API_KEY, POOLSIDE_API_KEY, or the CUSTOM_* trio.
  3. Start the gateway: npm start → http://localhost:5000.
.env (local example)
OPENROUTER_API_KEY=sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxx
POOLSIDE_API_KEY=psk-xxxxxxxxxxxxxxxxxxxxxxxx
# Optional:
DEFAULT_PROVIDER=openrouter
# CUSTOM_BASE_URL=https://api.openai.com/v1
# CUSTOM_API_KEY=sk-xxxx
# CUSTOM_MODEL=gpt-4o-mini
বাংলা ব্যাখ্যা

Key-গুলো কোনো ফাইলে হার্ডকোড করবেন না। লোকালে .env ফাইলে (যা .gitignore-এ আছে), আর Cloudflare-এ ড্যাশবোর্ডের Environment variables-এ রাখুন। যে key-ই আগে সেট করবেন, সেই provider সাথে সাথে চালু হয়ে যাবে — GET /api/v1/providers কল করে নিশ্চিত হতে পারবেন।

2. First request

The smallest possible integration — one POST with JSON:

bash
curl -X POST https://YOUR-DOMAIN/api/v1/chat \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "openrouter",
    "message": "What is the meaning of life?",
    "temperature": 0.7
  }'
javascript
const res = await fetch("https://YOUR-DOMAIN" + "/api/v1/chat", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    provider: "openrouter",
    message: "What is the meaning of life?",
  }),
});
const data = await res.json();
console.log(data.reply);
python
import requests

data = requests.post(
    "https://YOUR-DOMAIN/api/v1/chat",
    json={"message": "What is the meaning of life?"},
    timeout=60,
).json()
print(data["reply"], "— served by", data["provider"])

Swap "provider" to "poolside" for code-focused questions — same body, same response shape.

3. JavaScript / Node

Reusable client function

chat.js — works in browsers and Node 18+
const GATEWAY = "https://YOUR-DOMAIN";   // auto-detected on this page
const API_KEY  = "";                             // only if API_SECRET_KEY is set

export async function chat({ provider, message, systemPrompt, model, temperature, maxTokens }) {
  const body = { provider, message };
  if (systemPrompt) body.system_prompt = systemPrompt;
  if (model) body.model = model;
  if (temperature !== undefined) body.temperature = temperature;
  if (maxTokens) body.max_tokens = maxTokens;

  const res = await fetch(`${GATEWAY}/api/v1/chat`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      ...(API_KEY ? { "X-API-Key": API_KEY } : {}),
    },
    body: JSON.stringify(body),
  });

  const data = await res.json();
  if (!data.success) {
    const err = new Error(data.error?.message || "Gateway error");
    err.code = data.error?.code;
    err.retryAfter = data.error?.details?.retryAfterSeconds;
    throw err;
  }
  return data; // { reply, provider, model, usage }
}

Usage

example.mjs
import { chat } from "./chat.js";

const answer = await chat({
  provider: "poolside",
  message: "Write a Go function that sums a slice of ints",
  systemPrompt: "You are a senior Go developer. Answer with code first.",
  maxTokens: 400,
});
console.log(answer.reply);

4. Python

gateway.py
import os
import requests

GATEWAY = "https://YOUR-DOMAIN"
API_KEY = os.getenv("GATEWAY_API_KEY", "")  # only if API_SECRET_KEY is set


def chat(message, provider="openrouter", system_prompt=None, **kwargs):
    payload = {"provider": provider, "message": message, **kwargs}
    if system_prompt:
        payload["system_prompt"] = system_prompt

    res = requests.post(
        f"{GATEWAY}/api/v1/chat",
        json=payload,
        headers={"X-API-Key": API_KEY} if API_KEY else {},
        timeout=60,
    )
    res.raise_for_status()
    data = res.json()
    if not data["success"]:
        raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
    return data["reply"]


if __name__ == "__main__":
    print(chat("What are channels in Go?", provider="poolside"))

5. Multi-turn conversations

The gateway is stateless — you own the conversation. Keep the turns in your app and send the full messages[] array each time:

conversation loop (JavaScript)
const turns = [
  { role: "system", content: "You are a helpful assistant." },
];

async function talk(userText) {
  turns.push({ role: "user", content: userText });

  const res = await fetch(`${GATEWAY}/api/v1/chat`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ provider: "openrouter", messages: turns }),
  });
  const data = await res.json();
  if (!data.success) throw new Error(data.error?.code);

  turns.push({ role: "assistant", content: data.reply });
  return data.reply;
}

await talk("Hi! What is HTTP 429?");     // -> explanation
await talk("Show me a retry example.");  // model remembers the context
Trim old turns when the conversation grows — MAX_MESSAGES defaults to 50 and MAX_MESSAGE_LENGTH to 8000 chars.

6. Errors, retries & auto-failover

Heads-up: the gateway already retries for you. Auto-failover is enabled by default — when a provider errors (rate limit, timeout, outage…), the gateway transparently retries the next configured provider until a response arrives, and the response tells you who answered via the provider field and the failover.attempts list. Most client-side retry loops are therefore unnecessary; add one only as a final safety net for ALL_PROVIDERS_FAILED or network-level failures.

Retry only on transient codes: ALL_PROVIDERS_FAILED, UPSTREAM_RATE_LIMIT, UPSTREAM_UNAVAILABLE, UPSTREAM_TIMEOUT, UPSTREAM_ERROR. Never retry validation (400) or auth (401) errors.

smart retry with backoff (JavaScript)
const RETRYABLE = new Set([
  "UPSTREAM_RATE_LIMIT", "UPSTREAM_UNAVAILABLE",
  "UPSTREAM_TIMEOUT", "UPSTREAM_ERROR",
]);

export async function chatWithRetry(body, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(`${GATEWAY}/api/v1/chat`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
    });
    const data = await res.json();

    if (data.success) return data.reply;

    const code = data.error?.code;
    if (!RETRYABLE.has(code) || i === attempts - 1) {
      throw new Error(`${code}: ${data.error?.message}`);
    }

    const wait = (data.error?.details?.retryAfterSeconds ?? 2 ** i) * 1000;
    await new Promise((r) => setTimeout(r, wait));
  }
}
বাংলা ব্যাখ্যা

ফ্রি-টিয়ার মডেল (যেমন OpenRouter-এর :free) মাঝে মাঝে 429 UPSTREAM_RATE_LIMIT দেয় — এটা স্বাভাবিক। details.retryAfterSeconds অনুযায়ী অপেক্ষা করে আবার চেষ্টা করলেই হয়। 400/401 এরর কখনো retry করবেন না; সেগুলো ঠিক করতে হবে।

7. Deploy to Cloudflare Pages

  1. Push to GitHub — the whole project folder, with .env/.dev.vars excluded (the repo's .gitignore already does this).
  2. Create the Pages project: Cloudflare dashboard → Workers & Pages → Create → Pages → Connect to Git → select the repo.
  3. Build settings — exactly as below:
    SettingValue
    Project nameyour choice (becomes *.pages.dev subdomain)
    Production branchmain
    Framework presetNone
    Build commandnpm run build
    Build output directoryout
    Root directory (Advanced)leave empty
  4. Set environment variables: Settings → Variables and Secrets → use Import .env to upload your env file in one go (or add OPENROUTER_API_KEY, POOLSIDE_API_KEY etc. manually). Add to both Production and Preview. Keys should be type Secret.
  5. Deploy — first build runs automatically. Every push to main redeploys.
  6. Verify: open https://YOUR-PROJECT.pages.dev/api/v1/health → should return {"status":"ok",…}.
বাংলা ব্যাখ্যা

ধাপে ধাপে: ① পুরো ফোল্ডার GitHub-এ push করুন (key ছাড়া)। ② Cloudflare-এ Workers & Pages → Create → Pages → Connect to Git। ③ Build command npm run build, Output out, Root directory খালি। ④ Settings → Variables and Secrets-এ Import .env দিয়ে একবারে সব ভেরিয়েবল আপলোড করুন (বা আলাদা করে key দিন) — Production এবং Preview দুই জায়গাতেই। ⑤ ডিপ্লয় শেষে /api/v1/health চেক করুন। এরপর আপনার সাইটের যেকোনো পেজ (Playground, Status) ব্রাউজারে খুলেই লাইভ ব্যবহার করতে পারবেন।

8. Local development

bash
npm install

# Option A — Node runtime (simplest): serves site + API on :5000
cp .env.example .env      # add your keys
npm start

# Option B — exact Cloudflare runtime (wrangler): site + API on :8787
cp .dev.vars.example .dev.vars   # add your keys
npm run dev

# Smoke tests against a running gateway
npm run smoke                 # add RUN_E2E=1 for a real chat call

Then open http://localhost:5000 (or :8787) — the full site with the playground runs right next to the API.

9. Troubleshooting

SymptomCodeFix
"provider is not configured"PROVIDER_NOT_CONFIGUREDEnv var missing on the server — add it and redeploy (Cloudflare needs a new deployment to see new vars).
"rejected the API key"UPSTREAM_AUTH_ERRORKey invalid/revoked — re-check the value in the dashboard (no quotes, no spaces).
Free model returns 429 oftenUPSTREAM_RATE_LIMITShared free pool is busy — auto-failover will hop to the next provider; keep the request as-is or set a paid default model.
All providers failALL_PROVIDERS_FAILEDCheck error.details.attempts — each failed provider with its reason; fix keys/quota or raise FAILOVER_MAX_ROUNDS.
"could not find the requested model"UPSTREAM_ERRORModel id typo — copy exact ids from the provider's docs.
Request hangs then 504UPSTREAM_TIMEOUTRaise PROVIDER_TIMEOUT_MS (default 60000).
Browser blocks the callCORS error in consoleAdd your origin to ALLOWED_ORIGINS (comma-separated).
401 from your own gatewayUNAUTHORIZEDYou set API_SECRET_KEY — clients must send X-API-Key.

Adding your own provider next? → Custom Provider guide