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
| Status | Likely cause | What to do |
|---|---|---|
| 401 | Wrong or rolled key | Check or re-copy the key on API keys |
| 400 | Malformed request or unknown model id | Check the model id against /models |
| 429 | Too many requests too fast | Retry with backoff, as above |
| 5xx | Transient upstream issue | Retry 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:
| Model | Price per 1M tokens | Cost 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.
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.