Google Custom Search compatible API
A drop-in replacement for the Google Custom Search JSON API (cse.list). Same query parameters, same customsearch#search response with items[], searchInformation and queries.nextPage, so existing Custom Search code keeps working after you change the host to api.scavio.dev and use your Scavio key. 1 credit per request.
Authorizations
AuthorizationstringheaderrequiredBearer authentication header of the form Bearer <token>, where <token> is your Scavio API key (e.g. Bearer sk_live_your_key).
Parameters
query string (GET) or JSON body (POST)qstringrequiredThe search query. Up to 500 characters once the operator parameters below are appended.
Example: how to make cold brew coffee
keystringqueryYour Scavio API key, accepted in the query string on the Custom Search paths only, because every Custom Search client sends it there. An Authorization: Bearer header wins if both are sent. Prefer the header in new code: a key in a URL can end up in proxy logs. Scavio never logs or stores it.
cxstringAccepted and echoed back in queries.request[0].cx. Results always come from the whole web; restrict to a site with siteSearch. Engine refinements, weighting and promotions have no equivalent.
numintegerdefault:10Results to return, 1-10. Sliced from one Google results page, so fewer can come back when Google shows fewer organic results. Outside 1-10 is a 400.
startintegerdefault:11-based index of the first result, as in Custom Search. start + num cannot exceed 100 (400), and queries.nextPage stops at that limit, so a while-nextPage loop costs what it cost on Custom Search. For deeper pages use POST /api/v2/google.
glstringTwo-letter country code to geolocate results.
Example: us
hlstringInterface language.
Example: en
lrstringLanguage restrict, a single value such as lang_en.
crstringCountry restrict, a single value such as countryUS. Boolean expressions longer than 20 characters return 400.
safeenum<string>default:offactive filters explicit results; off (or omitted) does not. Legacy high and medium mean active.
filterenum<string>0 turns off duplicate-content filtering, 1 keeps it.
dateRestrictstringd[N], w[N], m[N] or y[N]. d1, w1, m1 and y1 map to Google's past day / week / month / year filter; any other window becomes an after:YYYY-MM-DD bound (UTC) on the query.
Example: m6
siteSearchstringSite to include or exclude, e.g. stackoverflow.com. Added to the query as site: and also enforced on the returned results, because Google loosens site: for some queries. Off-site results are dropped, so items can be fewer than num, even zero, and the request still costs 1 credit.
siteSearchFilterenum<string>default:ii includes siteSearch, e excludes it (-site: plus the same result filter).
exactTermsstringA phrase every result must contain, appended as "phrase".
excludeTermsstringA word or phrase no result may contain, appended as -word or -"phrase".
orTermsstringSpace-separated terms, at least one of which must appear, appended as a OR b.
hqstringExtra terms ANDed onto the query.
fileTypestringAdds filetype:<ext> to the query (e.g. filetype:pdf). Google usually honours it, but it is not enforced on the returned results, so a non-matching page can appear.
Example: pdf
googlehoststringGoogle domain to search, e.g. google.co.uk.
locationstringScavio extension: canonical location name for city-level results.
Example: Austin,Texas,United States
deviceenum<string>Scavio extension: desktop or mobile results.
nfprbooleanScavio extension: true turns off Google's spelling autocorrection.
no_cachebooleanScavio extension: true skips the 24-hour result cache.
Notes
Three paths, one handler. GET /customsearch/v1 and GET /customsearch/v1/siterestrict sit at Google's own paths, so swapping www.googleapis.com (or customsearch.googleapis.com) for api.scavio.dev is the whole migration. The canonical path is GET or POST /api/v1/google/customsearch; POST takes the same parameter names as a JSON body.
Google's own client works unchanged with client_options={"api_endpoint": "https://api.scavio.dev"} and your Scavio key as developerKey, for both cse().list() and cse().siterestrict().list().
# pip install google-api-python-client
# Your existing Custom Search code. Only the endpoint and the key change.
from googleapiclient.discovery import build
service = build(
"customsearch",
"v1",
developerKey="sk_live_your_scavio_key",
client_options={"api_endpoint": "https://api.scavio.dev"},
)
res = service.cse().list(q="how to make cold brew coffee", cx="your-cx", num=3).execute()
for item in res.get("items", []):
print(item["title"], item["link"])Ignored, never an error: alt=json, prettyPrint, fields, quotaUser, userIp, $.xgafv, c2coff. cx is echoed back.
Rejected with 400, never silently dropped: searchType (image search), imgSize, imgType, imgColorType, imgDominantColor, rights, linkSite, relatedSite, sort, lowRange, highRange, a cr expression over 20 characters, and any parameter not listed on this page. The error names the parameter in error.details.param. A 400 costs nothing.
Response fields.
items[]:kind,title,htmlTitle,link,displayLink(the host of link, www. kept),snippet(always present, "" when Google shows none),htmlSnippet,formattedUrl,htmlFormattedUrl. htmlTitle and htmlSnippet are HTML-escaped with Google's highlighted words in<b>.pagemap.cse_thumbnail[0].srconly when the result has a real thumbnail URL.searchInformation.totalResultsis a string, as in Custom Search.queries.request,queries.nextPageandqueries.previousPagecarry startIndex, count and the parameters you sent; searchTerms is your original q.itemsis omitted when there are no results, as Custom Search does.- Not returned:
cacheId,mime,fileFormat,labels,image,spelling,context,promotions. scavio: credits_used, credits_remaining, cached and response_time. Not part of the Custom Search format; parsers ignore it.
Errors use Google's envelope (error.code, error.message, error.errors[0].reason, error.status) with Scavio's HTTP status: 400 invalid, 401 keyInvalid, 402 dailyLimitExceeded (out of credits), 429 rateLimitExceeded (plan concurrency, with a Retry-After header), 502/503 backendError. Extra fields such as plan and billing_url sit in error.details.
Billing and cache. 1 credit per request, the same as POST /api/v2/google, and the two share one 24-hour result cache. There is no daily cap; your plan's concurrency limit applies.
Need more than Custom Search ever returned? The knowledge graph, AI Overview, People Also Ask, ads and related searches come back from POST /api/v2/google, which takes query instead of q and returns organic_results[]. The migration guide maps every parameter and field.
SDKs and agents. Python client.google.custom_search(), JavaScript client.google.customSearch() and the MCP tool google_custom_search call this endpoint.
Request
curl 'https://api.scavio.dev/customsearch/v1?q=how+to+make+cold+brew+coffee&cx=your-cx&num=3' \
-H 'Authorization: Bearer sk_live_your_key'Response