cloro
⬢ Node · TypeScript

The cloro API for JavaScript & Node

One typed client for Google Search, ChatGPT, Perplexity, Gemini, Copilot, and Grok — one authenticated HTTP request per call, structured JSON back. No proxies, no headless browsers, no selectors to maintain. Ships ESM + CommonJS with bundled types.

4.8 · 37 reviewsG2.com software review platform logo

Quickstart

Install the SDK, store your API key in the environment, and make your first call. The cloro SDK wraps proxies, rendering, and anti-bot logic on the server — whether you're hitting the Google SERP API or an AI engine like ChatGPT — so your Node code shrinks to one method call and a parse. Prefer Python? See the cloro API for Python.

npm install @cloro-dev/cloro

Store your key in the environment — never hardcode secrets:

export CLORO_API_KEY="sk-..."

Authenticate and make the first call:

import { Cloro } from "@cloro-dev/cloro";

// Reads your key from the CLORO_API_KEY environment variable.
const client = new Cloro();

// Google Search — structured SERP JSON, no proxies or selectors.
const serp = await client.monitor.google({ query: "best running shoes", country: "US" });
for (const item of serp.result.organicResults) {
  console.log(item.position, item.link);
}

// Any AI engine is the same client — just change the method.
const answer = await client.monitor.chatgpt({
  prompt: "What do you know about Acme Corp?",
  country: "US",
});
console.log(answer.result.text);

Every engine, one client

Google Search and every AI engine share one client and one credit pool. You add coverage by changing the method — each maps to one endpoint, shown below — not by writing a new scraper per engine.

EngineEndpointCredits
Google SearchPOST /v1/monitor/google3 +2/page · +2 with AI Overview
Google NewsPOST /v1/monitor/google/news3 +2/page
Google AI ModePOST /v1/monitor/aimode4
ChatGPTPOST /v1/monitor/chatgpt7 (full) · 5 (web search)
PerplexityPOST /v1/monitor/perplexity3
GeminiPOST /v1/monitor/gemini4
CopilotPOST /v1/monitor/copilot5
GrokPOST /v1/monitor/grok4

Base URL: https://api.cloro.dev. Full request and response schemas live in the API docs, and the TypeScript SDK reference documents every client method.

Recipe 1: rank tracker across countries

The most common production job: a nightly tracker that checks a keyword list across several markets and writes results to JSONL you can load into a warehouse or a database. It fans out 300 queries (100 keywords × 3 countries) across a capped pool of concurrent requests. For the endpoint-level detail, see the Google rank tracking API guide.

import { writeFile } from "node:fs/promises";
import { Cloro } from "@cloro-dev/cloro";

// The client reads CLORO_API_KEY and retries 429/5xx with backoff for you.
const client = new Cloro();

const KEYWORDS = [
  "best running shoes",
  "trail running shoes men",
  "waterproof running shoes",
  // ... up to 100
];
const COUNTRIES = ["US", "GB", "DE"];
const TARGET_DOMAIN = "nike.com";
const MAX_CONCURRENCY = 10; // match your plan's concurrency limit, not your CPU count

interface Row {
  date: string;
  query: string;
  country: string;
  rank: number | null;
  inAiOverview: boolean;
}

async function fetchOne(query: string, country: string): Promise<Row> {
  const res = await client.monitor.google({
    query,
    country,
    include: { aioverview: { markdown: true } },
  });
  const result = res.result;
  const hit = result.organicResults.find((item: any) =>
    (item.link ?? "").includes(TARGET_DOMAIN),
  );
  const aioSources = JSON.stringify(result.aioverview?.sources ?? []);
  return {
    date: new Date().toISOString().slice(0, 10),
    query,
    country,
    rank: hit ? hit.position : null,
    inAiOverview: aioSources.includes(TARGET_DOMAIN),
  };
}

async function runTracker(outputPath = "ranks.jsonl"): Promise<void> {
  const tasks = KEYWORDS.flatMap((kw) => COUNTRIES.map((c) => [kw, c] as const));
  const rows: Row[] = [];

  // Concurrency-capped worker pool — fan out without flooding the API.
  let cursor = 0;
  async function worker(): Promise<void> {
    while (cursor < tasks.length) {
      const [kw, c] = tasks[cursor++];
      try {
        rows.push(await fetchOne(kw, c));
        console.log(`OK  ${kw} / ${c}`);
      } catch (err) {
        console.log(`ERR ${kw} / ${c}: ${(err as Error).message}`);
      }
    }
  }
  await Promise.all(Array.from({ length: MAX_CONCURRENCY }, worker));

  await writeFile(outputPath, rows.map((r) => JSON.stringify(r)).join("\n") + "\n");
  console.log(`\nDone. ${rows.length} rows written to ${outputPath}`);
}

runTracker();

SERP calls are I/O-bound, so a small pool of concurrent promises is the right tool here. Cap MAX_CONCURRENCY at your plan's concurrency limit, and keep each returned row flat so it writes cleanly to JSONL, CSV, or a column.

Recipe 2: AI visibility across ChatGPT, Perplexity & Gemini

Rank tracking no longer stops at Google. If your brand appears in a ChatGPT answer but not a Perplexity citation, that gap is worth measuring. Each AI engine is a separate endpoint on the same credit pool — notice how little changes from the rank-tracker code above.

import { Cloro } from "@cloro-dev/cloro";

const client = new Cloro();

// Same client, same call shape — only the method changes per engine.
const ENGINES = {
  chatgpt: (prompt: string) => client.monitor.chatgpt({ prompt, country: "US" }),
  perplexity: (prompt: string) => client.monitor.perplexity({ prompt, country: "US" }),
  gemini: (prompt: string) => client.monitor.gemini({ prompt, country: "US" }),
};

const BRAND_QUERIES = [
  "best electric SUV 2026",
  "tesla model y vs competitors",
  "ev range comparison",
];
const TARGET_BRAND = "tesla.com";

async function runAiVisibilityAudit() {
  const results = [];
  for (const prompt of BRAND_QUERIES) {
    for (const [engine, call] of Object.entries(ENGINES)) {
      try {
        const { result } = await call(prompt);
        const sources = result.sources ?? [];
        const brandCited = sources.some((s: any) => (s.url ?? "").includes(TARGET_BRAND));
        results.push({ engine, prompt, brandCited, answer: (result.text ?? "").slice(0, 200) });
        const status = brandCited ? "cited" : "not cited";
        console.log(`[${engine}] ${prompt.slice(0, 40)}... -> ${status}`);
      } catch (err) {
        console.log(`ERR [${engine}] ${prompt.slice(0, 40)}...: ${(err as Error).message}`);
      }
    }
  }
  return results;
}

const rows = await runAiVisibilityAudit();
const cited = rows.filter((r) => r.brandCited).length;
console.log(`\n${cited}/${rows.length} queries cite ${TARGET_BRAND} across AI engines`);

This is the foundation of AI visibility monitoring: tracking where your brand surfaces (or doesn't) in generative answers alongside traditional search. You add engines by adding endpoints, not by writing new scrapers.

Production concerns

Five things separate a script that works once from one that runs on a schedule:

Batching high-volume work through the async task queue looks like this:

import { Cloro } from "@cloro-dev/cloro";

const client = new Cloro();

const KEYWORDS = ["best running shoes", "trail shoes", "waterproof boots"];

// Enqueue up to 500 tasks in one request, then poll each to completion.
// The queue absorbs concurrency and retries — no Semaphore to hand-tune.
const results = await client.asyncTasks.createBatch(
  KEYWORDS.map((kw) => ({ taskType: "GOOGLE", payload: { query: kw, country: "US" } })),
);

for (const item of results) {
  if (item.success) {
    const done = await client.asyncTasks.wait(item.task.id);
    const top = done.response.result.organicResults[0];
    console.log(item.task.id, "->", top.link);
  } else {
    console.log("failed:", item.error.message);
  }
}

Error handling

The SDK throws typed errors, all subclassing CloroError. Catch the ones you want to handle specially and let the transient ones (which the client already retries) bubble. They map to the underlying HTTP status codes:

StatusMeaning
200Success — parsed JSON in the body.
400Bad request — malformed payload or an invalid parameter. Not retryable; fix the request.
401Unauthorized — missing or invalid API key. Check CLORO_API_KEY.
429Rate / concurrency limit hit — back off and retry with jitter.
5xxTransient server error — retry with exponential backoff.
import {
  Cloro,
  AuthenticationError,
  BadRequestError,
  RateLimitError,
  CloroError,
} from "@cloro-dev/cloro";

const client = new Cloro();

try {
  const res = await client.monitor.google({ query: "best running shoes", country: "US" });
  // ... use res.result
} catch (err) {
  if (err instanceof AuthenticationError) {
    throw new Error("Invalid or missing CLORO_API_KEY");
  } else if (err instanceof BadRequestError) {
    throw new Error(`Bad request: ${err.message}`);
  } else if (err instanceof RateLimitError) {
    // the client already retried; back off further if this persists
  } else if (err instanceof CloroError) {
    throw err; // timeouts, 5xx, task failures — all subclass CloroError
  } else {
    throw err;
  }
}

Prefer to build it yourself?

The DIY route — fetch + a headless browser + rotating proxies — is real, and sometimes the right call for a one-off. Our How to Scrape Google Search guide walks through the direct-scrape path, including headless rendering. For anything on a schedule, the API absorbs the maintenance the DIY path never stops demanding.

Pricing that scales with you

Pick a plan that fits your volume. Price per credit drops as you scale.

Hobby
$100/mo
250,000 credits
  • $0.40 per 1000 credits
  • 20 concurrent jobs
  • Email support
Starter
$250/mo
650,000 credits
  • $0.39 per 1000 credits
  • 50 concurrent jobs
  • Email support
Growth
$500/mo
1,350,000 credits
  • $0.37 per 1000 credits
  • 75 concurrent jobs
  • Priority email support
Most Popular
Business
$1,000/mo
2,800,000 credits
  • $0.36 per 1000 credits
  • 100 concurrent jobs
  • Priority email support
Enterprise
/mo
5,871,025 credits
  • $0.34 per 1000 credits
  • 135 concurrent jobs
  • Priority support
Enterprise$5,000+

Increased concurrency, overages on credits and credit discounts for annual contracts.

Know more

Credit cost per request varies by provider. The rates below apply to async/batch requests; sync requests add a +2 credit surcharge.

ChatGPT (full response) 7 credits
ChatGPT (web search) 5 credits
Perplexity 3 credits
Grok 4 credits
Copilot 5 credits
AI Mode 4 credits
AI Overview (incl. SERP) 5 credits
Gemini 4 credits
Google Search 3 credits +2/page
Google News 3 credits +2/page

ChatGPT full response includes query fan-out, ads, and shopping data. Google News uses the same pricing as Google Search.

Estimate your monthly cost and plan

7 credits each
5 credits each
3 credits each
4 credits each
5 credits each
4 credits each
4 credits each
3 credits / 1 page
3 credits / 1 page
Monthly requests
0
Credits needed
0
Recommended plan:

One client, one credit pool for Google Search and every AI engine. See how per-call SERP costs stack up in the cheapest SERP APIs breakdown, or measure your brand across generative answers with AI visibility tracking.

Frequently Asked Questions

What is a SERP API for Node.js?+

A SERP API is a hosted service you call from Node or the browser that returns structured JSON for a search query. Your code sends a query with a Bearer token; the API handles proxies, rendering, and anti-bot logic, so you get parsed organic results, AI Overviews, and positions back without maintaining selectors or a headless browser.

How do I call a SERP API from JavaScript or TypeScript?+

Install the SDK (npm install @cloro-dev/cloro), store your key in the CLORO_API_KEY environment variable, then call client.monitor.google({ query }) — you get parsed JSON back, fully typed. The quickstart above is a complete first call you can paste and run. Prefer raw HTTP? Every method maps to one authenticated POST you can hit with fetch.

Does the SDK ship TypeScript types?+

Yes. npm install @cloro-dev/cloro gives you a typed client with bundled .d.ts declarations, and both ESM and CommonJS builds, so it works in TypeScript, modern ESM projects, and older require()-based Node. It runs on Node 18+ using the built-in fetch — no extra HTTP dependency to install.

Can I query ChatGPT, Perplexity, and Gemini from the same Node client?+

Yes. One client covers them all — client.monitor.chatgpt, .perplexity, .gemini, .copilot, and .grok — on the same credit pool. The call shape is identical across engines; only the method changes, so one client covers Google and every AI engine.

How do I fan out hundreds of queries without hitting rate limits?+

Cap concurrency to your plan's limit with a simple worker pool (the rank-tracker recipe above shows one), or for large batches use the async task queue — client.asyncTasks.createBatch enqueues up to 500 tasks and the queue absorbs concurrency and retries for you. The client also retries 429 and 5xx responses with exponential backoff automatically.

How much does the cloro API cost from Node?+

cloro bills a single credit pool: each engine has a fixed per-call credit cost and your plan sets the monthly credit allowance. See the pricing page for current plan rates (it has a calculator to size your volume), and the cheapest SERP API comparison for how per-call costs stack up against other providers.

Start building with cloro in Node today

New accounts get 500 free credits — enough to run either recipe, SERP or AI-engine, end to end before you pick a plan.