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.
- Copy the examples:
cp .env.example .env(Node) orcp .dev.vars.example .dev.vars(wrangler). - Fill in one of:
OPENROUTER_API_KEY,POOLSIDE_API_KEY, or theCUSTOM_*trio. - Start the gateway:
npm start→http://localhost:5000.
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:
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
}'
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);
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
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
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
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:
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
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.
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
- Push to GitHub — the whole project folder, with
.env/.dev.varsexcluded (the repo's.gitignorealready does this). - Create the Pages project: Cloudflare dashboard → Workers & Pages → Create → Pages → Connect to Git → select the repo.
- Build settings — exactly as below:
Setting Value Project name your choice (becomes *.pages.devsubdomain)Production branch mainFramework preset None Build command npm run buildBuild output directory outRoot directory (Advanced) leave empty - 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_KEYetc. manually). Add to both Production and Preview. Keys should be type Secret. - Deploy — first build runs automatically. Every push to
mainredeploys. - 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
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
| Symptom | Code | Fix |
|---|---|---|
| "provider is not configured" | PROVIDER_NOT_CONFIGURED | Env var missing on the server — add it and redeploy (Cloudflare needs a new deployment to see new vars). |
| "rejected the API key" | UPSTREAM_AUTH_ERROR | Key invalid/revoked — re-check the value in the dashboard (no quotes, no spaces). |
| Free model returns 429 often | UPSTREAM_RATE_LIMIT | Shared 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 fail | ALL_PROVIDERS_FAILED | Check error.details.attempts — each failed provider with its reason; fix keys/quota or raise FAILOVER_MAX_ROUNDS. |
| "could not find the requested model" | UPSTREAM_ERROR | Model id typo — copy exact ids from the provider's docs. |
| Request hangs then 504 | UPSTREAM_TIMEOUT | Raise PROVIDER_TIMEOUT_MS (default 60000). |
| Browser blocks the call | CORS error in console | Add your origin to ALLOWED_ORIGINS (comma-separated). |
| 401 from your own gateway | UNAUTHORIZED | You set API_SECRET_KEY — clients must send X-API-Key. |
Adding your own provider next? → Custom Provider guide