No KYC — no ID, no selfie, no documents. Pay in crypto: BTC, USDT, USDC, ETH, SOL, LTC
Language: English
Get an API key

Docs · Node.js

Node.js examples: search with fetch, a small pool and cited answers

Node 18 and newer ship fetch, so the API needs no package at all. This page shows one search, a pool that runs several searches without flooding your rate limit, and a loop that turns sources into an answer with numbered citations.

Last updated: · 5 min read

Quick answer

How do I call a web search API for AI agents from Node.js?

Use the built-in fetch of Node 18+ to POST your query to https://llmwebsearch.com/api/v1/search with a Bearer key. LLMWebSearch answers with ranked results and the top 3 pages as Markdown, ready to number and pass to a model for a cited answer. One search costs 1 credit, from $1.20/1K searches as of October 2026. No npm package needed. No KYC; pay in crypto.

What you need

Node.js18 or newer, for the built-in fetch; save the scripts as .mjs for top-level await
PackagesNone
KeyLLMWEBSEARCH_API_KEY in your environment
For the answer loopAn OpenAI-compatible chat endpoint: LLM_BASE_URL, LLM_API_KEY, LLM_MODEL
BillingOne credit pays for one search: up to 20 ranked results, with the pages of the first 3 read and returned as Markdown. Reading 4 to 10 pages makes it 2 credits. A call that ends in an error costs nothing

How do I write a reusable search function?

Wrap the call once and reuse it everywhere. The function below sends the body, throws a readable error on any HTTP failure, and returns the parsed JSON. Errors carry the API's own code, such as rate_limited or insufficient_credits, so the caller can decide what to do.

search.mjs
const API = 'https://llmwebsearch.com/api/v1/search';

export async function search(body) {
  const res = await fetch(API, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.LLMWEBSEARCH_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });
  const data = await res.json();
  if (!res.ok) {
    const err = new Error(`${res.status} ${data.error?.code}: ${data.error?.message}`);
    err.status = res.status;
    throw err;
  }
  return data;
}

const data = await search({ q: 'how to size a postgres connection pool', read: 3 });
for (const r of data.results) {
  console.log(r.position, r.content_status, r.url, r.word_count ?? '');
}

How do I run several searches without hitting the rate limit?

Run them through a small pool: a fixed number of workers that each take the next query from a shared list. Four workers is a sensible start. Every account has a per-minute call limit, shown in the dashboard; ask support and it can be raised.

The pool below needs no library. Each worker keeps its own results, and a failed search is recorded with its error instead of stopping the run. That matters for research agents, where one bad sub-question should not cost the whole task.

pool.mjs
const API = 'https://llmwebsearch.com/api/v1/search';
const questions = [
  'what is connection pooling in postgres',
  'recommended pool size per cpu core',
  'pgbouncer transaction mode limitations',
  'connection pool exhaustion symptoms',
  'pool size for serverless functions',
];

async function search(q) {
  const res = await fetch(API, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.LLMWEBSEARCH_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ q, read: 2, max_chars: 4000 }),
  });
  const data = await res.json();
  if (!res.ok) return { q, error: data.error?.code ?? String(res.status) };
  return { q, urls: data.results.filter((r) => r.content_status === 'ok').map((r) => r.url) };
}

async function runPool(items, size, task) {
  const out = [];
  let next = 0;
  const worker = async () => {
    while (next < items.length) {
      const i = next++;
      out[i] = await task(items[i]);
    }
  };
  await Promise.all(Array.from({ length: size }, worker));
  return out;
}

for (const row of await runPool(questions, 4, search)) console.log(row);

How do I turn results into an answer with citations?

Number the sources, put the URL next to each, trim them to a budget, and ask the model to cite by number. The API returns sources, not a written answer: your own model reads the pages, writes the reply and cites the URLs. The script below talks to any OpenAI-compatible chat/completions endpoint, chosen with three environment variables, so it works with hosted and local models alike.

The budget of about 24,000 characters, roughly 6,000 tokens, is split evenly between the sources. A result whose page could not be read is passed as its snippet, marked as such.

answer.mjs
const SEARCH = 'https://llmwebsearch.com/api/v1/search';
const BUDGET = 24000; // characters, about 6,000 tokens

async function sources(question) {
  const res = await fetch(SEARCH, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.LLMWEBSEARCH_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ q: question, max_results: 5, read: 3 }),
  });
  const { results } = await res.json();
  const share = Math.floor(BUDGET / Math.max(results.length, 1));
  return results
    .map((r, i) => {
      const body = r.content_status === 'ok' ? r.content.slice(0, share) : `(snippet only) ${r.snippet}`;
      return `[${i + 1}] ${r.title} - ${r.url}\n${body}`;
    })
    .join('\n\n');
}

async function answer(question) {
  const prompt = 'Answer using only the sources below. Cite them as [1], [2] and so on. '
    + 'Say so if they do not answer the question.\n\n' + (await sources(question)) + '\n\nQuestion: ' + question;
  const res = await fetch(`${process.env.LLM_BASE_URL.replace(/\/$/, '')}/chat/completions`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.LLM_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ model: process.env.LLM_MODEL, messages: [{ role: 'user', content: prompt }] }),
  });
  const data = await res.json();
  return data.choices[0].message.content;
}

console.log(await answer('How should I size a Postgres connection pool?'));

Should I use GET or POST from Node?

POST, with a JSON body, in application code. Arrays such as include_domains and long questions travel more cleanly in a body than in a query string. GET is there for quick tests and for tools that cannot send a body; build its query string with URLSearchParams and join lists with commas.

Both forms cost the same and return the same JSON. The search reference lists every parameter for both.

Where should the key live?

In an environment variable read at runtime, never in the source file. On a server, use your platform's secret store. In a browser, do not call the API at all: the key would be visible to every visitor. Call your own backend, which then calls LLMWebSearch.

Make one API key per agent, app or teammate and revoke any of them from the dashboard without touching the others. One key per service makes the dashboard history easier to read, and a leaked key easy to revoke.

Node.js: common questions

Is there an npm package?

No. fetch is built into Node 18 and newer, and the API is plain JSON over HTTPS. The functions on this page are the whole integration.

Does it work on Deno, Bun or edge runtimes?

The code only uses fetch, JSON and process.env. Runtimes with fetch work after replacing process.env with their own way of reading secrets.

How do I retry failed calls?

Retry only 429, 502 and 504, with a pause that doubles each time. Never retry 400, 401 or 402. The errors page explains why and shows a helper.

From $1.20/1K searches · no KYC

Get an API key