← Blog
By the apillms team · Published 2026-10-12 · Node.js · OpenAI SDK · API

Node.js LLM API quickstart: Claude, GPT and DeepSeek with one key

The openai npm package can talk to any OpenAI-compatible API, not just OpenAI's own models. apillms exposes Claude, GPT, DeepSeek and other models behind one OpenAI-compatible base URL and one API key, so you can write standard Node.js code and swap model ids without touching your request logic.

Project setup

Create a fresh project, or drop this into an existing one. You only need the official openai package - no extra client for Claude or DeepSeek.

mkdir apillms-node-demo && cd apillms-node-demo
npm init -y
npm install openai dotenv

Add "type": "module" to package.json so you can use ES module import syntax, or use require instead if you'd rather stay on CommonJS.

Create a .env file to hold your key - never commit this file:

APILLMS_API_KEY=your-key-here

Load it at the top of your entry file:

import "dotenv/config";
import OpenAI from "openai";

Get your key and base URL

Sign up at /login, which gives you free starter credits automatically and one API key created for your account. There is no "create key" step - you just show it or roll it on the API keys page if you ever need to disable the current one. Rolling the key immediately disables the old one, so update your .env file before you roll if you're running anything in production.

The OpenAI-compatible base URL is:

https://apillms.com/v1

If you'd rather use Anthropic's own SDK for Claude models, there's also an Anthropic-compatible base URL at https://apillms.com (no /v1). This post sticks to the openai package since it works for every model on the platform.

Basic chat completion

Point the SDK's baseURL at apillms and pass your key as apiKey. Everything else is the normal OpenAI SDK call:

import "dotenv/config";
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.APILLMS_API_KEY,
  baseURL: "https://apillms.com/v1",
});

const response = await client.chat.completions.create({
  model: "claude-sonnet-5",
  messages: [
    { role: "system", content: "You are a concise assistant." },
    { role: "user", content: "Summarize what a CDN does in two sentences." },
  ],
});

console.log(response.choices[0].message.content);

Run it with:

node index.js

If it prints a short answer, your key and base URL are working.

Switching models

The whole point of a single gateway is that switching models is a one-line change. The model ids below are all active on apillms right now:

// Claude
model: "claude-sonnet-5"

// GPT
model: "gpt-5.5"

// DeepSeek
model: "deepseek-v4-flash"

// Claude's larger model
model: "claude-opus-5-5"

Nothing else in your code changes - same messages array, same response shape. This makes it easy to A/B test models for a given task, or to pick a cheaper model for a given endpoint. The full list and filterable capabilities are on /models.

A small helper makes switching even easier at the call site:

async function ask(model, prompt) {
  const res = await client.chat.completions.create({
    model,
    messages: [{ role: "user", content: prompt }],
  });
  return res.choices[0].message.content;
}

const claudeAnswer = await ask("claude-sonnet-5", "Explain closures in JS.");
const gptAnswer = await ask("gpt-5.5", "Explain closures in JS.");
const dsAnswer = await ask("deepseek-v4-flash", "Explain closures in JS.");

You can run these three calls in parallel with Promise.all if you want to compare outputs side by side rather than waiting on each one sequentially.

Streaming responses

Streaming works the same way as with OpenAI's own API - just set stream: true:

const stream = await client.chat.completions.create({
  model: "gpt-5.5",
  messages: [{ role: "user", content: "Write a haiku about Node.js." }],
  stream: true,
});

for await (const chunk of stream) {
  const delta = chunk.choices[0]?.delta?.content;
  if (delta) process.stdout.write(delta);
}

If you need to cancel a long stream, pass an AbortController signal:

const controller = new AbortController();
setTimeout(() => controller.abort(), 15000);

const stream = await client.chat.completions.create(
  { model: "gpt-5.5", messages: [...], stream: true },
  { signal: controller.signal }
);

If you'd rather experiment without writing any code first, the console's Playground lets you pick a model, set system prompt, temperature and streaming on/off, and generates the matching code with its Code button.

Prompt caching, automatically

If you're sending the same system prompt or long context repeatedly - for example a coding agent re-sending file contents each turn - apillms applies prompt caching automatically with per-user cache keys. There's nothing to configure in your Node.js code: cache reads are simply billed at the lower cache price for the model you're calling. For claude-sonnet-5 that's $0.046 per 1M cached input tokens versus $0.274 for fresh input. See prompt caching explained for the mechanics.

Error handling for failed requests

The openai SDK throws typed errors you can catch with try/catch. apillms does not bill for failed requests, so a failed call costs you nothing, but you still want to handle it in your app:

import OpenAI, { APIError } from "openai";

const client = new OpenAI({
  apiKey: process.env.APILLMS_API_KEY,
  baseURL: "https://apillms.com/v1",
});

try {
  const response = await client.chat.completions.create({
    model: "claude-opus-5-5",
    messages: [{ role: "user", content: "Draft a release note." }],
  });
  console.log(response.choices[0].message.content);
} catch (err) {
  if (err instanceof APIError) {
    console.error(`Request failed: ${err.status} ${err.message}`);
    // err.status 401 -> check your key
    // err.status 429 -> back off and retry
  } else {
    throw err;
  }
}

Retrying with backoff

For transient errors (429 or 5xx), a simple exponential backoff retry wrapper avoids hammering the API:

async function askWithRetry(model, prompt, maxAttempts = 4) {
  let attempt = 0;
  while (true) {
    try {
      const res = await client.chat.completions.create({
        model,
        messages: [{ role: "user", content: prompt }],
      });
      return res.choices[0].message.content;
    } catch (err) {
      attempt++;
      const retryable =
        err instanceof APIError && (err.status === 429 || err.status >= 500);
      if (!retryable || attempt >= maxAttempts) throw err;
      const delay = 2 ** attempt * 200;
      await new Promise((r) => setTimeout(r, delay));
    }
  }
}

Since failed requests aren't billed, retrying costs nothing extra in credits - only time.

Common status codes

StatusLikely causeWhat to do
401Wrong or rolled keyCheck or re-copy the key on API keys
400Malformed request or unknown model idCheck the model id against /models
429Too many requests too fastRetry with backoff, as above
5xxTransient upstream issueRetry with backoff; request was not billed

Comparing cost before you commit

Since the same code runs any model, it's worth checking relative price before you pick a default. For a typical chat message (1,000 input + 500 output tokens) on apillms:

ModelPrice per 1M tokensCost per 1,000 messages
claude-sonnet-5$0.274 in / $1.37 out$2.24
gpt-5.5$0.532 in / $3.19 out$4.26
deepseek-v4-flash$0.047 in / $0.047 out$0.14
claude-opus-5-5$0.639 in / $3.19 out$4.47

For an agent-style turn (100,000 cache-read + 5,000 fresh input + 1,000 output tokens), 1,000 turns on claude-sonnet-5 cost $13.69, versus $5.88 on deepseek-v4-flash and $17.79 on gpt-5.5 - useful numbers if your Node.js service is driving a long-running agent loop rather than one-off chat messages. A live side-by-side against OpenRouter pricing is on /compare, and per-pack token estimates are on /pricing.

Credits and billing basics

1 credit equals $0.005, and credits never expire. New accounts start with 50 free credits. If you want to buy more, packs range from Starter ($5, 1,000 credits) up to Scale ($100, 23,000 credits), paid by PayPal or card. None of this requires any code change - your Node.js app keeps using the same key regardless of how credits were purchased.

Takeaway

Install openai, set baseURL to https://apillms.com/v1, and use your one apillms key. From there, switching between Claude, GPT and DeepSeek is just changing the model string - same SDK, same error handling, same streaming code, same retry logic. If you're building a Python service instead, the same approach is covered in the Python quickstart.

Try apillms

One API key for Claude, GPT, Grok, DeepSeek and Kimi. OpenAI- and Anthropic-compatible, prompt caching included, pay-as-you-go from $5 with PayPal or card. Credits never expire.

Get an API key Compare prices

FAQ

Do I need a separate SDK for Claude or DeepSeek models?

No. The standard openai npm package works for Claude, GPT and DeepSeek models through apillms's OpenAI-compatible base URL - you only change the model string.

Does apillms charge me if a request fails?

No, failed requests are not billed, so retries after an error don't cost extra credits.

Can I use the Anthropic SDK instead of the OpenAI SDK?

Yes, apillms also exposes an Anthropic-compatible base URL at https://apillms.com (no /v1) for use with Anthropic's own SDK.

How do I pick a cheaper model without rewriting my app?

Since the request shape is identical across models, you can swap the model id (for example from gpt-5.5 to deepseek-v4-flash) and compare costs on /compare before committing.