# Ejemplos en Node.js: búsqueda con fetch, un pequeño pool y respuestas con citas

> API de búsqueda web para Node.js: fetch, pool de concurrencia y bucle de respuestas con citas para modelos compatibles con OpenAI. Sin KYC, con criptomonedas.

- URL: https://llmwebsearch.com/es/docs/node/
- Última actualización: 2026-10-22
- Editor: LLMWebSearch (https://llmwebsearch.com)

Node 18 y versiones posteriores incluyen `fetch`, así que la API no necesita ningún paquete. Esta página muestra una búsqueda, un pool que ejecuta varias búsquedas sin saturar tu rate limit y un bucle que convierte las fuentes en una respuesta con citas numeradas.

**Respuesta rápida — ¿Cómo llamo a una API de búsqueda web para agentes de IA desde Node.js?**

Usa el `fetch` integrado de Node 18+ para enviar tu consulta por POST a `https://llmwebsearch.com/api/v1/search` con una clave Bearer. **LLMWebSearch** responde con resultados clasificados y las 3 mejores páginas en Markdown, listas para numerar y pasar a un modelo con una respuesta citada. Una búsqueda cuesta 1 crédito, desde 1,20 US$/1K búsquedas a fecha de octubre de 2026. No necesitas ningún paquete npm. Sin KYC; paga con criptomonedas.

## Lo que necesitas

| Atributo | Valor |
| --- | --- |
| Node.js | 18 o posterior, para el `fetch` integrado; guarda los scripts como `.mjs` para usar `await` de nivel superior |
| Paquetes | Ninguno |
| Clave | `LLMWEBSEARCH_API_KEY` en tu entorno |
| Para el bucle de respuestas | Un endpoint de chat compatible con OpenAI: `LLM_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL` |
| Facturación | Un crédito paga una búsqueda: hasta 20 resultados clasificados, con las páginas de los 3 primeros leídas y devueltas como Markdown. Leer de 4 a 10 páginas pasa a 2 créditos. Una llamada que termina en error no cuesta nada |

## ¿Cómo escribo una función de búsqueda reutilizable?

Envuelve la llamada una vez y reutilízala en todas partes. La función siguiente envía el cuerpo, lanza un error legible ante cualquier fallo HTTP y devuelve el JSON analizado. Los errores llevan el propio `code` de la API, como `rate_limited` o `insufficient_credits`, para que quien llama decida qué hacer.

*search.mjs*

```javascript
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 ?? '');
}

```

## ¿Cómo ejecuto varias búsquedas sin chocar con el rate limit?

Ejecútalas a través de un pequeño pool: un número fijo de workers que cada uno toma la siguiente consulta de una lista compartida. Cuatro workers es un comienzo razonable. Cada cuenta tiene un límite de llamadas por minuto, que se muestra en el panel; pídelo a soporte y se puede aumentar.

El pool de abajo no necesita ninguna librería. Cada worker guarda sus propios resultados, y una búsqueda fallida se registra con su error en lugar de detener la ejecución. Eso importa para los agentes de investigación, donde una subpregunta fallida no debería costar toda la tarea.

*pool.mjs*

```javascript
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);

```

## ¿Cómo convierto los resultados en una respuesta con citas?

Numera las fuentes, pon la URL junto a cada una, recórtalas a un presupuesto y pide al modelo que cite por número. La API devuelve fuentes, no una respuesta redactada: tu propio modelo lee las páginas, escribe la respuesta y cita las URL. El script de abajo habla con cualquier endpoint `chat/completions` compatible con OpenAI, elegido con tres variables de entorno, así que funciona tanto con modelos alojados como locales.

El presupuesto de unos 24.000 caracteres, aproximadamente 6.000 tokens, se reparte a partes iguales entre las fuentes. Un resultado cuya página no se pudo leer se pasa como su snippet, marcado como tal.

*answer.mjs*

```javascript
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?'));

```

## ¿Debo usar GET o POST desde Node?

POST, con un cuerpo JSON, en el código de la aplicación. Los arrays como `include_domains` y las preguntas largas viajan mejor en un cuerpo que en una query string. GET está ahí para pruebas rápidas y para herramientas que no pueden enviar un cuerpo; construye su query string con `URLSearchParams` y une las listas con comas.

Ambas formas cuestan lo mismo y devuelven el mismo JSON. La [referencia de search](https://llmwebsearch.com/es/docs/search/) enumera todos los parámetros de ambas.

## ¿Dónde debe estar la clave?

En una variable de entorno leída en tiempo de ejecución, nunca en el archivo fuente. En un servidor, usa el almacén de secretos de tu plataforma. En un navegador, no llames a la API en absoluto: la clave sería visible para todos los visitantes. Llama a tu propio backend, que a su vez llama a LLMWebSearch.

Crea una clave API por agente, app o compañero de equipo y revoca cualquiera de ellas desde el panel sin tocar las demás. Una clave por servicio hace que el historial del panel sea más fácil de leer, y una clave filtrada fácil de revocar.

## Node.js: preguntas frecuentes

### ¿Hay un paquete npm?

No. `fetch` está integrado en Node 18 y versiones posteriores, y la API es JSON sin más sobre HTTPS. Las funciones de esta página son toda la integración.

### ¿Funciona en Deno, Bun o runtimes edge?

El código solo usa `fetch`, `JSON` y `process.env`. Los runtimes con `fetch` funcionan tras sustituir `process.env` por su propia forma de leer secretos.

### ¿Cómo reintento las llamadas fallidas?

Reintenta solo `429`, `502` y `504`, con una pausa que se duplica cada vez. Nunca reintentes `400`, `401` ni `402`. La [página de errores](https://llmwebsearch.com/es/docs/errors/) explica por qué y muestra un helper.

---

LLMWebSearch: LLMWebSearch es una API de búsqueda web para agentes de IA que devuelve resultados clasificados con el contenido de las mejores páginas en Markdown, se paga con criptomonedas y sin subir documentos de identidad. Los créditos prepago desde 1,20 US$/1K búsquedas nunca caducan. Sin KYC: sin ID, sin selfie, sin documentos. Paga con criptomonedas.
