Search
Search the web from one request — a working call, the options that matter, the response, what it costs, and the errors to handle.
For AI agents: https://bytekit.com/llms.txt — every docs page is available as markdown by appending .md
POST /v1/search runs a web search and returns ranked results: a title, a URL and, when
available, a snippet for each. Send a query and you get back up to ten results. Everything
else — how many results, which country and language, news or images, how recent — is optional.
Run a search
curl -X POST https://api.bytekit.com/v1/search \
-H "Authorization: Bearer sk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{"query": "best web scraping tools", "limit": 3}'The call is synchronous: it returns 200 with the results in the response body.
{
"id": "sr_742ffcf841d24db7a2261f6a8c087469",
"query": "best web scraping tools",
"results": [
{
"position": 1,
"title": "Best web scraping tools I've tried (and what I learned from ...",
"url": "https://www.reddit.com/r/automation/comments/1ncuv8k/best_web_scraping_tools_ive_tried_and_what_i/",
"snippet": "Playwright: Great for structured. Hyperbrowser: The one I'm using most now. . Playwright is powerful and well-structured, Selenium / Playwright ...",
"date": "1 year ago"
},
{
"position": 2,
"title": "5 best AI web scraper tools I'm using in 2026 (free + paid)",
"url": "https://www.gumloop.com/blog/best-ai-web-scrapers",
"snippet": "5 best AI web scraper tools to use in 2026 · 1. Gumloop · 2. Octoparse · 3. Browse AI · 4. Thunderbit · 5. Firecrawl. Firecrawl web scraper. Best ...",
"date": null
},
{
"position": 3,
"title": "Best web scraping tools (ideally premium instead of open source)?",
"url": "https://www.reddit.com/r/Entrepreneur/comments/1pc9u5k/best_web_scraping_tools_ideally_premium_instead/",
"date": null
}
],
"related_searches": [
"Best web scraping tools reddit",
"Best web scraping tools free",
"AI web scraping tool free",
"Best web scraper Python"
],
"credits_used": 1
}This request was run for real and returned
200. The response is shortened for the page: the real one listed eightrelated_searches, and four are shown. Results are live search results, so yours will differ.
| Field | Description |
|---|---|
id | Search id, prefixed sr_ |
query | The query that was searched |
results | The ranked results, best first. position starts at 1 and runs without gaps; the list holds at most limit results |
related_searches | Related queries the search suggests. An empty array when there are none |
credits_used | Credits this search cost (see Cost) |
Each item in results has:
| Field | Description |
|---|---|
position | Rank of the result, from 1 |
title | The result's title |
url | The result's URL |
snippet | A short text excerpt. Not every result has one — in the example, result 3 has none — and it is never present for images results |
date | When the result was published, as the search reports it. A string such as 1 year ago (the wording follows language) or null when no date is reported. Not a fixed date format, so do not parse it as ISO. Absent for images results |
A search can return fewer results than limit, and an empty results array is a valid answer.
Options
Only query is required. Send any of the others alongside it.
| Option | Default | Description |
|---|---|---|
query | — | The search query, 1 to 2048 characters |
limit | 10 | Maximum number of results, an integer from 1 to 100. It also sets what the call costs (see Cost) |
type | web | Which index to search: web, news, or images |
country | us | Two-letter country code to localize results, for example us or de |
language | en | Two-letter ISO 639-1 language code, for example en or fr. Region forms such as en-US are rejected |
date_range | any | Only results from a recent window: any (no filter), hour, day, week, month, or year |
country and language are not case sensitive. An unknown value for type or date_range, a
limit outside 1–100, a country that is not two characters, or a language that is not a
real ISO 639-1 code is rejected with 400 validation_error.
curl -X POST https://api.bytekit.com/v1/search \
-H "Authorization: Bearer sk_live_YOUR_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"query": "web scraping",
"type": "news",
"limit": 2,
"country": "de",
"language": "de",
"date_range": "week"
}'This request was run for real and returned
200with two results, each carrying asource(the publisher) and animage, and"credits_used": 1. Thedatevalues came back in German (vor 3 Tagen), followinglanguage.
Result types
type changes which fields a result carries. The rest of the response is the same, and so is the
cost.
type | What results holds |
|---|---|
web | Ranked web results with title, url, snippet and date |
news | News articles. Adds source (the publisher) and image, a thumbnail whose only field is url |
images | Image results. Adds source, domain (the page hosting the image) and a fuller image object: url, width, height, thumbnail_url, thumbnail_width, thumbnail_height. Has no snippet or date |
The extra fields are present when the search reports them, so treat each as optional.
Cost
Search is billed in credits, not bandwidth. A search costs one credit for every ten results requested, rounded up, and never more than ten credits:
credits = min(ceil(limit / 10), 10)limit | Credits |
|---|---|
10 (the default) | 1 |
11 | 2 |
35 | 4 |
100 | 10 |
The charge follows the limit you ask for, not the number of results you get back: a search
that returns fewer results than limit still costs the full amount. Ask for no more than you
need. credits_used in the response reports what the call cost.
The
11row was run for real: the response held 11 results atposition1 through 11 and reported"credits_used": 2. The other rows follow from the formula, which Credit billing also states.
A search that is rejected before it runs (400, 401, 402, 429) is not charged, and a
search that fails with 500 or 503 is refunded. For how the credit balance works, see
Credit billing.
Use it from an SDK
TypeScript:
import { ByteKit } from '@hunt-labs/bytekit-sdk';
const client = new ByteKit({ apiKey: process.env.BYTEKIT_API_KEY! });
const res = await client.search.query({ query: 'best web scraping tools', limit: 3 });
for (const r of res.results) console.log(r.position, r.title, r.url);
console.log('credits used:', res.credits_used);Python:
import os
from bytekit import AuthenticatedClient
client = AuthenticatedClient(token=os.environ["BYTEKIT_API_KEY"])
res = client.search("best web scraping tools", limit=3)
for r in res.results:
print(r.position, r.title, r.url)
print("credits used:", res.credits_used)Both snippets were run for real and printed three results and
credits used: 1. The endpoint accepts the same options as the table above, passed astype,limit,country,languageanddate_range. A non-200response raises an error carrying the status and the errorcode(ByteKitErrorin TypeScript,errors.UnexpectedStatusin Python). See the TypeScript and Python references.
Errors
Errors use the unified envelope described in the Errors guide. These are the ones you are most likely to meet on this endpoint:
| Status | Code | When | Retry? |
|---|---|---|---|
400 | validation_error | query is missing or empty, an option is out of range or not an allowed value, or the body is not valid JSON | No — fix the request |
401 | unauthorized, invalid_api_key | The API key is missing, malformed, or not valid | No — check your key |
402 | quota_exceeded | Your credit balance cannot fund the search. The message states the credit total that is exhausted | No — add credits or upgrade; a retry fails until then |
402 | spending_cap_reached, billing_entitlement_impaired, billing_entitlement_expired | The spending cap or the subscription state blocks the request — see the Errors guide | No — resolve the billing state first |
429 | rate_limited | Too many requests per second | Yes — wait for the Retry-After header, then back off |
500 | internal_error | The search could not be recorded, so it did not run | Yes — back off and retry |
503 | search_provider_error | The search service failed to answer | Yes — back off and retry |
400 validation_error is the status this endpoint uses for validation failures; the
Errors guide lists it as such for /v1/search. The error body carries the first
failing check in error.message and no error.details:
{
"status": "failed",
"error": {
"code": "validation_error",
"message": "Number must be less than or equal to 100",
"http_status": 400,
"failed_at": "2026-10-09T20:46:17.996Z"
}
}This response was returned by a real request with
"limit": 101.
A 429 carries a Retry-After header, in seconds. A real burst of requests past a
5-requests-per-second limit returned:
{
"status": "failed",
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Allowed 5 requests per second.",
"http_status": 429,
"failed_at": "2026-10-09T20:46:28.344Z"
}
}with the headers Retry-After: 1 and X-RateLimit-Limit: 5. Your own limit depends on your
plan; see Rate Limits.
A 503 search_provider_error does not use the unified envelope. Its body is exactly {"error": "search_provider_error"}
— a bare string, not an object. The Errors guide does not list this code, so
handle it here: check for a top-level error that is a string. The TypeScript SDK raises a
ByteKitError whose code is search_provider_error; the Python SDK raises
errors.UnexpectedStatus with status_code 503 and the raw body in content, and its code is
None for this body.
Next steps
- Scraping — fetch the content of a result's URL
- Errors — the error envelope and retry guidance
- Rate Limits — requests per second and concurrency slots
- Credit billing — how credits are charged
- API Reference: Search — full request and response schema