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
| 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].
// 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 — 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 — 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.