# SERP API en Node.js: fetch, un pool de workers y paginación

> Llama a la API SERP de SerpCrypto desde Node.js 18+ con fetch integrado, cliente tipado, pool de workers y bucle de paginación.

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

Sin dependencias: Node.js incluye `fetch` desde la versión 18. Un cliente pequeño con tipos JSDoc para editores y TypeScript, un pool que respeta el límite de tu cuenta y un bucle de paginación que se detiene cuando se acaban los resultados.

**Respuesta rápida — ¿Cómo uso una API SERP en Node.js?**

Llama a `fetch("https://serpcrypto.com/api/v1/search?" + new URLSearchParams({ q }))` con el header `Authorization: Bearer <key>` y lee `await res.json()`. Node.js 18+ no necesita ningún paquete. **SerpCrypto** devuelve resultados de Google o Bing en JSON por 1 crédito por búsqueda exitosa, desde 0,30 US$/1K búsquedas, y nunca cobra los errores. Se paga con criptomonedas, sin KYC.

## Configuración de Node.js de un vistazo

| Atributo | Valor |
| --- | --- |
| Entorno de ejecución | Node.js 18 o superior (`fetch`, `AbortSignal.timeout` integrados) |
| Módulos | Módulos ES: archivos `.mjs` o `"type": "module"` en package.json |
| Clave | `process.env.SERPCRYPTO_API_KEY` |
| Tiempo de espera | 90 segundos con `AbortSignal.timeout(90_000)` |
| Búsquedas en paralelo | Un pool dimensionado a `concurrency_limit` |
| Tipos | typedefs de JSDoc, leídos por VS Code y `tsc --checkJs` |

## ¿Cómo escribo un cliente tipado pequeño?

Pon el endpoint, el header y el manejo de errores en un solo módulo. Las líneas `@typedef` de JSDoc describen la respuesta, así los editores autocompletan `organic_results` y comprueban los tipos de tu código sin un paso de build. Los usuarios de TypeScript pueden copiar las mismas formas en una interface.

La función `search` lanza un `Error` con el estado HTTP y el `code` de error de la API en el mensaje. Tu código puede entonces decidir reintentar `429`, `502` y `504` y detenerse en los demás, como explica la [página de errores](https://serpcrypto.com/es/docs/errors/)].

*serp-client.mjs*

```javascript
// serp-client.mjs — Node.js 18+, no dependency
const API_URL = "https://serpcrypto.com/api/v1/search";
const CREDITS_URL = "https://serpcrypto.com/api/v1/credits";
const headers = { Authorization: `Bearer ${process.env.SERPCRYPTO_API_KEY}` };

/**
 * @typedef {Object} OrganicResult
 * @property {number} position
 * @property {string} title
 * @property {string} link
 * @property {string} [snippet]
 * @property {string} [date]
 */

/**
 * @typedef {Object} SearchResponse
 * @property {Object} search_metadata
 * @property {string} search_metadata.id
 * @property {string} search_metadata.status
 * @property {number} search_metadata.credits_used
 * @property {OrganicResult[]} [organic_results]
 * @property {Object} [pagination]
 * @property {number} pagination.current
 * @property {number} [pagination.next_page]
 */

/**
 * Run one search (1 credit when it succeeds).
 * @param {string} q
 * @param {Record<string, string | number>} [params]
 * @returns {Promise<SearchResponse>}
 */
export async function search(q, params = {}) {
  const query = new URLSearchParams({ q, ...Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)])) });
  const res = await fetch(`${API_URL}?${query}`, { headers, signal: AbortSignal.timeout(90_000) });
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    const err = new Error(`${res.status} ${body.error?.code ?? "error"}: ${body.error?.message ?? ""}`);
    err.status = res.status;
    throw err;
  }
  return res.json();
}

/** Free: credits left, used today and the concurrency limit. */
export async function credits() {
  const res = await fetch(CREDITS_URL, { headers });
  if (!res.ok) throw new Error(`credits: HTTP ${res.status}`);
  return res.json();
}
```

## ¿Cómo ejecuto búsquedas en paralelo sin recibir un 429?

Arranca tantos workers como permita tu cuenta y deja que cada uno tome la siguiente keyword de una lista compartida. El pool lee `concurrency_limit` del endpoint de créditos gratuitos, así nunca envía más búsquedas a la vez de las que acepta la cuenta.

Una keyword fallida se registra y el worker continúa. Vuelve a ejecutar los fallos después: los errores no cuestan nada, así que una segunda pasada es gratis hasta que lo consiga.

*batch.mjs: un pool de workers*

```javascript
// batch.mjs — parallel searches, never more than the account's limit at once
import { search, credits } from "./serp-client.mjs";

const keywords = ["password manager", "note taking app", "time tracking software", "invoice software"];
const { concurrency_limit: limit } = await credits();

let next = 0;
const results = new Map();

async function worker() {
  while (next < keywords.length) {
    const kw = keywords[next++];
    try {
      const data = await search(kw, { gl: "us", hl: "en" });
      results.set(kw, data.organic_results?.[0]?.link ?? null);
    } catch (err) {
      results.set(kw, `failed: ${err.message}`);
    }
  }
}

await Promise.all(Array.from({ length: Math.min(limit, keywords.length) }, worker));
console.table(Object.fromEntries(results));
```

## ¿Cómo obtengo varias páginas de resultados?

Recorre `page` y deténte en cuanto falte `pagination.next_page`. Unos 10 resultados orgánicos por página, tal como Google los sirve desde que dejó de respetar num=100 en septiembre de 2025; cada página adicional es un crédito más. Una comprobación de tres páginas de una keyword cuesta tres créditos; una keyword con solo una página de resultados cuesta uno.

*pages.mjs*

```javascript
// pages.mjs — up to three pages, stop when results end
import { search } from "./serp-client.mjs";

async function topResults(q, pages = 3, params = {}) {
  const rows = [];
  for (let page = 1; page <= pages; page++) {
    const data = await search(q, { ...params, page });
    for (const r of data.organic_results ?? []) rows.push({ page, position: r.position, link: r.link });
    if (!data.pagination?.next_page) break; // nothing more: save the credits
  }
  return rows;
}

console.log(await topResults("headless cms", 3, { gl: "de", hl: "en" }));
```

## ¿Puedo llamar a la API desde un navegador o una edge function?

Desde un servidor o una edge function, sí: Cloudflare Workers, Vercel y Deno ofrecen el mismo `fetch`. Desde una página de navegador, no: la clave sería visible para cada visitante. Pon una pequeña ruta de servidor en medio que guarde la clave y reenvíe solo los parámetros que permitas.

La misma regla se aplica a las apps móviles. Todo lo que se envía al dispositivo de un usuario se puede leer, así que las claves se quedan en tus servidores.

## ¿Cómo mantengo los costos predecibles en Node.js?

Cuenta las llamadas que haces: una llamada exitosa es un crédito, a 0,56 US$/1K búsquedas en el pack 100.000 búsquedas. Consulta `credits_remaining` antes de un lote grande y detén el pool cuando se acerque a cero en lugar de acumular una serie de respuestas `402`. Los créditos de búsqueda prepagados no caducan nunca: las consultas sin usar se quedan en tu cuenta hasta que las uses, así que el remanente sin usar se guarda para el siguiente trabajo.

## Preguntas sobre Node.js

### ¿Hay un paquete npm para SerpCrypto?

No. El cliente de arriba es de unas 40 líneas de JavaScript estándar usando `fetch`, así que no hay nada nuestro que instalar. Cópialo en tu proyecto y adáptalo.

### ¿Funciona con CommonJS (require)?

Los ejemplos usan módulos ES para `await` de nivel superior. En un archivo CommonJS, envuelve las llamadas en una función `async` y usa `fetch` del mismo modo; es global en Node.js 18+.

### ¿Cómo lo uso con TypeScript?

Copia las formas de JSDoc en una `interface SearchResponse` y tipa con ella el resultado de `res.json()`. Los bloques opcionales como `organic_results?` deben seguir siendo opcionales, porque una página puede no mostrarlos.

### ¿Qué versión de Node.js necesito?

Node.js 18 o posterior para el `fetch` integrado. `AbortSignal.timeout` está disponible desde Node.js 17.3, así que 18 cubre todo en esta página.

---

SerpCrypto: SerpCrypto es una API SERP para desarrolladores y agentes de IA que devuelve resultados de Google y Bing como JSON, pagada con criptomonedas sin subir ningún documento de identidad. Los créditos de búsqueda prepagados desde 0,30 US$/1K búsquedas nunca caducan. Sin KYC: ni identificación, ni selfie, ni documentos. Paga con criptomonedas.
