If your app calls https://www.googleapis.com/customsearch/v1, it has a deadline. Google's Custom Search JSON API overview now says:
The Custom Search JSON API is closed to new customers.
Existing Custom Search JSON API customers have until January 1, 2027 to transition to an alternative solution.
This guide covers what your options are, how to move a Python or LangChain integration with two changed values, and — just as important — what will behave differently after the switch.
What Google offers instead
Google's overview page points to two paths:
- Vertex AI Search, which Google describes as "a favorable alternative for searching up to 50 domains."
- A full web search solution, for which the page says to contact Google. No public per-query price is listed for it; you request access through a form.
If you searched a handful of your own sites, Vertex AI Search may fit. If your engine searched the whole web — the common case for AI agents, research tools and RAG pipelines — the only self-serve option Google lists is going away.
Three ways forward
Option | What changes in your code |
|---|---|
Google full web search (by request) | New contract, new API; pricing on request |
A different search API | New request format and new response parsing |
A CSE-compatible endpoint | The endpoint URL and the key |
The rest of this guide covers the third option with Search1API's Google CSE compatible endpoint. It answers first-page web searches with Search1API's own Google engine and returns the items[].title, link and snippet fields CSE clients already read. It does not call Google's retiring API.
Migrate the Google Python client
The official google-api-python-client lets you override the API endpoint. Point it at Search1API and pass a Search1API key where the Google key used to go:
import os
from googleapiclient.discovery import build
with build(
"customsearch",
"v1",
developerKey=os.environ["SEARCH1API_API_KEY"],
client_options={
# Keep the trailing slash: the client appends customsearch/v1 itself.
"api_endpoint": "https://api.search1api.com/compat/google/",
},
) as service:
response = service.cse().list(
q="hono cloudflare workers",
cx="YOUR_OLD_ENGINE_ID",
num=5,
).execute()
for item in response.get("items", []):
print(item["title"], item["link"], item["snippet"])Your cse().list() call, your loop over items, and your HttpError handling stay the same.
Calling it over plain HTTP works too. You can send the key as key= like before, or as a bearer header:
curl --get 'https://api.search1api.com/compat/google/customsearch/v1' \
--data-urlencode 'q=hono cloudflare workers' \
--data-urlencode 'num=5' \
-H "Authorization: Bearer $SEARCH1API_API_KEY"Migrate LangChain's GoogleSearchAPIWrapper
GoogleSearchAPIWrapper (from langchain-google-community) has no endpoint setting. When it is created, it builds its own Google client with the default URL. You can replace that client right after construction:
import os
from googleapiclient.discovery import build
from langchain_google_community import GoogleSearchAPIWrapper
S1_KEY = os.environ["SEARCH1API_API_KEY"]
search = GoogleSearchAPIWrapper(
google_api_key=S1_KEY,
google_cse_id="YOUR_OLD_ENGINE_ID", # accepted, but see the cx note below
k=5,
)
search.search_engine = build(
"customsearch",
"v1",
developerKey=S1_KEY,
client_options={"api_endpoint": "https://api.search1api.com/compat/google/"},
)
print(search.results("hono cloudflare workers", num_results=5))run(), results() and the GoogleSearchRun / GoogleSearchResults tools built on the wrapper keep their output format, because they read the same title, link and snippet fields.
Two caveats:
- Leave
siterestrictat its default,False.Truesends requests to Google's old/siterestrictpath, which the compatible endpoint does not cover. results()also looks forpagemapthumbnails. The compatible endpoint omitspagemap, so theimageandthumbnailkeys simply won't appear.
We checked this with langchain-google-community 5.1.1 and google-api-python-client 2.201.0 against a local stub: both run() and results() sent their requests to /compat/google/customsearch/v1, with the key in key=. Run your own representative queries before switching production traffic.
What carries over, and what doesn't
A compatible format doesn't mean full CSE compatibility. Check this table against what your code actually uses:
You use | After the switch |
|---|---|
| Works |
| Returned |
| Works for one site or URL prefix |
| Mapped to a language hint |
| Mapped to day / week / month / year; other values ignored |
|
|
|
|
|
|
| Not returned |
| Omitted, not faked |
| Ignored |
Any option the endpoint ignores shows up by name in the X-Search1API-Ignored response header. That makes it easy to spot gaps while you test.
Your cx does not come with you
This is the step people most often miss. cx is accepted, but only as an opaque value: it does not import the sites, exclusions, refinements or ranking rules saved in your Google engine. Without a filter, a request searches the whole web.
If your engine was limited to certain sites, recreate that restriction:
- One site or URL prefix: add
siteSearch=docs.example.com(andsiteSearchFilter=eto exclude instead). - Several sites: use Search1API's native `/search` with
include_sitesandexclude_sites.
Cost and errors
- A completed search costs 1 credit, even when it returns no results. Searches that fail for technical reasons are not charged.
- Errors use Google's envelope (
{"error": {"code": …, "errors": [...]}}), so existingHttpErrorhandling keeps working. The status codes are Search1API's:400invalid or unsupported,401bad key,402out of credits,403forbidden,429rate limited (withRetry-After).
Migration checklist
- Confirm you call the Custom Search JSON API. Google's embedded Search Element (the JavaScript widget) is not covered.
- Write down the site restrictions stored behind your
cxand recreate them withsiteSearchor the native API. - Change the endpoint and key. Keep your
cse().list()calls anditemsparsing. - Remove pagination loops and image-search calls from this path, and check the ignored options in
X-Search1API-Ignored. - Run representative queries, test your error handling, then switch production traffic — well before January 1, 2027.
FAQ
Why do I get 403 from Google with a brand-new key?
Google's overview says the API is closed to new customers. If your project wasn't already using the API, you can't start now; plan a migration instead.
Can I keep using the Google Python SDK?
Yes. Set client_options={"api_endpoint": "https://api.search1api.com/compat/google/"} (with the trailing slash) and use a Search1API key.
Does this still depend on Google's retiring API?
No. The endpoint runs on Search1API's own Google engine and does not call the Custom Search JSON API, so the January 1, 2027 shutdown does not affect it.
Will my results be identical?
Not necessarily. Results and ranking can differ from CSE, and fields like pagemap and total-result estimates are omitted. Compare a sample of real queries before switching.
Can I get more than 10 results?
Not from this endpoint, which serves the first page only. If you need more, use the native Search API.
Get started
Create an account at app.s1.dev to get 100 free credits with no card required — enough for 100 test searches. The full parameter reference is in the Google CSE compatibility docs.
No comments yet