cloro
🐍 Python

The cloro API for Python

One Python 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.

4.8 · 42 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 Python code shrinks to one method call and a parse. Working in Node or TypeScript? See the cloro API for JavaScript & Node.

pip install cloro

Store your key in the environment — never hardcode secrets:

export CLORO_API_KEY="sk-..."

Authenticate and make the first call:

from cloro import Cloro

# Reads your key from the CLORO_API_KEY environment variable.
client = Cloro()

# Google Search — structured SERP JSON, no proxies or selectors.
serp = client.monitor.google(query="best running shoes", country="US")
for item in serp["result"]["organicResults"]:
    print(item["position"], item["link"])

# Any AI engine is the same client — just change the method.
answer = client.monitor.chatgpt(
    prompt="What do you know about Acme Corp?",
    country="US",
)
print(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 Python 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 pandas, BigQuery, or a database. It fans out 300 queries (100 keywords × 3 countries) across a capped pool of workers. For the endpoint-level detail, see the Google rank tracking API guide.

import json
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import date

from cloro import Cloro

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

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


def fetch_one(query: str, country: str) -> dict:
    res = client.monitor.google(
        query=query,
        country=country,
        include={"aioverview": {"markdown": True}},
    )
    result = res["result"]
    positions = [
        item["position"]
        for item in result["organicResults"]
        if TARGET_DOMAIN in item.get("link", "")
    ]
    aio_sources = str(result.get("aioverview", {}).get("sources", []))
    return {
        "date": str(date.today()),
        "query": query,
        "country": country,
        "rank": positions[0] if positions else None,
        "in_ai_overview": TARGET_DOMAIN in aio_sources,
    }


def run_tracker(output_path: str = "ranks.jsonl") -> None:
    tasks = [(kw, c) for kw in KEYWORDS for c in COUNTRIES]
    rows: list[dict] = []

    with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
        futures = {pool.submit(fetch_one, kw, c): (kw, c) for kw, c in tasks}
        for future in as_completed(futures):
            kw, c = futures[future]
            try:
                rows.append(future.result())
                print(f"OK  {kw} / {c}")
            except Exception as exc:
                print(f"ERR {kw} / {c}: {exc}")

    with open(output_path, "w") as f:
        for row in rows:
            f.write(json.dumps(row) + "\n")

    print(f"\nDone. {len(rows)} rows written to {output_path}")


if __name__ == "__main__":
    run_tracker()

SERP calls are I/O-bound, so threads are the right tool here — not multiprocessing. Cap MAX_WORKERS 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.

from cloro import Cloro

client = Cloro()

# Same client, same call shape — only the method changes per engine.
ENGINES = {
    "chatgpt": client.monitor.chatgpt,
    "perplexity": client.monitor.perplexity,
    "gemini": client.monitor.gemini,
}

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


def check_ai_visibility(prompt: str, engine: str, call) -> dict:
    result = call(prompt=prompt, country="US")["result"]
    sources = result.get("sources", [])
    return {
        "engine": engine,
        "prompt": prompt,
        "brand_cited": any(TARGET_BRAND in (s.get("url") or "") for s in sources),
        "answer_snippet": result.get("text", "")[:200],
    }


def run_ai_visibility_audit() -> list[dict]:
    results = []
    for prompt in BRAND_QUERIES:
        for engine, call in ENGINES.items():
            try:
                row = check_ai_visibility(prompt, engine, call)
                results.append(row)
                status = "cited" if row["brand_cited"] else "not cited"
                print(f"[{engine}] {prompt[:40]}... -> {status}")
            except Exception as exc:
                print(f"ERR [{engine}] {prompt[:40]}...: {exc}")
    return results


if __name__ == "__main__":
    rows = run_ai_visibility_audit()
    cited = sum(1 for r in rows if r["brand_cited"])
    print(f"\n{cited}/{len(rows)} 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:

from cloro import Cloro

client = Cloro()

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.
results = client.async_tasks.create_batch(
    [{"task_type": "GOOGLE", "payload": {"query": kw, "country": "US"}} for kw in KEYWORDS]
)

for item in results:
    if item["success"]:
        done = client.async_tasks.wait(item["task"]["id"])
        top = done["response"]["result"]["organicResults"][0]
        print(item["task"]["id"], "->", top["link"])
    else:
        print("failed:", item["error"]["message"])

Error handling

The SDK raises typed exceptions, 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.
from cloro import (
    Cloro,
    AuthenticationError,
    BadRequestError,
    RateLimitError,
    CloroError,
)

client = Cloro()

try:
    res = client.monitor.google(query="best running shoes", country="US")
except AuthenticationError as exc:
    raise RuntimeError("Invalid or missing CLORO_API_KEY") from exc
except BadRequestError as exc:
    raise RuntimeError(f"Bad request: {exc}") from exc
except RateLimitError:
    ...  # the client already retried; back off further if this persists
except CloroError:
    raise  # timeouts, 5xx, task failures — all subclass CloroError

Prefer to build it yourself?

The DIY route — requests + BeautifulSoup + rotating proxies — is real, and sometimes the right call for a one-off. Our Python web scraping pillar guide covers the ecosystem end to end, and How to Scrape Google Search 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

Start free. Price per credit drops as your volume grows — see every tier below.

Free
$0/mo
500 credits / month
  • Added monthly
  • 1 concurrent job
  • 1 seat
  • Community support
Lite
$30/mo
37,500 credits
  • $0.80 per 1k credits
  • 10 concurrent jobs
  • Unlimited seats
  • Email support
Hobby
$100/mo
250,000 credits
  • $0.40 per 1k credits
  • 20 concurrent jobs
  • Unlimited seats
  • Email support
Most Popular
Starter
$250/mo
650,000 credits
  • $0.39 per 1k credits
  • 50 concurrent jobs
  • Unlimited seats
  • Email support
Scale
$500+/mo
More credits
  • Volume discounts
  • More concurrency
  • Unlimited seats
  • Faster support
Compare all 11 tiers
PlanPrice / moCreditsPer 1kConcurrencySeatsSupport
Free$050011Community
Lite$3037,500$0.8010UnlimitedEmail
Hobby$100250,000$0.4020UnlimitedEmail
StarterPopular$250650,000$0.3950UnlimitedEmail
Growth$5001,350,000$0.3775UnlimitedPriority email
Business$1,0002,800,000$0.36100UnlimitedPriority email
Enterprise 2K$2,0005,871,025$0.34135UnlimitedSlack
Enterprise 3K$3,0009,306,606$0.32175UnlimitedSlack
Enterprise 4K$4,00012,756,261$0.31215UnlimitedSlack
Enterprise 5K$5,00016,391,783$0.31255UnlimitedSlack
Enterprise$5,000+Increased concurrency, overages on credits and credit discounts for annual contracts.Know more

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 Python?+

A SERP API is a hosted service you call from Python 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.

How do I call a SERP API from Python?+

Install the SDK (pip install cloro), store your key in the CLORO_API_KEY environment variable, then call client.monitor.google(query=...) — you get parsed JSON back. 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 call directly.

Do I need requests or httpx to call the API?+

No — pip install cloro gives you a typed client that wraps the HTTP layer, with automatic retries on sync calls. When you need to fan out hundreds of queries per minute, use the async task queue (client.async_tasks.create_batch) rather than hand-rolling httpx with asyncio.

Can I query ChatGPT, Perplexity, and Gemini from the same Python 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 much does the cloro API cost from Python?+

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.

How do I handle rate limits when calling the API from Python?+

The SDK retries 429 and 5xx responses with exponential backoff automatically. Cap your worker count — a ThreadPoolExecutor sized to your plan's concurrency limit — or for large batches use the async task queue, which absorbs concurrency for you rather than flooding the API.

Start building with cloro in Python today

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