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 18+ · fetch · ESM

SERP API in Node.js: fetch, a worker pool and pagination

No dependency: Node.js has had fetch since version 18. A small client with JSDoc types for editors and TypeScript, a pool that respects your account's limit, and a pagination loop that stops when the results run out.

Last updated: · 5 min read

Quick answer

How do I use a SERP API in Node.js?

Call fetch("https://serpcrypto.com/api/v1/search?" + new URLSearchParams({ q })) with the header Authorization: Bearer <key> and read await res.json(). Node.js 18+ needs no package. SerpCrypto returns Google or Bing results as JSON for 1 credit per successful search, from $0.30/1K searches, and never charges errors. Paid in crypto, no KYC.

Node.js setup at a glance

RuntimeNode.js 18 or later (built-in fetch, AbortSignal.timeout)
ModulesES modules: .mjs files or "type": "module" in package.json
Keyprocess.env.SERPCRYPTO_API_KEY
Timeout90 seconds with AbortSignal.timeout(90_000)
Parallel searchesA pool sized to concurrency_limit
TypesJSDoc typedefs, read by VS Code and tsc --checkJs

How do I write a small typed client?

Put the endpoint, the header and the error handling in one module. The JSDoc @typedef lines describe the answer, so editors autocomplete organic_results and type-check your code without a build step. TypeScript users can copy the same shapes into an interface.

The search function throws an Error with the HTTP status and the API's error code in its message. Your code can then decide to retry 429, 502 and 504 and to stop on the others, as the errors page explains.

serp-client.mjs
// 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();
}

How do I run searches in parallel without hitting 429?

Start as many workers as your account allows, and let each one take the next keyword from a shared list. The pool reads concurrency_limit from the free credits endpoint, so it never sends more searches at once than the account accepts.

A failed keyword is recorded and the worker moves on. Re-run the failures afterwards: errors cost nothing, so a second pass is free until it succeeds.

batch.mjs: a pool of workers
// 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));

How do I fetch several pages of results?

Loop over page and stop as soon as pagination.next_page is missing. About 10 organic results per page, as Google has served since it stopped honouring num=100 in September 2025; each extra page is one more credit. A three-page check of one keyword costs three credits; a keyword with only one page of results costs one.

pages.mjs
// 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" }));

Can I call the API from a browser or an edge function?

From a server or an edge function, yes: Cloudflare Workers, Vercel and Deno all provide the same fetch. From a browser page, no: the key would be visible to every visitor. Put a small server route in between that holds the key and forwards only the parameters you allow.

The same rule applies to mobile apps. Anything shipped to a user's device can be read, so keys stay on your servers.

How do I keep costs predictable in Node.js?

Count the calls you make: one successful call is one credit, at $0.56/1K searches on the 100,000 searches pack. Check credits_remaining before a large batch and stop the pool when it gets close to zero rather than collecting a series of 402 answers. Prepaid search credits never expire: unused searches stay on your account until you use them, so an unused remainder is kept for the next job.

Node.js questions

Is there an npm package for SerpCrypto?

No. The client above is about 40 lines of standard JavaScript using fetch, so there is nothing of ours to install. Copy it into your project and adapt it.

Does this work with CommonJS (require)?

The examples use ES modules for top-level await. In a CommonJS file, wrap the calls in an async function and use fetch the same way; it is global in Node.js 18+.

How do I use it with TypeScript?

Copy the JSDoc shapes into an interface SearchResponse and type the result of res.json() with it. Optional blocks such as organic_results? must stay optional, because a page may not show them.

Which Node.js version do I need?

Node.js 18 or later for the built-in fetch. AbortSignal.timeout is available from Node.js 17.3, so 18 covers everything on this page.

From $0.30/1K searches · no KYC

Get an API key