Developer guide
Call an uncensored LLM API from Node.js
Install the openai package, read the base URL, key and model from the environment, and call client.chat.completions.create. Streaming works with for await, and an AbortController cancels a slow request. The same code runs against hosted providers and local OpenAI-compatible servers by changing three environment variables.
Test status, 14 November 2026: Every Node.js sample on this page was run unchanged against the mock OpenAI-compatible server in this site's repository, including two 429 responses that the SDK retried and a missing key that the code reported. Environment: Node.js 22.19.0, openai 7.57.0 for Node, Windows 10. Not yet run against a live provider endpoint.
Set up
You need Node.js 22 or newer for the current version of the openai package, an API key (or a local server such as llama.cpp, vLLM or Ollama), and the model id your provider expects.
mkdir llm-demo && cd llm-demo
npm init -y
npm pkg set type=module
npm install openai
Set three environment variables instead of hard-coding anything. The values below are one documented example; see the API overview for others.
export LLM_BASE_URL="https://api.venice.ai/api/v1"
export LLM_API_KEY="your-key-here"
export LLM_MODEL="venice-uncensored-1-2"
With Node.js 20.6 or newer you can also keep them in a .env file that is excluded from version control and run node --env-file=.env chat.mjs.
First request
// One chat completion against any OpenAI-compatible API.
// Set LLM_BASE_URL, LLM_API_KEY and LLM_MODEL in the environment first.
import OpenAI from 'openai'
const client = new OpenAI({
baseURL: process.env.LLM_BASE_URL,
apiKey: process.env.LLM_API_KEY,
timeout: 60_000, // milliseconds for the whole request
maxRetries: 2, // the SDK retries 408, 409, 429 and 5xx with backoff
})
try {
const reply = await client.chat.completions.create({
model: process.env.LLM_MODEL,
messages: [
{ role: 'system', content: 'You are a concise assistant for developers.' },
{ role: 'user', content: 'Explain what a race condition is in two sentences.' },
],
max_tokens: 300,
})
const choice = reply.choices[0]
console.log(choice.message.content)
console.log(`finish_reason=${choice.finish_reason}`)
if (reply.usage) console.log(`tokens: ${reply.usage.prompt_tokens} in, ${reply.usage.completion_tokens} out`)
} catch (err) {
if (err instanceof OpenAI.APIError && err.status) {
// 401 bad key, 404 wrong base URL or model, 413 prompt too long, 429 rate limit
console.error(`The API returned ${err.status}: ${err.message}`)
} else {
console.error(`Request failed: ${err.message}`)
}
process.exitCode = 1
}
The client posts to <base URL>/chat/completions with a bearer token. maxRetries: 2 makes the SDK retry connection errors, 408, 409, 429 and server errors with a short backoff; in our test, two 429 responses were retried and the third request succeeded. OpenAI.APIError carries the HTTP status, so the catch block can tell a bad key (401) from a rate limit (429).
Check finish_reason. A value of length means the reply was cut off by max_tokens or by the provider’s own output cap, which some providers apply whatever you send.
Streaming with a timeout
// Stream a reply with the OpenAI SDK, with a timeout that cancels the request.
import OpenAI from 'openai'
const client = new OpenAI({ baseURL: process.env.LLM_BASE_URL, apiKey: process.env.LLM_API_KEY })
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), 30_000) // give up after 30 s
let text = ''
let usage = null
try {
const stream = await client.chat.completions.create(
{
model: process.env.LLM_MODEL,
messages: [{ role: 'user', content: 'Write a haiku about version control.' }],
max_tokens: 200,
stream: true,
stream_options: { include_usage: true }, // some servers ignore this
},
{ signal: controller.signal },
)
for await (const chunk of stream) {
if (chunk.usage) usage = chunk.usage
const piece = chunk.choices[0]?.delta?.content
if (piece) {
text += piece
process.stdout.write(piece)
}
}
process.stdout.write('\n')
if (usage) console.log(`tokens: ${usage.prompt_tokens} in, ${usage.completion_tokens} out`)
console.log(`characters received: ${text.length}`)
} catch (err) {
console.error(controller.signal.aborted ? 'Timed out and cancelled.' : `Stream failed: ${err.message}`)
process.exitCode = 1
} finally {
clearTimeout(timer)
}
The SDK turns the server-sent event stream into an async iterator. Passing an AbortSignal lets you cancel: when the timer fires, the request is aborted, the loop throws, and the connection closes, so you stop paying for tokens you will not use. The same pattern works for a “stop generating” button.
stream_options: { include_usage: true } requests a final chunk with token counts. That chunk has an empty choices array, which is why the loop uses optional chaining. Some servers send usage regardless, and some never do.
Without the SDK
If you cannot add a dependency, or you want to see what the SDK does, the request is a plain fetch. The streaming guide shows a complete server-sent events parser that we ran against the same mock server.
Errors and limits
| Symptom | Likely cause |
|---|---|
| 401 | The key is missing in this process, or wrong |
| 404 | Base URL path is wrong (/v1 vs /api/v1) or the model id is not listed |
| 413 or 400 about length | Input plus reserved output exceed the context limit |
| 429 | Rate limit or quota; retries help only for short bursts |
finish_reason: "length" with no text |
A thinking model spent its output budget on reasoning |
Context limits are set per provider and are often lower than the model’s configuration; the context window planner helps you budget a request.
Next steps
- Add tools: the tool-calling guide has a complete loop in Node.js and Python.
- Know what is ignored: the OpenAI compatibility guide lists parameters providers accept but do not apply.
- Estimate the bill from real
usagenumbers with the token cost calculator.
Questions
Can I call the API from the browser?
Not with your own key: anything in browser code is visible to users. Call the provider from your server and send only the result to the browser.
Does this work with TypeScript?
Yes. The openai package ships its own types, so the same code works in a .ts file; the request and response objects are typed.
Which Node.js version do I need?
The current major version of the openai package declares Node.js 22 or newer. The fetch-based streaming example needs only Node.js 18 or newer.
Sources
- openai-node, GitHub (OpenAI)
- Chat Completions API reference, OpenAI
- Ollama OpenAI compatibility, Ollama
Found an error? Report it. We log material corrections publicly.