Custom Search JSON API shutdown · January 1, 2027
Google Custom Search JSON API shutdown: migrate in 5 minutes
Google's Custom Search JSON API is closed to new customers, and existing customers have until January 1, 2027 to move off it. If your code calls it, Searlo's drop-in endpoint takes the same request and answers in the same JSON and error format: change the host and the key, and keep the rest. Searlo returns Google results from the whole web, at 1 credit a call, $0.30 per 1,000 calls on the Scale pack.
When does the Google Custom Search JSON API stop working?
On January 1, 2027. Google's overview page says existing customers "have until January 1, 2027 to transition to an alternative solution" and calls that date "the service discontinuation". New customers cannot sign up at all.
Google gives no time of day and does not say what a request will return after that date, so plan to finish well before it. The separate Site Restricted JSON API (the /siterestrict endpoint) already stopped serving traffic on January 8, 2025.
- New customers
- Closed: “The Custom Search JSON API is closed to new customers.”— developers.google.com
- Existing customers
- “have until January 1, 2027 to transition to an alternative solution”— developers.google.com
- The service itself
- “the service discontinuation on January 1, 2027”— developers.google.com
- “Search the entire web” engines
- Existing engines can keep the option until January 1, 2027; new engines since January 20, 2026 must use “Sites to search” (up to 50 domains)— Programmable Search Engine blog
- Site Restricted JSON API (/siterestrict)
- “will cease to serve traffic on January 8, 2025”— developers.google.com
- Google's suggested replacement
- Vertex AI Search for up to 50 domains; for full web search, a form to register interest— developers.google.com
Third-party figures on this page last verified against the sources linked above. Prices change; if you find one of these stale, tell us and we will correct it.
Migration guides by stack
- Python: requests and google-api-python-clientChange the URL in requests, or pass one client_options argument to build(). Paging, errors and the siterestrict resource keep working.requests · googleapiclient
- Node.js: fetch and googleapisChange the URL in fetch, or pass rootUrl to google.customsearch(). The response and error objects keep their shape.fetch · googleapis
- PHP: cURL and google/apiclientChange the URL in your cURL call, or pass the root URL as the second argument to Google\Service\CustomSearchAPI.cURL · google/apiclient
- LangChain: GoogleSearchAPIWrapper and GoogleCustomSearchPython: replace the wrapper's client with one that points at Searlo. JavaScript: swap GoogleCustomSearch for a ten-line tool with the same output.langchain-google-community · LangChain.js
- Open WebUI: the Google PSE web search engineOpen WebUI's google_pse engine has Google's URL in its code. Change that one line, mount the file, and put your Searlo key in the Google PSE settings.google_pse · Docker
- n8n: HTTP Request workflowsn8n has no built-in Custom Search node; workflows call it with HTTP Request. Change the URL and the key in that node, and every later node keeps its fields.HTTP Request · HTTP Request Tool
- WordPress: plugins that use a Custom Search keyA ten-line must-use plugin sends any plugin's Custom Search requests to Searlo and keeps results on your own site. Tested with Search with Google.mu-plugin · Search with Google
The change: the host and the key
Point the request at api.searlo.tech instead of www.googleapis.com (or customsearch.googleapis.com, the host Google's client libraries use) and put your Searlo API key where the Google key was. The path, the parameter names, the response JSON and the error format stay as they are. With a client library, it is one option.
- https://www.googleapis.com/customsearch/v1?key=GOOGLE_API_KEY&cx=YOUR_CX&q=lectures
+ https://api.searlo.tech/customsearch/v1?key=SEARLO_API_KEY&cx=YOUR_CX&q=lectures- service = build("customsearch", "v1", developerKey=GOOGLE_API_KEY)
+ service = build("customsearch", "v1", developerKey=SEARLO_API_KEY,
+ client_options={"api_endpoint": "https://api.searlo.tech"})- const customsearch = google.customsearch({ version: "v1", auth: GOOGLE_API_KEY });
+ const customsearch = google.customsearch({ version: "v1", auth: SEARLO_API_KEY,
+ rootUrl: "https://api.searlo.tech/" });- $client->setDeveloperKey($googleApiKey);
- $service = new Google\Service\CustomSearchAPI($client);
+ $client->setDeveloperKey($searloApiKey);
+ $service = new Google\Service\CustomSearchAPI($client, 'https://api.searlo.tech/');Migrate in five steps
- 01Get a Searlo API keySign up at dashboard.searlo.tech and create a key. New accounts get 3,000 free credits, enough for 3,000 calls; no card, no Google Cloud project, no search engine to configure.
- 02Change the hostReplace https://www.googleapis.com (or https://customsearch.googleapis.com) with https://api.searlo.tech. The path stays /customsearch/v1, and /customsearch/v1/siterestrict works too.
- 03Replace the keyPut your Searlo key (it starts with sk_) in the key parameter. If you can set headers, send it as x-api-key instead and keep it out of URLs and logs.
- 04Keep results on your sites, if your engine didcx is accepted and has no effect, because an engine's site list lives in your Google console. If your engine searched a list of sites, pass siteSearch=example.com, or site:a.com OR site:b.com in q.
- 05Run your testsYour parser, paging (queries.nextPage) and error handling should pass unchanged. Parameters Searlo accepts with no effect are named in the X-Searlo-Ignored-Params response header, and a parameter it cannot honour returns a 400 that names it, instead of being dropped silently.
What breaks on January 1, 2027
Any code that calls https://www.googleapis.com/customsearch/v1, directly or through Google's client libraries: google-api-python-client, googleapis for Node.js, google/apiclient for PHP and the others built from the same discovery document. Google's overview page speaks of "the service discontinuation", with no exception for engines that search only your own sites.
So does every tool that wraps that API: LangChain's GoogleSearchAPIWrapper and GoogleCustomSearch, Open WebUI's google_pse web search engine, n8n workflows that call it with an HTTP Request node, and WordPress plugins that ask for a Custom Search API key. Each one needs a key that still works after the cut-off, and the guides above show where each keeps its URL and key.
The Programmable Search Element, the search box you embed on your own site with Google's script, is a different product. Google's January 2026 post says it remains the solution for up to 50 domains in "Sites to search", while engines set to "Search the entire web" can keep that option only until January 1, 2027. The drop-in replaces the JSON API, not the embedded box.
For Google's whole timeline, quoted and dated, and for moving to Searlo's native web search API instead of the drop-in, read the step-by-step shutdown migration guide on our blog, linked at the end of this page.
Parameter compatibility
| Parameter | On Searlo | Notes |
|---|---|---|
| key | Supported | Your Searlo API key (sk_…). The x-api-key header and Authorization: Bearer also work, and keep it out of URLs |
| q | Supported | Required. Up to 500 characters, and 2,048 once the filter parameters below are added as operators |
| num | Supported | 1 to 10, default 10 |
| start | Supported | 1-based. As on Google, the last result can be at most the 100th: start=91&num=10 is the last full page |
| gl | Supported | Two-letter country code |
| cr | Supported | One country as countryXX, read as gl. A boolean expression, or a gl naming another country, returns 400 |
| hl | Supported | Interface language |
| lr | Supported | One language as lang_xx, used as the request language rather than a strict filter on the document's language. A boolean expression, or an hl in another language, returns 400 |
| safe | Supported | active or off (the legacy high and medium mean active) |
| dateRestrict | Supported | d[n], w[n], m[n] or y[n]. Returns 400 with searchType=image, or with the operator parameters (siteSearch, fileType, orTerms…) |
| siteSearch | Supported | A domain or URL prefix, applied as site: |
| siteSearchFilter | Supported | i (include, the default) or e (exclude, applied as -site:) |
| exactTerms | Supported | Applied as a quoted phrase |
| excludeTerms | Supported | Applied as -word, or -"phrase" for a phrase |
| orTerms | Supported | Applied as (a OR b) |
| fileType | Supported | Applied as filetype:; several types become an OR |
| hq | Supported | Appended to the query, as Google documents |
| lowRange | Supported | Sent with highRange, appended as low..high |
| highRange | Supported | Sent with lowRange, appended as low..high |
| searchType | Supported | image, for image search |
| imgSize | Supported | Image search: icon, small, medium, large, xlarge, xxlarge, huge |
| imgType | Supported | Image search: clipart, face, lineart, stock, photo, animated |
| imgColorType | Supported | Image search: color, gray, mono, trans |
| imgDominantColor | Supported | Image search: black, blue, brown, gray, green, orange, pink, purple, red, teal, white, yellow |
| cx | Accepted, no effect | Searlo searches the whole web: an engine's site list lives in your Google console. Restrict results with siteSearch, or site: in q |
| googlehost | Accepted, no effect | Deprecated by Google |
| filter | Default value only | filter=1 is accepted; any other value returns 400. Results always come with Google's duplicate-content filter on (filter=1). |
| c2coff | Default value only | c2coff=0 is accepted; any other value returns 400. Simplified/Traditional Chinese search cannot be switched off (c2coff=0 only). |
| sort | Default value only | An empty sort= is accepted; any other value returns 400. Sorting by structured data (sort=date and friends) needs a Google-hosted engine. |
| enableAlternateSearchHandler | Default value only | enableAlternateSearchHandler=false is accepted; any other value returns 400. There is no alternate search handler. |
| linkSite | Returns 400 | Google retired the link: operator this parameter relies on. |
| relatedSite | Returns 400 | Google retired the related: operator this parameter relies on. |
| rights | Returns 400 | Licence (Creative Commons) filtering is not available. |
| snippetLength | Returns 400 | Not taken: an unknown parameter returns Google's own 400 ("Unknown name") |
| alt | Supported | json only |
| fields | Supported | Google's partial-response syntax, for example items(title,link) |
| prettyPrint | Supported | true or false |
| callback | Returns 400 | JSONP is not supported. Call the API from a server and keep the key there. |
| quotaUser | Accepted, no effect | Google's quota plumbing |
| userIp | Accepted, no effect | Google's quota plumbing |
| location, uule, near, city and other city or point locations | Returns 400 | Location is country level only, through gl or cr |
The response
| Field | On Searlo |
|---|---|
| kind | customsearch#search |
| url.template | Google's template, pointing at api.searlo.tech |
| queries.request[0] | title, totalResults (a string), searchTerms, count, startIndex, inputEncoding, outputEncoding, safe, cx, and the parameters you sent, under Google's names |
| queries.nextPage[0] | Present only when more results exist within the first 100 |
| queries.previousPage[0] | Present when start is past 1 |
| context.title | Searlo |
| searchInformation | searchTime, formattedSearchTime, totalResults (a string), formattedTotalResults |
| items[] | kind, title, htmlTitle, link, displayLink, snippet, htmlSnippet, formattedUrl (the full link), htmlFormattedUrl. Left out when nothing matched, as on Google |
| items[].pagemap | cse_thumbnail only, when the result has a thumbnail |
| items[] for searchType=image | mime, fileFormat, image.contextLink, plus image width, height, thumbnailLink, thumbnailWidth and thumbnailHeight when reported |
| Not returned | cacheId, labels, promotions, spelling, queries.request[0].startPage, image.byteSize, and pagemap entries other than cse_thumbnail |
Errors
| When | HTTP status | errors[0].reason |
|---|---|---|
| A parameter is missing or invalid | 400 | badRequest, invalid, required, unsupportedParameter, cityLocationUnavailable |
| The key is not a Searlo key (a Google AIza… key included) | 400 | keyInvalid |
| No key | 403 | forbidden |
| Account suspended, or email not confirmed | 403 | forbidden |
| Out of credits: clears when you top up, not at midnight | 403 | dailyLimitExceeded |
| Rate limit, or too many searches in flight | 429 | rateLimitExceeded, with Retry-After |
| Search temporarily unavailable | 503 | backendError, with Retry-After |
| Internal error | 500 | backendError |
| Unknown path under /customsearch | 404 | notFound |
Check it in one minute
Two calls with your own key: the first shows Custom Search JSON coming back, the second shows which of your parameters had no effect.
# 1. Your key works and the answer is Custom Search JSON
curl -s "https://api.searlo.tech/customsearch/v1?key=$SEARLO_API_KEY&cx=YOUR_CX&q=lectures&num=3" \
| python3 -c 'import json,sys; d=json.load(sys.stdin); print(d.get("kind"), [i["link"] for i in d.get("items", [])])'
# 2. Parameters Searlo accepts with no effect (cx and friends) are named in a response header
curl -s -o /dev/null -D - "https://api.searlo.tech/customsearch/v1?key=$SEARLO_API_KEY&cx=YOUR_CX&q=lectures&num=1" \
| grep -i x-searlo-ignored-params
What it costs
| Pack | Price | Credits | Custom Search calls | Per 1,000 calls |
|---|---|---|---|---|
| Free | $0 | 3,000 | 3,000 | Free |
| Micro | $3.99 | 5,000 | 5,000 | $0.80 |
| Starter | $9.99 | 20,000 | 20,000 | $0.50 |
| Builder | $29.99 | 75,000 | 75,000 | $0.40 |
| Scale | $74.99 | 250,000 | 250,000 | $0.30 |
| Pro | $199.99 | 900,000 | 900,000 | $0.22 |
| Enterprise | $799 | 4,000,000 | 4,000,000 | $0.20 |
| Google Custom Search JSON API (until January 1, 2027) | Per use | — | 100 a day free, then up to 10,000 a day | $5.00 |
What is different from Google
Searlo is a separate service that returns Google results; it is not Google's API on another address. These differences are deliberate, and each one is in the tables above:
- cx has no effect: results come from the whole web. Restrict with siteSearch or site:
- Ranking, snippets and totalResults can differ from your engine
- Location is country level (gl, cr); city locations return 400
- Boolean cr and lr expressions return 400; one country and one language per call
- q is limited to 500 characters
- Out of credits is 403 dailyLimitExceeded until you top up, not until midnight
- Call it from a server: browsers on other sites are blocked by CORS, and JSONP is refused
- Responses carry X-Request-ID; quote it to support
FAQ
Frequently asked questions
When does the Google Custom Search JSON API shut down?
Google's overview page says existing customers have until January 1, 2027 to transition, and calls that date "the service discontinuation". It is already closed to new customers, and Google has not said what a request will return after the cut-off. Facts checked on October 25, 2026.
Will my existing code work if I only change the URL?
If it calls /customsearch/v1 and reads the standard fields (items, title, link, snippet, searchInformation, queries.nextPage, error.code), yes: put your Searlo key where the Google key was and the request, response and error shapes match. Check three things: an engine that searched a site list needs siteSearch, a few parameters (linkSite, rights, sort=date, city locations) return 400, and fields such as cacheId, labels and spelling are not returned.
Do I still need a search engine ID (cx)?
No. cx is accepted so existing code does not have to change, and it has no effect: Searlo searches the whole web. It is echoed back in queries.request[0].cx and named in the X-Searlo-Ignored-Params header.
Does it work with Google's client libraries?
Yes, with one option, because the drop-in serves Google's own paths. In Python, pass client_options={"api_endpoint": "https://api.searlo.tech"} to build(); in Node.js, pass rootUrl: "https://api.searlo.tech/" to google.customsearch(); in PHP, pass 'https://api.searlo.tech/' as the second argument to Google\Service\CustomSearchAPI. We ran each of them before publishing; the Python, Node.js and PHP guides have the full code.
What about the /siterestrict endpoint?
Google's Site Restricted JSON API stopped serving traffic on January 8, 2025, according to its own page. Searlo answers /customsearch/v1/siterestrict with the same search as /customsearch/v1, so code still pointed at it works again once you change the host; restrict results with siteSearch, since cx has no effect.
Can I use it for search on my own website?
Yes, by adding siteSearch=yourdomain.com to each call (or site: in q). The WordPress guide has a must-use plugin that does this for plugins that call the API through WordPress. The embedded Programmable Search Element is a different product and is not replaced by the drop-in.
How much does it cost?
1 credit per call, the same as a Searlo web search, for up to 10 results. That is $0.30 per 1,000 calls on the Scale pack, with 3,000 free credits at sign-up and no 10,000-a-day cap. Google's rate for existing customers is $5 per 1,000 after 100 free queries a day, up to 10,000 a day.
Is Searlo affiliated with Google?
No. Searlo is an independent service that returns Google results through its own API. The drop-in uses Custom Search's request and response format so that existing code keeps working; Google is a trademark of Google LLC.
Move before the cut-off
Create a key, change the host, run your tests. 3,000 free credits, no card.