[{"data":1,"prerenderedAt":84},["ShallowReactive",2],{"guides-nodejs-uncensored-llm-api":3},{"collection":4,"slug":5,"path":6,"title":7,"seoTitle":8,"description":9,"summary":10,"primaryKeyword":11,"cluster":12,"published":13,"updated":13,"reviewed":13,"readingMinutes":14,"html":15,"toc":16,"wordCount":35,"sources":36,"faq":49,"related":59,"tested":76,"internalLinks":80},"guides","nodejs-uncensored-llm-api","\u002Fguides\u002Fnodejs-uncensored-llm-api\u002F","Call an uncensored LLM API from Node.js","Uncensored LLM API in Node.js and TypeScript","Call an uncensored or open-weight LLM from Node.js with the OpenAI SDK: environment variables, a first request, streaming with cancellation, errors and retries.","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.","uncensored llm api nodejs","quickstart","2026-11-14",2,"\u003Ch2 id=\"set-up\">Set up\u003C\u002Fh2>\n\u003Cp>You need Node.js 22 or newer for the current version of the \u003Ccode>openai\u003C\u002Fcode> package, an API key (or a local server such as llama.cpp, vLLM or Ollama), and the model id your provider expects.\u003C\u002Fp>\n\u003Cdiv class=\"code-block\" data-lang=\"bash\">\u003Cspan class=\"sr-only\">bash code\u003C\u002Fspan>\u003Cpre tabindex=\"0\">\u003Ccode class=\"language-bash\">mkdir llm-demo &amp;&amp; cd llm-demo\nnpm init -y\nnpm pkg set type=module\nnpm install openai\n\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>Set three environment variables instead of hard-coding anything. The values below are one documented example; see the \u003Ca href=\"\u002Fapi\u002F\">API overview\u003C\u002Fa> for others.\u003C\u002Fp>\n\u003Cdiv class=\"code-block\" data-lang=\"bash\">\u003Cspan class=\"sr-only\">bash code\u003C\u002Fspan>\u003Cpre tabindex=\"0\">\u003Ccode class=\"language-bash\">export LLM_BASE_URL=&quot;https:\u002F\u002Fapi.venice.ai\u002Fapi\u002Fv1&quot;\nexport LLM_API_KEY=&quot;your-key-here&quot;\nexport LLM_MODEL=&quot;venice-uncensored-1-2&quot;\n\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>With Node.js 20.6 or newer you can also keep them in a \u003Ccode>.env\u003C\u002Fcode> file that is excluded from version control and run \u003Ccode>node --env-file=.env chat.mjs\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"first-request\">First request\u003C\u002Fh2>\n\u003Cdiv class=\"code-block\" data-lang=\"javascript\">\u003Cspan class=\"sr-only\">javascript code\u003C\u002Fspan>\u003Cpre tabindex=\"0\">\u003Ccode class=\"language-javascript\">\u002F\u002F One chat completion against any OpenAI-compatible API.\n\u002F\u002F Set LLM_BASE_URL, LLM_API_KEY and LLM_MODEL in the environment first.\nimport OpenAI from 'openai'\n\nconst client = new OpenAI({\n  baseURL: process.env.LLM_BASE_URL,\n  apiKey: process.env.LLM_API_KEY,\n  timeout: 60_000, \u002F\u002F milliseconds for the whole request\n  maxRetries: 2, \u002F\u002F the SDK retries 408, 409, 429 and 5xx with backoff\n})\n\ntry {\n  const reply = await client.chat.completions.create({\n    model: process.env.LLM_MODEL,\n    messages: [\n      { role: 'system', content: 'You are a concise assistant for developers.' },\n      { role: 'user', content: 'Explain what a race condition is in two sentences.' },\n    ],\n    max_tokens: 300,\n  })\n  const choice = reply.choices[0]\n  console.log(choice.message.content)\n  console.log(`finish_reason=${choice.finish_reason}`)\n  if (reply.usage) console.log(`tokens: ${reply.usage.prompt_tokens} in, ${reply.usage.completion_tokens} out`)\n} catch (err) {\n  if (err instanceof OpenAI.APIError &amp;&amp; err.status) {\n    \u002F\u002F 401 bad key, 404 wrong base URL or model, 413 prompt too long, 429 rate limit\n    console.error(`The API returned ${err.status}: ${err.message}`)\n  } else {\n    console.error(`Request failed: ${err.message}`)\n  }\n  process.exitCode = 1\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>The client posts to \u003Ccode>&lt;base URL&gt;\u002Fchat\u002Fcompletions\u003C\u002Fcode> with a bearer token. \u003Ccode>maxRetries: 2\u003C\u002Fcode> 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. \u003Ccode>OpenAI.APIError\u003C\u002Fcode> carries the HTTP status, so the catch block can tell a bad key (401) from a rate limit (429).\u003C\u002Fp>\n\u003Cp>Check \u003Ccode>finish_reason\u003C\u002Fcode>. A value of \u003Ccode>length\u003C\u002Fcode> means the reply was cut off by \u003Ccode>max_tokens\u003C\u002Fcode> or by the provider’s own output cap, which some providers apply whatever you send.\u003C\u002Fp>\n\u003Ch2 id=\"streaming-with-a-timeout\">Streaming with a timeout\u003C\u002Fh2>\n\u003Cdiv class=\"code-block\" data-lang=\"javascript\">\u003Cspan class=\"sr-only\">javascript code\u003C\u002Fspan>\u003Cpre tabindex=\"0\">\u003Ccode class=\"language-javascript\">\u002F\u002F Stream a reply with the OpenAI SDK, with a timeout that cancels the request.\nimport OpenAI from 'openai'\n\nconst client = new OpenAI({ baseURL: process.env.LLM_BASE_URL, apiKey: process.env.LLM_API_KEY })\nconst controller = new AbortController()\nconst timer = setTimeout(() =&gt; controller.abort(), 30_000) \u002F\u002F give up after 30 s\n\nlet text = ''\nlet usage = null\ntry {\n  const stream = await client.chat.completions.create(\n    {\n      model: process.env.LLM_MODEL,\n      messages: [{ role: 'user', content: 'Write a haiku about version control.' }],\n      max_tokens: 200,\n      stream: true,\n      stream_options: { include_usage: true }, \u002F\u002F some servers ignore this\n    },\n    { signal: controller.signal },\n  )\n  for await (const chunk of stream) {\n    if (chunk.usage) usage = chunk.usage\n    const piece = chunk.choices[0]?.delta?.content\n    if (piece) {\n      text += piece\n      process.stdout.write(piece)\n    }\n  }\n  process.stdout.write('\\n')\n  if (usage) console.log(`tokens: ${usage.prompt_tokens} in, ${usage.completion_tokens} out`)\n  console.log(`characters received: ${text.length}`)\n} catch (err) {\n  console.error(controller.signal.aborted ? 'Timed out and cancelled.' : `Stream failed: ${err.message}`)\n  process.exitCode = 1\n} finally {\n  clearTimeout(timer)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\u003C\u002Fdiv>\n\u003Cp>The SDK turns the server-sent event stream into an async iterator. Passing an \u003Ccode>AbortSignal\u003C\u002Fcode> 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.\u003C\u002Fp>\n\u003Cp>\u003Ccode>stream_options: { include_usage: true }\u003C\u002Fcode> requests a final chunk with token counts. That chunk has an empty \u003Ccode>choices\u003C\u002Fcode> array, which is why the loop uses optional chaining. Some servers send usage regardless, and some never do.\u003C\u002Fp>\n\u003Ch2 id=\"without-the-sdk\">Without the SDK\u003C\u002Fh2>\n\u003Cp>If you cannot add a dependency, or you want to see what the SDK does, the request is a plain \u003Ccode>fetch\u003C\u002Fcode>. The \u003Ca href=\"\u002Fguides\u002Fstreaming-llm-responses\u002F\">streaming guide\u003C\u002Fa> shows a complete server-sent events parser that we ran against the same mock server.\u003C\u002Fp>\n\u003Ch2 id=\"errors-and-limits\">Errors and limits\u003C\u002Fh2>\n\u003Cdiv class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\">\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Symptom\u003C\u002Fth>\n\u003Cth>Likely cause\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>401\u003C\u002Ftd>\n\u003Ctd>The key is missing in this process, or wrong\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>404\u003C\u002Ftd>\n\u003Ctd>Base URL path is wrong (\u003Ccode>\u002Fv1\u003C\u002Fcode> vs \u003Ccode>\u002Fapi\u002Fv1\u003C\u002Fcode>) or the model id is not listed\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>413 or 400 about length\u003C\u002Ftd>\n\u003Ctd>Input plus reserved output exceed the context limit\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>429\u003C\u002Ftd>\n\u003Ctd>Rate limit or quota; retries help only for short bursts\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr>\n\u003Ctd>\u003Ccode>finish_reason: &quot;length&quot;\u003C\u002Fcode> with no text\u003C\u002Ftd>\n\u003Ctd>A thinking model spent its output budget on reasoning\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003C\u002Fdiv>\n\u003Cp>Context limits are set per provider and are often lower than the model’s configuration; the \u003Ca href=\"\u002Ftools\u002Fcontext-window-planner\u002F\">context window planner\u003C\u002Fa> helps you budget a request.\u003C\u002Fp>\n\u003Ch2 id=\"next-steps\">Next steps\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>Add tools: the \u003Ca href=\"\u002Fguides\u002Ftool-calling-llm-api\u002F\">tool-calling guide\u003C\u002Fa> has a complete loop in Node.js and Python.\u003C\u002Fli>\n\u003Cli>Know what is ignored: the \u003Ca href=\"\u002Fguides\u002Fopenai-compatible-api\u002F\">OpenAI compatibility guide\u003C\u002Fa> lists parameters providers accept but do not apply.\u003C\u002Fli>\n\u003Cli>Estimate the bill from real \u003Ccode>usage\u003C\u002Fcode> numbers with the \u003Ca href=\"\u002Ftools\u002Ftoken-cost-calculator\u002F\">token cost calculator\u003C\u002Fa>.\u003C\u002Fli>\n\u003C\u002Ful>\n",[17,20,23,26,29,32],{"id":18,"text":19,"level":14},"set-up","Set up",{"id":21,"text":22,"level":14},"first-request","First request",{"id":24,"text":25,"level":14},"streaming-with-a-timeout","Streaming with a timeout",{"id":27,"text":28,"level":14},"without-the-sdk","Without the SDK",{"id":30,"text":31,"level":14},"errors-and-limits","Errors and limits",{"id":33,"text":34,"level":14},"next-steps","Next steps",450,[37,41,45],{"title":38,"publisher":39,"url":40},"openai-node","GitHub (OpenAI)","https:\u002F\u002Fgithub.com\u002Fopenai\u002Fopenai-node",{"title":42,"publisher":43,"url":44},"Chat Completions API reference","OpenAI","https:\u002F\u002Fplatform.openai.com\u002Fdocs\u002Fapi-reference\u002Fchat",{"title":46,"publisher":47,"url":48},"Ollama OpenAI compatibility","Ollama","https:\u002F\u002Fdocs.ollama.com\u002Fapi\u002Fopenai-compatibility",[50,53,56],{"q":51,"a":52},"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.",{"q":54,"a":55},"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.",{"q":57,"a":58},"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.",[60,64,68,72],{"path":61,"title":62,"description":63},"\u002Fguides\u002Fpython-uncensored-llm-api\u002F","Call an uncensored LLM API from Python","Call an uncensored or open-weight LLM from Python with the OpenAI SDK: environment variables, first request, streaming, error handling, retries and provider switching.",{"path":65,"title":66,"description":67},"\u002Fguides\u002Fstreaming-llm-responses\u002F","Streaming LLM responses with server-sent events","How LLM API streaming works: the server-sent events format, parsing chunks, the final usage chunk, cancelling a stream, timeouts and errors that arrive mid-stream.",{"path":69,"title":70,"description":71},"\u002Fguides\u002Fopenai-compatible-api\u002F","OpenAI-compatible LLM APIs, and where they differ","What OpenAI-compatible means for uncensored LLM APIs: base URLs, ignored parameters, reasoning fields, usage in streams, tool calls, plus a script to probe any endpoint.",{"path":73,"title":74,"description":75},"\u002Ftools\u002Ftoken-cost-calculator\u002F","Token cost calculator","Price a workload with the formula shown.",{"summary":77,"ranOn":13,"environment":78,"liveEndpoint":79},"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.","Node.js 22.19.0, openai 7.57.0 for Node, Windows 10",false,[81,65,82,83,69,73],"\u002Fapi\u002F","\u002Ftools\u002Fcontext-window-planner\u002F","\u002Fguides\u002Ftool-calling-llm-api\u002F",1794622336928]