Google Custom Search API in PHP
The call that still answers.
Google closed the Custom Search JSON API to new customers and ends it on January 1, 2027. A new PHP project gets no key, and code that calls googleapis.com/customsearch/v1 stops answering on that day.
SpecStream answers the same request at https://api.specstream.ai/customsearch/v1 in Google's JSON. In PHP you change the host in the URL and the key; items, title, link and snippet read as before, and any cx is accepted.
Searches cost $5 per 1,000 of up to 10 results, Google's own rate, with no 10,000 a day cap. SpecStream scores 97.9 F-score on all 1,000 SimpleQA Verified questions, on the benchmarks page.
New accounts get $10 of credit, no card. It covers 2,000 searches.
Custom Search from PHP: SpecStream, Google and Vertex AI Search
| SpecStream | Google Custom Search JSON API | Vertex AI Search | |
|---|---|---|---|
| The URL PHP calls | GET https://api.specstream.ai/customsearch/v1 with key, q, num and start. Any cx is accepted | GET https://www.googleapis.com/customsearch/v1 with an API key, q and cx, the ID of a Programmable Search Engine you set up | POST https://discoveryengine.googleapis.com/v1/{servingConfig}:search, with its own request body and reply |
| New keys | Yes. A key takes a minute and comes with $10 of credit, no card | No. Closed to new customers, and discontinued on January 1, 2027 | Yes. A free trial covers 10,000 queries per account a month |
| What it searches | The whole live web, read at the moment of the call. siteSearch keeps a search to one site | The Programmable Search Engine that cx names | Up to 50 domains. For full web search, Google asks you to contact it |
| Price | $5 per 1,000 searches of up to 10 results, with no daily cap | $5 per 1,000 queries after 100 free a day, up to 10,000 a day, for existing customers | $1.50 per 1,000 queries on Search Standard Edition, $4.00 on Search Enterprise Edition |
| What json_decode returns | Google's JSON: items with title, link, snippet, displayLink, htmlTitle, htmlSnippet and formattedUrl, plus searchInformation.totalResults and queries.nextPage | An items array with the URL, title and text snippets of each result | Its own reply shape. Search Enterprise Edition adds generated answers at $4.00 per 1,000 queries |
| Results a call | num from 1 to 10, with start for the next page, as on Google | 1 to 10 a call, and never more than 100 for one query | Billed per query: every API call to Enterprise Search counts as one |
| A busy or failed call | 429 in Google's error form with Retry-After, and not charged | Its terms say you may be charged for each successful request | A request is any API call, made directly, through an integration or in the console |
Why PHP code that called Custom Search moves to SpecStream
The search with PHP's curl extension
One request, Google's JSON back. A search that finds nothing has no items key, on Google and here, so the loop reads $data['items'] ?? [].
<?php
$params = http_build_query([
'key' => getenv('SPECSTREAM_API_KEY'),
'cx' => 'any',
'q' => 'open source vector databases',
'num' => 10,
]);
// was https://www.googleapis.com/customsearch/v1
$ch = curl_init("https://api.specstream.ai/customsearch/v1?$params");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 30]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($body, true);
if ($status !== 200) {
throw new RuntimeException($data['error']['message']);
}
echo $data['searchInformation']['totalResults'], " results\n";
foreach ($data['items'] ?? [] as $item) {
echo $item['title'], "\n ", $item['link'], "\n ", $item['snippet'], "\n";
}A refused call answers in Google's error object, with code, message, errors and status, so the error branch you already have stays as it is.
Without the curl extension: file_get_contents
ignore_errors lets PHP read the JSON body of a 4xx reply instead of returning false.
<?php
$url = 'https://api.specstream.ai/customsearch/v1?' . http_build_query([
'key' => getenv('SPECSTREAM_API_KEY'),
'cx' => 'any',
'q' => 'open source vector databases',
]);
$context = stream_context_create(['http' => ['timeout' => 30, 'ignore_errors' => true]]);
$data = json_decode(file_get_contents($url, false, $context), true);
foreach ($data['items'] ?? [] as $item) {
echo $item['title'], ' ', $item['link'], "\n";
}If the code uses google/apiclient
The client's CustomSearchAPI class takes the root address as the second argument of its constructor. Its source documents it as @param string $rootUrl The root URL used for requests to the service., and the constructor sets both rootUrl and rootUrlTemplate from it. Here is the same call before and after, with that one option set and the key swapped.
// Before: Google
$client = new Google\Client();
$client->setDeveloperKey(getenv('GOOGLE_API_KEY'));
$service = new Google\Service\CustomSearchAPI($client);
$results = $service->cse->listCse(['q' => 'open source vector databases', 'cx' => $cx]);
// After: the same call, with the constructor's $rootUrl
$client = new Google\Client();
$client->setDeveloperKey(getenv('SPECSTREAM_API_KEY'));
$service = new Google\Service\CustomSearchAPI($client, 'https://api.specstream.ai/');
$results = $service->cse->listCse(['q' => 'open source vector databases', 'cx' => $cx]);Tested on November 10, 2026 with google/apiclient 2.20.1: the client's own listCse call read 10 results from https://api.specstream.ai/. Pass the address as the constructor's second argument; setting $service->rootUrl after construction is not enough.
Paging, and a busy call
start counts from 1. While more results exist, the reply carries queries.nextPage[0].startIndex. A busy call answers 429 with Retry-After in Google's error form and costs nothing, so search() waits that many seconds and sends it again. Each page is one search at $0.005.
<?php
function search(string $q, int $start): array
{
$url = 'https://api.specstream.ai/customsearch/v1?' . http_build_query([
'key' => getenv('SPECSTREAM_API_KEY'), 'cx' => 'any', 'q' => $q, 'num' => 10, 'start' => $start,
]);
while (true) {
$retryAfter = 1;
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$retryAfter) {
if (stripos($line, 'Retry-After:') === 0) {
$retryAfter = (int) trim(substr($line, 12));
}
return strlen($line);
},
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status !== 429) {
return json_decode($body, true);
}
sleep(max(1, $retryAfter));
}
}
$start = 1;
for ($page = 0; $page < 3; $page++) {
$data = search('open source vector databases', $start);
foreach ($data['items'] ?? [] as $item) {
echo $item['link'], "\n";
}
$next = $data['queries']['nextPage'][0]['startIndex'] ?? null;
if ($next === null) {
break;
}
$start = $next;
}The parameters the route honours
Nine parameters shape the search: q, num, start, hq, exactTerms, orTerms, excludeTerms, siteSearch and siteSearchFilter. fileType, dateRestrict, lowRange and highRange are accepted and not applied. Any cx is accepted, so the engine ID in your config can stay. Every parameter and field is in the migration guide.
$5 per 1,000 searches, no daily cap
A call of up to 10 results costs $0.005. Google charges the same $5 per 1,000 and stops at 10,000 queries a day; SpecStream has no daily cap. It scores 97.9 F-score on all 1,000 SimpleQA Verified questions (978 correct) and 64.0 item F1 and 43.4 row F1 on all 200 WideSearch tasks. On production on November 9, 2026, the median search took 0.82 s at 1 a second and 1.07 s at 20 a second.
What a search leaves behind
With zero_retention on the key, a search keeps only the charge: account, key, request ID, price and time. The query text and the reply are not kept after the answer. Set it when you create the key, or later on the same key.
The same key reads pages and returns rows
The Extract API reads up to 20 pages or documents a call at $0.80 per 1,000 pages and scores 0.908 F1 on WCXB. Deep Search turns a request in plain words into structured rows at 5¢, 15¢ or $1 a row, and quotes the most a run can cost before it starts.
Other options for the Custom Search JSON API in PHP
Vertex AI Search
Google's pick for search over up to 50 domains, from $1.50 per 1,000 queries on Search Standard Edition. It is a POST to discoveryengine.googleapis.com with a different body and reply, so the PHP that reads items is rewritten. SpecStream answers the GET your code sends today, across the whole web.
Grounding with Google Search in the Gemini API
A Gemini model runs the search and writes an answer from it. On Gemini 3 and newer models it costs $14 per 1,000 search requests after 5,000 free a month, and PHP receives the model's text. SpecStream returns the result list itself at $5 per 1,000.
Brave Search API
Brave's own index at $5 per 1,000 requests, with $5 in free credits every month. Brave's guide calls the move swapping the endpoint and updating how you read the response. On SpecStream the PHP that reads Google's response stays as written.
How SpecStream scores
The same numbers as the benchmarks page, from the official tasks and scorers.
How to run the PHP example
1. Create a key under Agents & API in the workspace. A new account gets $10 of credit, no card, which covers 2,000 searches.
2. Put the key in the environment and check the route from the shell.
export SPECSTREAM_API_KEY="your key"
curl "https://api.specstream.ai/customsearch/v1?key=$SPECSTREAM_API_KEY&cx=any&q=open+source+vector+databases&num=3"3. Run the curl example above with php search.php. In code that already calls Google, replace https://www.googleapis.com or https://customsearch.googleapis.com with https://api.specstream.ai and the key with the SpecStream key. cx can stay.
4. With google/apiclient, pass 'https://api.specstream.ai/' as the second argument of new Google\Service\CustomSearchAPI(...), as shown above.
The same move in Python, with Google's own Python client, is on the Python page.
When your product needs every match instead of a page of links, write the request in plain words. SpecStream reads it back as the conditions every row must meet and the facts to bring back, quotes the most it can cost, and delivers the rows after you approve.
Frequently asked questions
Something we didn't answer? Talk to a human
How do I call the Google Custom Search API from PHP?
GET to /customsearch/v1 with key, cx and q using curl or file_get_contents, then json_decode the reply and read items. Google closed the API to new customers and ends it on January 1, 2027. The same request answers at https://api.specstream.ai/customsearch/v1 with a SpecStream key.Can I keep google/apiclient?
$rootUrl, the second argument of the CustomSearchAPI constructor, which defaults to https://customsearch.googleapis.com/. Set it to https://api.specstream.ai/ and use a SpecStream key with setDeveloperKey. Tested on November 10, 2026 with google/apiclient 2.20.1: the client's own listCse call read 10 results.When does the Custom Search JSON API stop working?
How do I get more than 10 results in PHP?
start set to 11, then 21, or read queries.nextPage[0].startIndex from each reply. Each page of up to 10 results is one search at $0.005.Do I still need a search engine ID (cx)?
cx value is accepted, so a config that sends one runs unchanged. SpecStream searches the whole live web, and siteSearch keeps a search to one site.How much does the Custom Search API cost from PHP?
What happens when the API is busy?
429 in Google's error form with a Retry-After header, and the call is not charged. Wait that many seconds and send it again, as the paging example does.Sources
SpecStream wrote this page. Each figure about Google comes from Google's own pages, the client setting from the client's own source, and each other figure from that company's own page.
- Google Custom Search JSON API overviewdevelopers.google.com/custom-search/v1/overview, read Nov 9, 2026
- Google Custom Search JSON API reference, cse.listdevelopers.google.com/custom-search/v1/reference/rest/v1/cse/list, read Nov 9, 2026
- Google Custom Search JSON API, using RESTdevelopers.google.com/custom-search/v1/using_rest, read Nov 9, 2026
- Google Custom Search terms of servicedevelopers.google.com/custom-search/terms, read Nov 9, 2026
- Programmable Search Engine blog: updates to our web search productsprogrammablesearchengine.googleblog.com/2026/01/updates-to-our-web-search-products.html, read Nov 9, 2026
- Google Cloud: Agent Search pricingcloud.google.com/generative-ai-app-builder/pricing, read Nov 9, 2026
- Google Cloud: servingConfigs.search methodcloud.google.com/generative-ai-app-builder/docs/reference/rest/v1/projects.locations.collections.engines.servingConfigs/search, read Nov 9, 2026
- Gemini API: Grounding with Google Searchai.google.dev/gemini-api/docs/google-search, read Nov 9, 2026
- Brave Search APIbrave.com/search/api/, read Nov 9, 2026
- Brave, alternatives to the Google Search APIbrave.com/learn/google-api-shutdown/, read Nov 9, 2026
- google/apiclient services: CustomSearchAPI.phpgithub.com/googleapis/google-api-php-client-services/blob/main/src/CustomSearchAPI.php, read Nov 10, 2026
- SpecStream benchmarksspecstream.ai/benchmarks, read Nov 10, 2026
- SpecStream pricingspecstream.ai/pricing, read Nov 10, 2026
- SpecStream for agentsspecstream.ai/agents.md, read Nov 10, 2026




