DEV Community

Cover image for Google Custom Search JSON API is closing. Here's how to migrate to live organic results
Adam Yang for Serper.live

Posted on Originally published at serper.live

Google Custom Search JSON API is closing. Here's how to migrate to live organic results

Disclosure: I build Serper.live, the API this guide migrates to. The parameter mapping and the steps apply to any live SERP API.

According to Google's documentation, the Custom Search JSON API is closed to new customers, and existing customers have until January 1, 2027 to transition to an alternative solution.

If your integration used Custom Search to see what Google ranks for a query, a live SERP API is the closer fit. It's not a drop-in replacement, though: the endpoint, the authentication, the parameters and the response shape all change. Plan for a small adapter, not a URL swap. Below is the mapping, a Python adapter you can paste in, and the steps I'd follow.

What is changing

The Custom Search JSON API returns web and image results from a Programmable Search Engine, the engine you configure and identify with cx. It's this JSON API access to those results that is closing.

Dates and terms are Google's and can change, so confirm them on Google's page before you set a deadline.

Two different use cases

You showed what Google ranks for a query (rank tracking, SEO tools, market research). You need a live SERP API: it runs a real Google search for a country and device and returns the organic results shown at that moment.

You powered search inside your own site or across a fixed list of domains. You need a site search product instead. Google's documentation points to Vertex AI Search for that case.

The rest of this post covers the first case.

Parameter mapping

Custom Search JSON API on the left, Serper.live on the right:

Custom Search Serper.live Notes
q query The keyword, 2–120 characters
gl country Two-letter code of an open market, from GET /v1/countries
hl language Google's interface language. Optional, defaults to the country's language
num limit Organic results per request, 1–100, default 10
start — No paging: raise limit, and one call returns up to 100 results
cx — Not needed: there is no engine to configure
key Authorization: Bearer sl_live_… Token in a header instead of the query string
items[] organic[] link → url, displayLink → roughly domain. title and snippet keep their names; snippet can be null

Open markets as of October 2026: the US, the UK, Canada, Australia, Ireland, India, Germany, Austria, Switzerland, France, Belgium, the Netherlands, Spain, Italy, Portugal, Poland, Sweden, Denmark, Norway, Finland, Brazil, Mexico, Türkiye and Ukraine. GET https://api.serper.live/v1/countries returns the current list without a token.

The request

The whole integration is one POST:

curl -X POST https://api.serper.live/v1/search \
  -H "Authorization: Bearer sl_live_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query":"best coffee shops","country":"US","limit":10}'
Enter fullscreen mode Exit fullscreen mode

The response, shortened to two results:

{
  "requestId": "…",
  "query": "best coffee shops",
  "country": "US",
  "language": "en",
  "device": "desktop",
  "fetchedAt": "2026-10-27T10:00:00.000Z",
  "latencyMs": 1873,
  "pages": 1,
  "organic": [
    {
      "position": 1,
      "title": "The 12 best coffee shops in town",
      "url": "https://example.com/guides/best-coffee-shops",
      "domain": "example.com",
      "snippet": "A curated list of specialty coffee shops, updated every month."
    },
    {
      "position": 2,
      "title": "Coffee shops near me",
      "url": "https://maps.example.org/coffee",
      "domain": "maps.example.org",
      "snippet": null
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

position counts organic results only, continuously across Google's result pages, so it's the number a rank tracker stores. pages is how many blocks of 10 were charged.

A drop-in adapter for existing code

If the rest of your code reads Custom Search items, keep it unchanged and swap only the call:

import uuid

import requests

API_URL = "https://api.serper.live/v1/search"
TOKEN = "sl_live_YOUR_TOKEN"


def custom_search(q, gl="us", hl=None, num=10, idempotency_key=None):
    """Returns results in the shape of Custom Search `items[]`."""
    body = {"query": q, "country": gl.upper(), "limit": num}
    if hl:
        body["language"] = hl

    response = requests.post(
        API_URL,
        headers={
            "Authorization": f"Bearer {TOKEN}",
            "Idempotency-Key": idempotency_key or str(uuid.uuid4()),
        },
        json=body,
        timeout=100,
    )
    response.raise_for_status()

    return [
        {
            "title": result["title"],
            "link": result["url"],
            "displayLink": result["domain"],
            "snippet": result["snippet"] or "",
        }
        for result in response.json()["organic"]
    ]


for item in custom_search("best coffee shops", gl="gb"):
    print(item["link"])
Enter fullscreen mode Exit fullscreen mode

If you retry after a network error, pass the same idempotency_key to the retry: a repeated key is never charged twice.

Migration steps

  1. Create an account and an API token. Credit is prepaid: by card from $10 or in USDT or USDC from $5, no subscription.
  2. Replace the GET to the Custom Search endpoint with POST https://api.serper.live/v1/search, a JSON body and the Authorization header.
  3. Map the parameters as in the table; drop cx and start.
  4. Read organic[] instead of items[], rename link to url, and handle a null snippet.
  5. Raise the HTTP timeout to at least 100 seconds and queue the call instead of running it inside a user's request. A live search usually takes 2–3 seconds; the first one for a market after a pause takes longer.
  6. Send an Idempotency-Key, so a repeated call is never charged twice.
  7. Run both side by side for a few days, compare the results, then switch.

What it costs

Billing is per page of up to 10 organic results: from $0.01 per page depending on the market, so a top-10 check costs one price and a top-100 check costs ten. Credit never expires.

FAQ

Is it a drop-in replacement for the Custom Search JSON API?

No. The endpoint, the authentication, the parameters and the response shape are different, and the results come from a live Google search rather than an engine you configure. The adapter above is the small layer you need.

Does the shutdown affect existing Custom Search customers?

According to Google's documentation, yes: existing customers have until January 1, 2027 to transition. Check Google's page for the current wording before you fix a date.

Can I restrict results to my own site like a Programmable Search Engine?

Not by configuration. A live SERP API searches Google for the market and returns the organic results up to limit. Filter organic[] by domain on your side. Whether Google honours search operators in the keyword is Google's call, so test it in the playground first: three live searches a day, no account needed.


Originally published at serper.live.

Top comments (0)