Search API
Find relevant web pages by keyword before scraping. Search returns URLs, titles, and snippets — optionally scraping each result inline.
Discovery-First Workflow
Overview
Search
POST a query to /api/v1/search. Get back URLs, titles, and snippets from Google.
Scrape (Optional)
Set scrape_results: true to scrape every result page and get full content back.
Extract (Optional)
Pass an extraction_schema to pull structured data from every result in one call.
POST /v1/search
/api/v1/searchExecute a web search. Returns results synchronously (200) or, when scrape_results is true and there are more than 5 results, returns 202 with a search_id for polling.
curl -X POST https://api.alterlab.io/api/v1/search \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"query": "best headless browsers 2026",
"num_results": 10
}'Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| query | string | Yes | Search terms (1-500 characters) |
| domain | string | No | Restrict results to a specific domain (applied as site: prefix) |
| num_results | integer | No | Number of results to return (1-50, default: 10) |
| country | string | No | ISO 3166-1 alpha-2 country code for geo-targeted results (e.g., US, GB, DE) |
| language | string | No | Language code for results (e.g., en, fr, de) |
| time_range | string | No | Filter by recency: hour, day, week, month, year |
| scrape_results | boolean | No | If true, scrape each result page and include full content (default: false) |
| formats | string[] | No | Output formats when scrape_results=true: text, json, json_v2, html, markdown |
| extraction_schema | object | No | JSON schema for structured extraction (when scrape_results=true) |
Search-Only Response (200)
When scrape_results is false (default), results are returned synchronously:
{
"search_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"query": "best headless browsers 2026",
"results_count": 10,
"credits_used": 2000,
"results": [
{
"url": "https://example.com/headless-browsers-guide",
"title": "Top 10 Headless Browsers in 2026",
"snippet": "A comprehensive comparison of the best headless browsers for web scraping and automation...",
"position": 1,
"content": null
},
{
"url": "https://blog.example.com/playwright-vs-puppeteer",
"title": "Playwright vs Puppeteer in 2026",
"snippet": "Which headless browser framework should you choose? We compare performance, features...",
"position": 2,
"content": null
}
]
}Search + Scrape Response (202)
When scrape_results=true and there are more than 5 results, the response is 202 with a polling URL:
{
"search_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"query": "best headless browsers 2026",
"status": "scraping",
"results_count": 10,
"credits_used": 52000,
"results": [ ... ],
"message": "Search complete. 10 results are being scraped. Poll GET /api/v1/search/a1b2c3d4-... for progress."
}Inline Scraping
scrape_results=true and there are 5 or fewer results, the API waits briefly and attempts to return content inline. For larger result sets, use the polling endpoint.GET /api/v1/search/{search_id}
/api/v1/search/{search_id}Poll for search + scrape progress. Returns the search results with scraped content populated as each job completes.
curl https://api.alterlab.io/api/v1/search/a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d \
-H "X-API-Key: your_api_key"Status Response
{
"search_id": "a1b2c3d4-...",
"query": "best headless browsers 2026",
"status": "completed",
"results_count": 10,
"completed": 10,
"credits_used": 52000,
"results": [
{
"url": "https://example.com/headless-browsers-guide",
"title": "Top 10 Headless Browsers in 2026",
"snippet": "A comprehensive comparison...",
"position": 1,
"content": {
"text": "Full article text here...",
"markdown": "# Top 10 Headless Browsers..."
}
}
]
}| Status | Meaning |
|---|---|
| scraping | Some result pages are still being scraped |
| completed | All result pages have been scraped |
24-Hour TTL
Batch Search
Submit up to 50 search queries for async parallel processing. Each query runs independently and results are aggregated. Requires Growth+ balance tier ($50+ balance). Poll for results or receive them via webhook.
Request Body
/api/v1/search/batchSubmit a batch of search queries for asynchronous processing.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| queries | string[] | Required | Search queries to execute (1-50 queries, each max 500 characters) |
| num_results | integer | Optional | Number of results per query (1-30)Default: 10 |
| country | string | Optional | ISO 3166-1 alpha-2 country code applied to all queries (e.g., US, GB, DE) |
| language | string | Optional | Language code applied to all queries (e.g., en, fr, de) |
| time_range | string | Optional | Filter results by recency: hour, day, week, month, year |
| scrape_results | boolean | Optional | If true, scrape each result page for content. Per-page tier costs are debited as scrapes complete.Default: false |
| formats | string[] | Optional | Output formats when scrape_results=true: text, json, json_v2, html, markdown |
| webhook_url | string | Optional | Webhook URL to POST when all queries complete. Receives a batch.search.completed event. |
Request Example
curl -X POST https://api.alterlab.io/api/v1/search/batch \
-H "X-API-Key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"queries": [
"web scraping best practices",
"structured data extraction",
"API rate limiting strategies"
],
"num_results": 10,
"country": "US",
"webhook_url": "https://your-app.com/webhooks/search"
}'Response Example
{
"batch_id": "b1c2d3e4-f5a6-7890-bcde-f12345678901",
"total_queries": 3,
"status": "processing",
"estimated_credits": 3,
"job_ids": [
"j1-search-001",
"j1-search-002",
"j1-search-003"
],
"message": "Batch search submitted. Poll GET /api/v1/search/batch/{batch_id} for results."
}Response
The POST response returns immediately with a 202 Accepted status. Use the batch_id to poll for results.
Python SDK Example
from alterlab import AlterLabSync
client = AlterLabSync(api_key="YOUR_API_KEY")
# Submit batch search
batch = client.batch_search(
queries=[
"web scraping best practices",
"structured data extraction",
"API rate limiting strategies"
],
num_results=10,
country="US"
)
print(f"Batch ID: {batch['batch_id']}")
print(f"Estimated credits: {batch['estimated_credits']}")
# Poll for results
import time
while True:
status = client.get_batch_search_status(batch["batch_id"])
if status["status"] != "processing":
break
print(f"Progress: {status['completed']}/{status['total']}")
time.sleep(2)
# Process results
for item in status["items"]:
if item["status"] == "succeeded":
print(f"Query: {item['query']}")
for result in item["results"]:
print(f" {result['position']}. {result['title']}")Poll Batch Search Status
Poll the status of a batch search to get per-query results as they complete.
/api/v1/search/batch/{batch_id}Get the current status and results of a batch search.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| batch_id | string | Required | Batch search ID returned from POST /search/batch |
Request Example
curl -X GET https://api.alterlab.io/api/v1/search/batch/b1c2d3e4 \
-H "X-API-Key: sk_live_..."Response Example
{
"batch_id": "b1c2d3e4-f5a6-7890-bcde-f12345678901",
"status": "completed",
"total": 3,
"completed": 3,
"failed": 0,
"pending": 0,
"items": [
{
"job_id": "j1-search-001",
"query": "web scraping best practices",
"status": "succeeded",
"results_requested": 10,
"results_count": 10,
"results": [
{
"url": "https://example.com/scraping-guide",
"title": "Web Scraping Best Practices Guide",
"snippet": "Learn the best practices for building reliable web scrapers...",
"position": 1
}
],
"credits_used": 1
}
],
"total_credits_used": 3,
"created_at": "2025-11-05T10:30:00Z",
"webhook_status": "delivered",
"webhook_attempts": 1
}| Status | Description |
|---|---|
| processing | Queries are still being executed |
| completed | All queries finished successfully |
| partial | All queries finished but some failed |
| failed | All queries failed |
Batch Search Requirements
- Requires Growth+ balance tier ($50+ balance)
- Maximum 50 queries per batch
- Each query is billed at the flat search cost up front
- When
scrape_results=true, per-page tier costs are debited as scrapes complete - Batch metadata expires after 24 hours
Credit Model
| Action | Cost | Notes |
|---|---|---|
| Search query | 2 credits | Flat fee per search, regardless of num_results |
| Scrape per result | 1-5 credits | Standard scrape pricing per URL (tier-based) |
| Extraction | Included | No extra charge when using extraction_schema with scrape_results |
BYOP Discount
Error Codes
| Status | Error | Description |
|---|---|---|
| 402 | insufficient_credits | Not enough credits for the search or scrape |
| 404 | search_not_found | Search ID not found or expired (polling endpoint) |
| 502 | search_provider_error | Upstream search provider returned an error |
| 503 | search_unavailable | Search service is not configured |
| 504 | search_timeout | Search provider timed out |
Examples
Basic Search
import requests
response = requests.post(
"https://api.alterlab.io/api/v1/search",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"query": "web scraping best practices",
"num_results": 5
}
)
data = response.json()
for result in data["results"]:
print(f"{result['position']}. {result['title']}")
print(f" {result['url']}")Domain-Scoped Search
# Search within a specific domain
response = requests.post(
"https://api.alterlab.io/api/v1/search",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"query": "authentication API",
"domain": "docs.github.com",
"num_results": 10
}
)
# All results will be from docs.github.com
data = response.json()
print(f"Found {data['results_count']} pages on docs.github.com")Search + Scrape with Extraction
import time
# Search and scrape with structured extraction
response = requests.post(
"https://api.alterlab.io/api/v1/search",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"query": "iPhone 16 Pro review",
"num_results": 5,
"scrape_results": True,
"formats": ["text", "markdown"],
"extraction_schema": {
"type": "object",
"properties": {
"rating": {"type": "number", "description": "Review rating out of 10"},
"pros": {"type": "array", "items": {"type": "string"}},
"cons": {"type": "array", "items": {"type": "string"}},
"verdict": {"type": "string", "description": "One-line summary"}
}
}
}
)
data = response.json()
# If 202, poll for results
if response.status_code == 202:
search_id = data["search_id"]
while True:
status = requests.get(
f"https://api.alterlab.io/api/v1/search/{search_id}",
headers={"X-API-Key": "YOUR_API_KEY"}
).json()
if status["status"] == "completed":
data = status
break
time.sleep(2)
# Process extracted data
for result in data["results"]:
if result.get("content") and result["content"].get("extraction"):
ext = result["content"]["extraction"]
print(f"{result['title']}: {ext.get('rating')}/10")
print(f" Verdict: {ext.get('verdict')}")