Risposta rapida
Come faccio a usare una SERP API in Node.js?
Chiama fetch("https://serpcrypto.com/api/v1/search?" + new URLSearchParams({ q })) con l'header Authorization: Bearer <key> e leggi await res.json(). Node.js 18+ non richiede pacchetti. SerpCrypto restituisce i risultati di Google o Bing in JSON per 1 credito per ricerca riuscita, da 0,30 USD/1K ricerche, e non addebita mai gli errori. Paghi in criptovalute, senza KYC.
Setup di Node.js in breve
| Runtime | Node.js 18 o successivo (integrato fetch, AbortSignal.timeout) |
|---|---|
| Moduli | Moduli ES: file .mjs o "type": "module" in package.json |
| Chiave | process.env.SERPCRYPTO_API_KEY |
| Timeout | 90 secondi con AbortSignal.timeout(90_000) |
| Ricerche in parallelo | Un pool dimensionato a concurrency_limit |
| Tipi | Typedef JSDoc, letti da VS Code e tsc --checkJs |
Come scrivo un piccolo client tipizzato?
Metti l'endpoint, l'header e la gestione degli errori in un unico modulo. Le righe JSDoc @typedef descrivono la risposta, così gli editor completano organic_results e controllano i tipi del tuo codice senza un passo di build. Chi usa TypeScript può copiare le stesse forme in un'interfaccia.
La funzione search lancia un Error con lo status HTTP e l'code di errore dell'API nel messaggio. Il tuo codice può quindi decidere di riprovare su 429, 502 e 504 e di fermarsi sugli altri, come spiega la pagina degli errori.
// 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();
}Come eseguo ricerche in parallelo senza incappare nel 429?
Avvia tutti i worker che il tuo account consente e lascia che ognuno prenda la keyword successiva da una lista condivisa. Il pool legge concurrency_limit dall'endpoint dei crediti gratuiti, così non invia mai più ricerche contemporaneamente di quante l'account accetta.
Una keyword fallita viene registrata e il worker passa oltre. Riesegui poi i fallimenti: gli errori non costano nulla, quindi un secondo passaggio è gratis finché non riesce.
// 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));Come recupero più pagine di risultati?
Itera su page e fermati appena pagination.next_page manca. Circa 10 risultati organici per pagina, come Google serve da quando ha smesso di rispettare num=100 a settembre 2025; ogni pagina in più costa un altro credito. Un controllo su tre pagine di una keyword costa tre crediti; una keyword con una sola pagina di risultati costa 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" }));Posso chiamare l'API da un browser o da una edge function?
Da un server o da una edge function, sì: Cloudflare Workers, Vercel e Deno forniscono tutti lo stesso fetch. Da una pagina del browser, no: la chiave sarebbe visibile a ogni visitatore. Metti in mezzo una piccola route server che custodisce la chiave e inoltra solo i parametri che consenti.
Vale la stessa regola per le app mobile. Tutto ciò che arriva sul dispositivo di un utente può essere letto, quindi le chiavi restano sui tuoi server.
Come mantengo i costi prevedibili in Node.js?
Conta le chiamate che fai: una chiamata riuscita è un credito, a 0,56 USD/1K ricerche sul pack 100.000 ricerche. Controlla credits_remaining prima di un grande batch e ferma il pool quando si avvicina a zero invece di raccogliere una serie di risposte 402. I crediti di ricerca prepagati non scadono mai: le ricerche non utilizzate restano sul tuo account finché non le usi, quindi il resto non usato resta per il lavoro successivo.
Domande su Node.js
Esiste un pacchetto npm per SerpCrypto?
No. Il client qui sopra è circa 40 righe di JavaScript standard che usano fetch, quindi non c'è nulla di nostro da installare. Copialo nel tuo progetto e adattalo.
Funziona con CommonJS (require)?
Gli esempi usano i moduli ES per l'await di primo livello. In un file CommonJS, avvolgi le chiamate in una funzione async e usa fetch allo stesso modo; è globale in Node.js 18+.
Come lo uso con TypeScript?
Copia le forme JSDoc in un'interface SearchResponse e tipizza con essa il risultato di res.json(). I blocchi opzionali come organic_results? devono restare opzionali, perché una pagina potrebbe non mostrarli.
Quale versione di Node.js mi serve?
Node.js 18 o successivo per l'fetch integrato. AbortSignal.timeout è disponibile da Node.js 17.3, quindi la 18 copre tutto ciò che c'è in questa pagina.