Search Endpoints
Both search operations are part of All tools. A start needs the
relevant scope and enough credits, and the tool must be available in
/v1/health. Free credits also follow the
free limits. Accepted starts return 202 Accepted.
Both endpoints are preview. Preview means the contract may still evolve before stable release; returned data is live search data, not sample output.
POST/v1/search/web
Creates a web search operation and returns normalized organic web results.
Required Access
- Plan: Starter, Pro, or Scale with paid API access. Verified free accounts can
start
search.webwith signup-bonus credits while free use is available. Guest trial API credentials can startsearch.webwith guest trial credits. - Operation family: Search
- Scope:
api:search:read - Credits: 1 credit per started 10 results, at least 2: 2 credits for up to 20
results, 10 credits for 100. Chab reserves credits for the
limityou ask for and bills on the results returned, so an engine that stops after 20 results costs 2 credits. Free credits are one-off grants and do not refresh monthly. - Idempotency:
Idempotency-Keyheader required
Request
{
"query": "laravel queue retry best practices",
"engine": "google",
"location": {
"country": "US"
},
"language": "en-US",
"device": "desktop",
"safe_search": "moderate",
"limit": 10
}
Fields:
query: required string, 1 to 500 characters.engine: required. Web search acceptsgoogle,bingoryahoo. SERP blocks acceptgoogleonly. See Bing results for how Chab handles a Bing answer that belongs to a different search.location.country: required ISO 3166-1 alpha-2 country code.location.city: optional, Google only. Results as people in that city see them, for example{"country":"NL","city":"Amsterdam"}. Local and English names both work (LisboaorLisbon,Den HaagorThe Hague). A city Chab cannot find in that country is rejected withvalidation_failedinstead of quietly searching the whole country. When several places share the name, Chab searches the biggest one and says which inlocation.resolved_nameand a warning.location.region: optional, needscity. Tells same-named cities apart, for example{"country":"US","city":"Portland","region":"OR"}. Postal-code, latitude, longitude and radius fields are rejected.language: required BCP 47 language tag, such asen-US,en-GB, orde-DE.device: optionaldesktopormobile; defaults todesktop.safe_search: optionaloff,moderate, orstrict; defaults tomoderate.moderateandstrictrequest safe filtering;offdoes not.limit: optional integer from 1 to 100; defaults to 10. Guest trial keys are capped at 10.callback_url,callback_secret,callback_events, andcallback_notify_on_expiration: optional one-off operation callback fields. They are available only for paid-funded starts; starts on free credits must omit callback fields and poll the operation URLs instead. Free credits are one-off grants and do not refresh monthly.
Unknown request fields are rejected. output_format, serp_depth, and SERP
blocks are not accepted on this endpoint.
Async Response
{
"operation": {
"id": "op_01JZ7H0A1B2C3D4E5F6G7H8J9K",
"status": "queued",
"operation_key": "search.web",
"family": "search",
"status_url": "/v1/operations/op_01JZ7H0A1B2C3D4E5F6G7H8J9K",
"result_url": "/v1/operations/op_01JZ7H0A1B2C3D4E5F6G7H8J9K/result",
"estimated_credits": 2,
"result_available": false,
"warnings": []
},
"meta": {
"request_id": "req_123",
"credits": {
"estimated": 2,
"reserved": 2,
"currency": "credit"
}
}
}
Succeeded Status
GET /v1/operations/{id} returns the operation status. After completion, the
status response includes structured warnings and the result URL:
{
"operation": {
"id": "op_01JZ7H0A1B2C3D4E5F6G7H8J9K",
"status": "succeeded",
"operation_key": "search.web",
"family": "search",
"status_url": "/v1/operations/op_01JZ7H0A1B2C3D4E5F6G7H8J9K",
"result_url": "/v1/operations/op_01JZ7H0A1B2C3D4E5F6G7H8J9K/result",
"estimated_credits": 2,
"result_available": true,
"public_error_code": null,
"warnings": [
{
"code": "result_truncated",
"message": "Results were capped at 10 rows.",
"field": "results",
"severity": "info"
}
],
"diagnostic_reference": null
},
"meta": {
"request_id": "req_123"
}
}
Result
GET /v1/operations/{id}/result returns the retained result after success:
{
"data": {
"result": {
"engine": "google",
"location": {
"country": "US"
},
"language": "en-US",
"device": "desktop",
"query": "laravel queue retry best practices",
"fetched_at": "2026-06-28T12:00:05+00:00",
"result_count": 2,
"complete": true,
"results": [
{
"title": "Laravel Queues - The PHP Framework For Web Artisans",
"url": "https://laravel.com/docs/queues",
"display_url": "laravel.com/docs/queues",
"snippet": "Learn how Laravel queues and retry handling work.",
"position": 1,
"result_type": "organic",
"availability": {
"title": "available",
"url": "available",
"display_url": "available",
"snippet": "available",
"position": "available"
},
"confidence": {
"score": null,
"availability": "unknown",
"source": "unknown"
}
}
]
},
"artifacts": [],
"warnings": [],
"result_expires_at": "2026-07-01T12:00:05+00:00"
},
"meta": {
"request_id": "req_123"
}
}
Result links
Since late October 2026 Google hides result links behind its own
google.com/goto redirects. Chab follows each redirect once and returns the
real page address in url. Google answers only a few of these in quick
succession, so the top results come first. A row Chab could not follow keeps
the site address (such as https://www.example.com) in url with
availability.url: "partial", adds redirect_url with Google's link (it opens
the page in a browser), and the result carries one partial_results warning.
Some rows (Reddit threads, videos) show only view or comment counts instead of
an address; Chab then gives the site's address when Google names it (for
example https://www.reddit.com/r/WFH), or url: null with
availability.url: "missing". url is never a Google redirect. Bing and
Yahoo links are real page addresses already.
A result appears once: when an engine repeats an earlier result on a later
page, Chab drops the repeat. position stays the rank the engine showed, so
positions can skip a number.
Bing results
Bing sometimes answers with the results of a different search. Chab checks
each Bing answer against the words of your query. When most of the first
results have nothing to do with it, Chab asks Bing once more, and if the
answer is still unrelated it asks Yahoo, which uses Bing's index. The result
then says "engine": "yahoo" and "requested_engine": "bing" and carries one
provider_degraded warning. If no engine gives a related answer, the
operation fails with provider_unavailable and you are not charged. The extra
requests are free for you. Queries in quotes, with search operators such as
site:, or without Latin words are not checked.
POST/v1/search/serp
Creates a SERP operation and returns normalized first-page result blocks. Advertisement blocks are excluded.
Required Access
- Access: Starter, Pro or Scale, or free credits (guest trial or verified account) within the free limits.
- Operation family: Search
- Scope:
api:search:read - Credits: 5 credits reserved on acceptance, 7 with
screenshot: true. Free credits are one-off grants and do not refresh monthly. - Idempotency:
Idempotency-Keyheader required
Request
{
"query": "best api monitoring tools",
"engine": "google",
"location": {
"country": "US"
},
"language": "en-US",
"device": "desktop",
"safe_search": "moderate",
"limit": 10,
"blocks": ["organic", "rich_results", "people_also_ask"]
}
The shared fields match POST /v1/search/web. Differences:
limit: optional integer from 1 to 10; caps organic rows.blocks: optional array containingorganic,rich_results, and/orpeople_also_ask. Defaults to all three.screenshot: optional boolean, defaultfalse. Also captures the results page as people in that country or city see it, in that language, as a PNG operation artifact with roleserp_screenshot(often a tall full-page image). It adds 2 credits and a few seconds.
serp_depth and output_format are rejected. Location takes a country and an
optional city (and region), as on web search.
Screenshot of the results page
{
"query": "fietsenmaker",
"engine": "google",
"location": {"country": "NL", "city": "Amsterdam"},
"language": "nl",
"screenshot": true
}
The result then carries screenshot with the artifact's filename, width
and height in pixels, and artifacts lists the PNG; download it from
GET /v1/operations/{id}/artifacts/{artifact_id}/download. If the screenshot
cannot be taken, the results still come back, screenshot is null, a
field_unavailable warning on screenshot says so, and the 2 extra credits
are not charged.
Async Response
The create response has the same operation envelope shape as web search, with
operation_key: "search.serp" and a 5 credit estimate/reservation (7 with a
screenshot).
Succeeded Status
{
"operation": {
"id": "op_01JZ7H0A1B2C3D4E5F6G7H8J9K",
"status": "succeeded",
"operation_key": "search.serp",
"family": "search",
"status_url": "/v1/operations/op_01JZ7H0A1B2C3D4E5F6G7H8J9K",
"result_url": "/v1/operations/op_01JZ7H0A1B2C3D4E5F6G7H8J9K/result",
"estimated_credits": 5,
"result_available": true,
"public_error_code": null,
"warnings": [
{
"code": "field_unavailable",
"message": "Rich results were not available for this query.",
"field": "blocks.rich_results",
"severity": "info"
}
],
"diagnostic_reference": null
},
"meta": {
"request_id": "req_123"
}
}
Result
{
"data": {
"result": {
"engine": "google",
"location": {
"country": "US"
},
"language": "en-US",
"device": "desktop",
"query": "best api monitoring tools",
"fetched_at": "2026-06-28T12:00:05+00:00",
"result_count": 12400000,
"complete": false,
"blocks": {
"organic": [
{
"title": "API Monitoring Tools",
"url": "https://example.com/api-monitoring",
"display_url": "example.com/api-monitoring",
"snippet": "Compare monitoring tools for APIs.",
"position": 1,
"result_type": "organic",
"block_type": "organic",
"availability": {
"title": "available",
"url": "available",
"display_url": "available",
"snippet": "available",
"position": "available"
},
"confidence": {
"score": null,
"availability": "unknown",
"source": "unknown"
}
}
],
"rich_results": [],
"people_also_ask": [
{
"block_type": "people_also_ask",
"question": "What is API monitoring?",
"answer": "API monitoring checks availability, latency, and correctness.",
"url": null,
"availability": {
"question": "available",
"answer": "available",
"url": "missing"
}
}
]
}
},
"artifacts": [],
"warnings": [
{
"code": "field_unavailable",
"message": "Rich results were not available for this query.",
"field": "blocks.rich_results",
"severity": "info"
}
],
"result_expires_at": "2026-07-01T12:00:05+00:00"
},
"meta": {
"request_id": "req_123"
}
}
Errors
Common errors include:
| Code | Meaning | Retry |
|---|---|---|
validation_failed |
Request fields, location, or language are invalid or unsupported. | No |
api_scope_missing |
The key does not have api:search:read. |
No |
paid_plan_required |
The team is not on a paid API plan. | No |
operation_family_not_included |
The plan does not include Search. | No |
insufficient_credits |
The team does not have enough available credits for the estimate. | No |
rate_limited |
The operation-start rate limit was exceeded. | Yes |
quota_exceeded |
The plan quota was exceeded. | No |
concurrency_limit_exceeded |
Too many active operations are running. | Yes |
provider_unavailable |
Search data is temporarily unavailable. | Yes |
provider_temporarily_rate_limited |
Search data is temporarily throttled after retries. | Yes |
provider_response_invalid |
Chab could not use the returned search data. | Yes |
Provider errors stay provider-neutral. Responses never include provider task IDs, raw provider messages, or internal diagnostics.
Credit Notes
search.web reserves 1 credit per started 10 results, at least 2 (2 credits
for up to 20 results, 5 for 50, 10 for 100), and bills on the unique results
returned, never more than the reservation. search.serp reserves 5 credits,
or 7 with screenshot: true; a screenshot that could not be taken is not
charged.
Successful
operations settle at the reserved amount. If work has not started or can be
cancelled with no chargeable work, Chab releases the reservation. If chargeable
work has partially started, Chab may charge the actual partial amount up to the
active hold and release the remainder.
Examples
curl -X POST https://www.chab.ai/v1/search/web \
-H "Authorization: Bearer CHAB_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: search-web-001" \
-d '{"query":"laravel queue retry best practices","engine":"google","location":{"country":"US"},"language":"en-US","device":"desktop","safe_search":"moderate","limit":10}'
curl -X POST https://www.chab.ai/v1/search/serp \
-H "Authorization: Bearer CHAB_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: search-serp-001" \
-d '{"query":"best api monitoring tools","engine":"google","location":{"country":"US"},"language":"en-US","device":"desktop","safe_search":"moderate","limit":10,"blocks":["organic","rich_results","people_also_ask"]}'