본문으로 건너뛰기

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.

This tutorial calls the real API, so a small amount of credit is deducted. Finish the Quickstart first to sign up and top up.

Build it#

  1. 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.

    Terminal
    export PLEUM_API_KEY="plm_xxxxxxxxxxxxxxxx"
  2. Create a project and install the SDK

    Terminal

    mkdir pleum-chat && cd pleum-chat
    npm init -y
    npm pkg set type=module
    npm i openai
    npm i -D tsx typescript @types/node
  3. Send your first message

    Call once with a client whose base_url points at PleumRouter, and print the reply and token usage.

    chat.ts

    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 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.ts
  4. Stream the response

    With stream: true, tokens arrive as they are generated, so waiting feels much shorter.

    chat.ts

    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 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");
  5. Add multi-turn conversation

    Keep the history (messages) yourself and send it every time so the model remembers what was said. Type exit to quit.

    chat.ts

    import * 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();
  6. 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-Flash
  7. Check the cost

    Cost settles after the reply. Look it up with the X-Request-Id response header at GET /v1/generation?id=… to get cost_krw and 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.ts

    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 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 ...

Check your work#

  • The first message gets a reply and usage shows token counts.
  • Streaming shows text a little at a time.
  • The multi-turn chat remembers what you said earlier.
  • Changing only model makes a different model answer.
  • You can see a cost_krw value.

Next steps#