Migrate to cloro: Field-Level Guides for 6 SERP APIs
On this page
Disclosure: this is cloro’s own site, so read it as a vendor’s migration guide rather than a neutral review. Every parameter and field name on the other vendors’ side comes from their own live documentation, linked inline. Confirm them there before you write code against them.
What cloro’s request looks like
Whatever you are leaving, the target is the same: a POST with a JSON body, one endpoint per surface, and a bearer token.
POST https://api.cloro.dev/v1/monitor/google
Authorization: Bearer <key>
{ "query": "serp api",
"country": "US",
"location": "Austin, Texas",
"pages": 1,
"include": { "aioverview": { "markdown": true } } }
The response splits the page into named arrays rather than one mixed list: result.organicResults[], result.ads[], result.peopleAlsoAsk[], result.relatedSearches[], result.shoppingCards[], result.localResults[], result.knowledgeGraph, and result.aioverview.
That last one is the field most migrations are for. In cloro’s own SERP feature census we scored 65,945 results and found an AI Overview on 87.2% of US results and People Also Ask on 90.6%, so it is not an edge case in the response you are paying for either way.
Migrating from SerpApi
The request
SerpApi puts everything in the query string:
GET https://serpapi.com/search
?engine=google
&q=serp+api
&location=Austin,+Texas
&api_key=<key>
| SerpApi | cloro | Note |
|---|---|---|
engine=google | the endpoint path | one endpoint per surface rather than an engine parameter |
q | query | same meaning |
api_key | Authorization: Bearer header | move it out of the URL |
location | location, or uule | cloro also accepts a raw UULE string |
gl / hl | gl / hl, or country | same names, plus a simpler country shortcut |
num / pagination | pages | one call returns N pages instead of N calls |
| — | include.aioverview.markdown | SerpApi returns ai_overview, often as a page_token stub. See below |
The response
The organic list is nearly a rename. The array path moves, and one field changes case:
| SerpApi field | cloro field |
|---|---|
organic_results[] | result.organicResults[] |
position | position |
title | title |
link | link |
snippet | snippet |
displayed_link | displayedLink |
source | (no equivalent) |
| — | date |
| — | page, which results page the row came from |
A parser reading position, title, link and snippet therefore needs one real change: the array path.
The AI Overview costs a second call
SerpApi does return an ai_overview object, so “no AI Overview” would be wrong. What it often returns is a stub carrying only a page_token, because Google serves that block asynchronously. Getting the text and its cited sources means a second, separately billed request to /search?engine=google_ai_overview&page_token=<token>.
We know this because cloro’s monitoring runs SerpApi, DataForSEO and Oxylabs in production against the same prompt set every day, and the SerpApi client carries that follow-up fetch. The same query answers both ways: inline text_blocks and references on one call, a bare token on the next, and which one you get is not predictable from the request. Budget two calls for any query where the AI Overview matters.
cloro returns the block and its sources in the first response when include.aioverview is set, at 2 extra credits.
Working code
# before
r = requests.get(
"https://serpapi.com/search",
params={"engine": "google", "q": "serp api", "api_key": KEY},
timeout=30,
)
for row in r.json()["organic_results"]:
print(row["position"], row["title"], row["link"])
# after
r = requests.post(
"https://api.cloro.dev/v1/monitor/google",
headers={"Authorization": f"Bearer {KEY}"},
json={"query": "serp api", "country": "US",
"include": {"aioverview": {"markdown": True}}},
timeout=30,
)
data = r.json()["result"]
for row in data["organicResults"]:
print(row["position"], row["title"], row["link"])
overview = data.get("aioverview") # the part SerpApi leaves you to extract
What depth does to the bill
This is the difference worth modelling before you migrate anything.
SerpApi counts a page of ten results as one search, so a top-100 pull is ten searches. The same query set costs roughly $2 to $4 per 1,000 at ten results and roughly $20 to $40 per 1,000 at a hundred.
cloro is page-driven on one call: a Google Search request is 3 credits, plus 2 for each additional page and 2 for AI Overview enrichment. Ten pages is one call at 3 + 18 credits rather than ten calls, which works out around $1.25 to $2.00 per 1,000 at ten results with the AI Overview included and $5.75 to $9.20 at a hundred.
If your pipeline only ever reads the top ten, the gap is real but modest. If it pulls deep, the billing model is the migration case on its own.
Migrating from DataForSEO
The request
DataForSEO posts an array of task objects, authenticated with HTTP Basic:
POST https://api.dataforseo.com/v3/serp/google/organic/live/advanced
Authorization: Basic <base64 of login:password>
[ { "keyword": "serp api",
"location_name": "United States",
"language_code": "en",
"depth": 10 } ]
| DataForSEO | cloro | Note |
|---|---|---|
keyword | query | same meaning |
location_name / location_code | location, country, or uule | strings port directly, no code table |
language_code | hl | same values |
depth (results, max 200) | pages (pages of ~10) | depth in pages rather than a result count |
Basic auth, base64 login:password | Authorization: Bearer <key> | one credential instead of two |
| array of tasks | one object per call | batch through the async task API instead |
| — | include.aioverview.markdown | DataForSEO has load_async_ai_overview. See below |
The response
Here the migration is mostly deletion. DataForSEO nests organic rows two levels down inside a mixed array; cloro returns them already separated:
| DataForSEO | cloro | |
|---|---|---|
| Path to the rows | tasks[] → result[] → items[] | result.organicResults[] |
| Filter needed | yes, type == "organic" | no, the array is already organic |
| Position | rank_absolute | position |
| Title | title | title |
| URL | url | link |
| Snippet | description | snippet |
| Extra fields | — | displayedLink, date, page |
Both the type filter and the two-level unwrap go away.
Two things the docs will not tell you
The AI Overview needs a flag, and the flag costs latency. items[] carries an entry with type: "ai_overview", but without load_async_ai_overview: true on the task it comes back as a stub with items and references null. Setting it adds roughly three seconds to the request. We run DataForSEO in production against a daily prompt set and that flag is in our task payload for exactly this reason.
Success is not an HTTP 200. DataForSEO reports per-task failure inside a 200 response, so a correct client checks status_code == 20000 on the envelope and tasks[0].status_code == 20000 on the task, then reads tasks[0].status_message for the reason. Any error handling you wrote around HTTP status alone will silently treat failures as successes. cloro returns a top-level success boolean and a normal HTTP status.
Working code
# before
r = requests.post(
"https://api.dataforseo.com/v3/serp/google/organic/live/advanced",
auth=(LOGIN, PASSWORD),
json=[{"keyword": "serp api", "location_name": "United States",
"language_code": "en", "depth": 10}],
timeout=60,
)
for row in r.json()["tasks"][0]["result"][0]["items"]:
if row["type"] == "organic":
print(row["rank_absolute"], row["title"], row["url"])
# after
r = requests.post(
"https://api.cloro.dev/v1/monitor/google",
headers={"Authorization": f"Bearer {KEY}"},
json={"query": "serp api", "country": "US", "hl": "en",
"include": {"aioverview": {"markdown": True}}},
timeout=60,
)
for row in r.json()["result"]["organicResults"]:
print(row["position"], row["title"], row["link"])
The honest cost comparison
DataForSEO is $2.00 per 1,000 in real time. cloro’s Google Search call is 3 credits, 5 with AI Overview enrichment, which lands around $1.25 to $2.00 per 1,000 at ten results on the Hobby plan and lower on higher tiers. So on Google organic the two are level, and cloro carries the AI Overview inside that rate.
So on Google organic alone the two are level on price. The comparison only moves when you count the things you would otherwise build yourself: AI Overview parsing, with the answer and its cited source URLs arriving as a field instead of a scrape of a nested payload; the AI answer engines, since ChatGPT, Perplexity, Copilot, Gemini, Grok and AI Mode run through the same endpoint family and the same credit pool; and the polling loop, which a synchronous endpoint removes.
If your pipeline does none of that, stay on DataForSEO. We would rather you stayed than switched and regretted it.
Migrating from ScraperAPI
The request
ScraperAPI’s structured Google endpoint is a GET with the key in the query string:
GET https://api.scraperapi.com/structured/google/search
?api_key=<key>
&query=serp+api
&country_code=us
| ScraperAPI | cloro | Note |
|---|---|---|
api_key | Authorization: Bearer header | move it out of the URL |
query | query | same name, same meaning |
country_code | country | same values |
hl / gl | hl / gl | unchanged |
uule | uule | unchanged |
start | pages | offset becomes a page count |
output_format | — | cloro returns JSON only |
| — | include.aioverview.markdown | not documented on ScraperAPI’s structured Google endpoint |
The response
This is the closest mapping on the page. ScraperAPI returns organic_results[] with position, title, snippet, link and displayed_link, so only the array path and the case of one field change:
| ScraperAPI | cloro |
|---|---|
organic_results[] | result.organicResults[] |
position, title, snippet, link | identical |
displayed_link | displayedLink |
The cost
ScraperAPI bills in credits from a monthly pool, and its Google structured endpoint costs about 25 credits a call. On the $49 Hobby plan of 100,000 credits that is roughly 4,000 Google calls, an effective rate near $12 per 1,000. cloro’s 3 credits a call, 5 with AI Overview enrichment, works out to $1.25 to $2.00 per 1,000. On Google specifically this is the largest price gap on this page, and it exists because ScraperAPI is a general proxy scraper carrying the cost of every other target it supports.
Keep ScraperAPI for anything that is not a search engine. It scrapes arbitrary sites, which cloro does not.
Migrating from Oxylabs
The request
Oxylabs Web Scraper API takes a job payload over HTTP Basic auth:
POST https://realtime.oxylabs.io/v1/queries
Authorization: Basic <base64 of username:password>
{ "source": "google_search",
"query": "serp api",
"parse": true }
| Oxylabs | cloro | Note |
|---|---|---|
source: "google_search" | the endpoint path | one endpoint per surface rather than a source field |
query | query | same name |
parse: true | — | cloro parses by default, there is no raw mode to opt out of |
geo_location | location, country, or uule | strings port directly |
Basic auth, base64 username:password | Authorization: Bearer <key> | one credential instead of two |
| — | include.aioverview.markdown | Oxylabs parses ai_overviews (plural). See below |
The response
This is the biggest rename on the page. Oxylabs nests organic rows three levels down and abbreviates the field names:
| Oxylabs | cloro | |
|---|---|---|
| Path to the rows | results[].content.results.organic[] | result.organicResults[] |
| Position | pos | position |
| Position across the page | pos_overall | position (single sequence) |
| Title | title | title |
| URL | url | link |
| Snippet | desc | snippet |
| Displayed URL | url_shown | displayedLink |
Two traps we hit in production
Set locale explicitly or your language follows your geo. Left out, Oxylabs picks the interface language from geo_location: hl=pt for Brazil, hl=ar for the UAE, hl=ga (Irish) for Ireland. SerpApi sends hl=en and DataForSEO language_code=en under the same conditions, so three providers end up answering three different questions while a dashboard presents the answers as one row. We found this comparing the three side by side and now pass locale: "en", which sets hl and leaves gl on the country.
The AI Overview key is plural. Oxylabs ships the block as content.results.ai_overviews, alongside organic, paid and related_questions in the same object. Verified against a live google_search parse on 2026-08-15. A parser looking for ai_overview finds nothing and concludes the block was absent.
The cost
The Oxylabs SERP Scraper API starts at $49/month and bills roughly $2 per 1,000 successful results, which moves retry risk onto the vendor. cloro bills per call at $1.25 to $2.00 per 1,000 at ten results with the AI Overview included. The rates are close enough that price is not the reason to move; the parsed AI Overview and the AI answer engines are.
Geo targeting is worth checking before you commit either way. Oxylabs supports country-level targeting on the developer tier and finer geo above it; cloro accepts any of Google’s canonical geotargets at the same per-call price on every plan.
Oxylabs also sells enterprise proxy infrastructure, a 100M+ IP pool that cloro has no equivalent for. If that is what you are really buying, this is not a migration.
Migrating from Serper
The request
Serper is the smallest API in this set, and the migration is correspondingly small.
POST https://google.serper.dev/search
X-API-KEY: <key>
Content-Type: application/json
{ "q": "apple inc" }
| Serper | cloro | Note |
|---|---|---|
X-API-KEY header | Authorization: Bearer header | both are headers, so no URL surgery |
q | query | same meaning |
gl / hl | gl / hl, or country | same names, same values |
tbs | — | no date-range filter |
page | pages | one call returns N pages |
| mini-batch, up to 100 queries | the async task API | batching moves to a different endpoint |
| — | location / uule | Serper has no location parameter in this shape |
| — | include.aioverview.markdown | Serper returns no AI Overview |
The response
The organic rows need no renaming at all. Verified against a live playground call on 2026-09-02:
| Serper | cloro |
|---|---|
organic[] | result.organicResults[] |
title, link, snippet, position | identical |
peopleAlsoAsk[] with question only | result.peopleAlsoAsk[] with question, snippet, markdown and sources[] |
relatedSearches[] with query | result.relatedSearches[] with query and link |
credits on the response | credits on the account, not per response |
| no equivalent | result.aioverview, result.ads[], result.shoppingCards[], result.knowledgeGraph |
So for row in data["organic"] becomes for row in data["result"]["organicResults"] and the loop body is unchanged.
What actually changes
The envelope, not the fields. Serper’s People Also Ask returns the question and nothing else, so you cannot see the answer Google gave or which page it came from. There is no AI Overview block at all.
That is a deliberate design, and it is why Serper is fast and cheap: about $1 per 1,000 queries at entry falling toward $0.30 at volume, against roughly $1.25 to $2.00 for cloro at ten results with the AI Overview included. If the thin envelope is enough for you, Serper is the cheaper tool and you should stay on it. Move when you need to know what the AI Overview said and who it cited.
Migrating from Bright Data
The request
This is the largest structural change on the page, because Bright Data’s Google SERP path is not a SERP API in the usual sense. It is a dataset job:
POST https://api.brightdata.com/datasets/v3/scrape
Authorization: Bearer <key>
{ "input": [ { "url": "https://www.google.com/search?q=serp+api&gl=us&hl=en&num=10&uule=<encoded location>" } ] }
The response is either the result inline or a snapshot_id. When the initial payload is small, you poll /datasets/v3/snapshot/{id} on an interval until it resolves.
The dataset takes a URL and nothing else. Send it a keyword field and it answers “This input should not contain a keyword field”; leave the URL out and it answers “url: Required field”. So the query, the interface language, the country and the location all have to be encoded into a Google search URL yourself, with the location going in as a uule parameter you generate. We verified that live on 2026-09-01 while wiring Bright Data into cloro’s own provider comparison.
| Bright Data | cloro |
|---|---|
input[].url, with q inside it | query |
gl inside the URL | country or gl |
hl inside the URL | hl |
uule inside the URL, encoded by you | location as plain text, or uule if you prefer |
num inside the URL | pages |
dataset_id | the endpoint path |
| snapshot polling | a synchronous response, or the async task API with webhooks |
What changes for your code
Three things collapse. The URL builder goes, because parameters become fields. The polling loop goes, because the Google endpoint answers synchronously. And the uule encoder goes, because location accepts a plain string like “Austin, Texas”.
Bright Data remains the right tool for scraping arbitrary sites at scale behind a large residential pool, which cloro does not sell. cloro’s own monitoring runs Bright Data for several AI engine surfaces alongside its own API, so this is a boundary rather than a replacement.
When not to migrate
Be honest about what you actually use. Any of these means you stay where you are, or run both:
- Engines beyond Google. Bing, Baidu, Yahoo or Yandex as tracked engines, which SerpApi covers and cloro does not.
- Retail and vertical endpoints. Walmart, Amazon, the App Store, YouTube or Google Maps as first-class products.
- DataForSEO’s non-SERP products. Keyword data, backlinks, on-page audits, business listings.
- Arbitrary websites. ScraperAPI, Oxylabs and Bright Data scrape any URL; cloro scrapes search surfaces only.
- A thin, fast, cheap Google envelope is all you need. Serper is cheaper per query than anything here and its organic rows are the same shape.
- A residential proxy pool. Oxylabs and Bright Data sell theirs at 100M+ and 72M+ IPs. cloro does not sell proxies at all.
- More than 100 results in a single call. DataForSEO’s
depthgoes to 200; cloro pages in tens. - A steady monthly volume that fits a SerpApi bundle cleanly, where the bundle discount beats a per-credit pool at your exact volume.
Plenty of teams end up running both, keeping the old vendor for the engine tail and moving the Google and AI-answer slice to a per-credit pool. That is a normal outcome.
The cutover checklist
- Take a key on the free tier, 500 credits a month, which is enough for a real diff.
- Dual-write for a week: send the same queries to both, and diff the organic lists on domain and position rather than on exact snippet text.
- Make the request change. From SerpApi or ScraperAPI, GET becomes POST and
api_keymoves into theAuthorizationheader. From DataForSEO or Oxylabs, drop the task or job envelope and swap Basic auth for a bearer token. - Make the response change. From SerpApi or ScraperAPI, repoint the array to
result.organicResultsand renamedisplayed_link. From DataForSEO, delete thetype == "organic"filter and thetasks[] → result[]unwrap, then renamerank_absolute,urlanddescription. From Oxylabs, flattenresults[].content.results.organic[]and renamepos,url,descandurl_shown. - Replace pagination with the
pagesparameter, or the polling loop with a synchronous call. Move to the async task API instead if you want webhooks. - Re-baseline the cost model on credits per call, including the extra 2 credits when AI Overview enrichment is on.
- Add
result.aioverviewandresult.peopleAlsoAsk[].sources[]to whatever you report on, which for most teams is the reason they moved. - Cut the old key after a full billing cycle of dual-writes, once you have seen the two bills side by side.
For the pricing picture across every vendor in the category, see the cheapest SERP API breakdown; for the ranked field, the SERP API comparison; and for each vendor against its alternatives, SerpApi, DataForSEO, Serper, Bright Data, ScraperAPI and Oxylabs.

About the author
Ricardo Batista
Founder, cloro
Ricardo is one of the founders and engineers behind its SERP and AI-search scraping infrastructure. Before cloro he scaled a financial comparison site to $7M ARR and ran the full-country operations of a unicorn to $65M ARR, then went back to building. He writes about search engine scraping, generative-engine optimization, and turning live search and AI-answer data into something teams can act on.
Frequently asked questions
How long does it take to migrate a SERP API integration to cloro?
For a single Google Search call, under an hour from either vendor. From SerpApi you are turning a GET with query parameters into a POST with a JSON body and renaming one field. From DataForSEO you are deleting a task envelope and an item-type filter and renaming three. What takes longer is anything downstream that assumed the old vendor's engine catalogue, because cloro covers Google and the AI answer engines rather than Bing, Baidu, Yandex, or the retail verticals.
Is cloro cheaper than the vendor I am on?
Cheaper than SerpApi, and level with DataForSEO. SerpApi runs $2 to $4 per 1,000 at ten results and $20 to $40 at a hundred, because it bills each page of ten as a separate search. DataForSEO is about $2.00 per 1,000. cloro is roughly $1.25 to $2.00 per 1,000 at ten results with the AI Overview included, and about $5.75 to $9.20 at a hundred. All three figures are real-time rates.
What do I gain by moving?
Mostly the AI Overview in the first response. All four vendors can reach that block, so the honest difference is what it costs to get: SerpApi frequently returns a page_token stub and bills a second request for the content, DataForSEO needs load_async_ai_overview on the task and adds about three seconds, and ScraperAPI does not document it on its structured Google endpoint. cloro returns the answer and its cited sources inline for 2 extra credits, and covers ChatGPT, Perplexity, Copilot, Gemini, Grok and AI Mode through the same endpoint family and credit pool. Depth billing is page-driven rather than per-search too.
What do I lose?
Engine breadth from SerpApi, which covers more than 80 engines including Bing, Baidu, Yandex, Walmart, YouTube and the App Store. Non-SERP products from DataForSEO, which sells keyword data, backlinks, on-page and business listings that cloro does not have. If any of those is load-bearing in your pipeline, run both rather than switching.
Can I run both APIs during the migration?
Yes, and you should. Both are stateless HTTPS calls with no shared state, so dual-write the same query set to each for a week and diff the organic lists on domain and position before you cut the old key. The free tier of 500 credits a month covers that diff on a real keyword set.
Which vendors are covered here?
Six: SerpApi, DataForSEO, Serper, Bright Data, ScraperAPI and Oxylabs, each mapped field by field. The target shape is the same whichever you are leaving: POST a JSON body to an endpoint per surface with a bearer token. If you are moving from a vendor not listed, such as Zyte, the cloro half of every table still applies and only your current vendor's parameter names need looking up.
Which migration is the hardest?
Bright Data, because its Google SERP path is a dataset job rather than a search endpoint. It accepts a URL and nothing else, rejecting a keyword field outright, so the query, language, country and location all have to be encoded into a Google search URL with a uule parameter you generate, and the response may arrive as a snapshot id you poll. Moving to a parameterised synchronous endpoint deletes the URL builder, the uule encoder and the polling loop in one go.
Which migration is the easiest?
ScraperAPI, because its structured Google endpoint already returns an `organic_results[]` array whose keys (`position`, `title`, `snippet`, `link`) match cloro's exactly. Only the array path and the auth change. Oxylabs is the most work: its parsed response nests organic rows at `results[].content.results.organic[]` and uses `pos`, `url` and `desc` where cloro uses `position`, `link` and `snippet`.
Related reading

Cheapest SERP API 2026: True Cost-per-Call Compared
The cheapest SERP API in 2026, ranked by true cost-per-call. Compare cloro, DataForSEO, Serper, and SerpApi, plus the hidden fees that flip rankings.

Best SERP APIs 2026: 9 Tested on Cost, AI and Tracking
9 SERP APIs tested head to head on AI Overview parsing, engine breadth, rank-tracking depth, and true cost per 1,000 calls. Full benchmark table inside.