Build a chat app
Use the official OpenAI SDK to send a first message to PleumRouter, then add streaming, multi-turn chat, model swapping and cost checks.
PleumRouter is an OpenAI-compatible API, so the official SDKs work once you change the base_url. This tutorial builds a small terminal chat app step by step. To connect a coding agent instead, go to Claude Code or Codex CLI.
Build it#
Prerequisites
TypeScript needs Node.js 20 or later; Python needs 3.10 or later. In the dashboard's API keys page, create a key that starts with
plm_and put it in an environment variable.Terminalexport PLEUM_API_KEY="plm_xxxxxxxxxxxxxxxx"Create a project and install the SDK
Terminalmkdir pleum-chat && cd pleum-chat npm init -y npm pkg set type=module npm i openai npm i -D tsx typescript @types/nodemkdir pleum-chat && cd pleum-chat python3 -m venv .venv && source .venv/bin/activate pip install openaiSend your first message
Call once with a client whose
base_urlpoints at PleumRouter, and print the reply and token usage.chat.tsimport OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://apirouter.pleum.ai/v1", apiKey: process.env.PLEUM_API_KEY, }); const model = "gpt-5.5"; const res = await client.chat.completions.create({ model, messages: [{ role: "user", content: "Say hello in one sentence." }], }); console.log(res.choices[0].message.content); console.log(res.usage); // run: npx tsx chat.tsimport os from openai import OpenAI client = OpenAI( base_url="https://apirouter.pleum.ai/v1", api_key=os.environ["PLEUM_API_KEY"], ) model = "gpt-5.5" res = client.chat.completions.create( model=model, messages=[{"role": "user", "content": "Say hello in one sentence."}], ) print(res.choices[0].message.content) print(res.usage) # run: python chat.pyStream the response
With
stream: true, tokens arrive as they are generated, so waiting feels much shorter.chat.tsimport OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://apirouter.pleum.ai/v1", apiKey: process.env.PLEUM_API_KEY, }); const model = "gpt-5.5"; const stream = await client.chat.completions.create({ model, messages: [{ role: "user", content: "Write a haiku about routers." }], stream: true, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); } process.stdout.write("\n");import os from openai import OpenAI client = OpenAI( base_url="https://apirouter.pleum.ai/v1", api_key=os.environ["PLEUM_API_KEY"], ) model = "gpt-5.5" stream = client.chat.completions.create( model=model, messages=[{"role": "user", "content": "Write a haiku about routers."}], stream=True, ) for chunk in stream: if chunk.choices: print(chunk.choices[0].delta.content or "", end="", flush=True) print()Add multi-turn conversation
Keep the history (
messages) yourself and send it every time so the model remembers what was said. Typeexitto quit.chat.tsimport * as readline from "node:readline/promises"; import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://apirouter.pleum.ai/v1", apiKey: process.env.PLEUM_API_KEY, }); const model = "gpt-5.5"; const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [ { role: "system", content: "You are a concise assistant." }, ]; while (true) { const text = (await rl.question("you> ")).trim(); if (!text || text === "exit") break; messages.push({ role: "user", content: text }); const stream = await client.chat.completions.create({ model, messages, stream: true }); let reply = ""; process.stdout.write("bot> "); for await (const chunk of stream) { const delta = chunk.choices[0]?.delta?.content ?? ""; reply += delta; process.stdout.write(delta); } process.stdout.write("\n"); messages.push({ role: "assistant", content: reply }); } rl.close();import os from openai import OpenAI client = OpenAI( base_url="https://apirouter.pleum.ai/v1", api_key=os.environ["PLEUM_API_KEY"], ) model = "gpt-5.5" messages = [{"role": "system", "content": "You are a concise assistant."}] while True: text = input("you> ").strip() if not text or text == "exit": break messages.append({"role": "user", "content": text}) stream = client.chat.completions.create(model=model, messages=messages, stream=True) reply = "" print("bot> ", end="") for chunk in stream: if chunk.choices: delta = chunk.choices[0].delta.content or "" reply += delta print(delta, end="", flush=True) print() messages.append({"role": "assistant", "content": reply})Swap models
The same code works with models from many makers; change only the model. Look up exact IDs in the model catalog or with
GET /v1/models.Terminal# every model in the catalog curl https://apirouter.pleum.ai/v1/models # then change only the model id in chat.ts / chat.py, for example: # claude-sonnet-5 # gemini-3.8-flash # deepseek-ai/DeepSeek-V4.1-FlashCheck the cost
Cost settles after the reply. Look it up with the
X-Request-Idresponse header atGET /v1/generation?id=…to getcost_krwand token counts. Streaming works the same way, and dashboard usage shows it too. Before settlement the lookup can return 404, so try again a moment later.chat.tsimport OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://apirouter.pleum.ai/v1", apiKey: process.env.PLEUM_API_KEY, }); const model = "gpt-5.5"; const raw = await client.chat.completions .create({ model, messages: [{ role: "user", content: "Say hello in one sentence." }], }) .asResponse(); // the settled cost is looked up by request id const requestId = raw.headers.get("x-request-id"); const gen = await fetch(`https://apirouter.pleum.ai/v1/generation?id=${requestId}`, { headers: { Authorization: `Bearer ${process.env.PLEUM_API_KEY}` }, }); console.log(await gen.json()); // cost_krw, input_tokens, output_tokens ...import json import urllib.request import os from openai import OpenAI client = OpenAI( base_url="https://apirouter.pleum.ai/v1", api_key=os.environ["PLEUM_API_KEY"], ) model = "gpt-5.5" raw = client.chat.completions.with_raw_response.create( model=model, messages=[{"role": "user", "content": "Say hello in one sentence."}], ) # the settled cost is looked up by request id request_id = raw.headers.get("x-request-id") req = urllib.request.Request( f"https://apirouter.pleum.ai/v1/generation?id={request_id}", headers={"Authorization": f"Bearer {os.environ['PLEUM_API_KEY']}"}, ) print(json.load(urllib.request.urlopen(req))) # cost_krw, input_tokens ...
Check your work#
- The first message gets a reply and
usageshows token counts. - Streaming shows text a little at a time.
- The multi-turn chat remembers what you said earlier.
- Changing only
modelmakes a different model answer. - You can see a
cost_krwvalue.
Next steps#
- Chat Completions — every request parameter and response field
- Streaming — SSE format per route, and cost lookup
- Choose a model or route — pick a model for the job
- Connect a Coding Agent — attach Claude Code, Codex and more