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.web with signup-bonus credits while free use is available. Guest trial API credentials can start search.web with 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 limit you 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-Key header 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 accepts google, bing or yahoo. SERP blocks accept google only. 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 (Lisboa or Lisbon, Den Haag or The Hague). A city Chab cannot find in that country is rejected with validation_failed instead of quietly searching the whole country. When several places share the name, Chab searches the biggest one and says which in location.resolved_name and a warning.
  • location.region: optional, needs city. 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 as en-US, en-GB, or de-DE.
  • device: optional desktop or mobile; defaults to desktop.
  • safe_search: optional off, moderate, or strict; defaults to moderate. moderate and strict request safe filtering; off does not.
  • limit: optional integer from 1 to 100; defaults to 10. Guest trial keys are capped at 10.
  • callback_url, callback_secret, callback_events, and callback_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"
  }
}

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-Key header 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 containing organic, rich_results, and/or people_also_ask. Defaults to all three.
  • screenshot: optional boolean, default false. Also captures the results page as people in that country or city see it, in that language, as a PNG operation artifact with role serp_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"]}'