Google Custom Search Engine closed to new signups and will end its search entire web feature on January 1, 2027. If your application relies on CSE for web search, you need a migration plan now. Scavio provides a drop-in replacement that covers Google, Amazon, YouTube, Walmart, Reddit, and TikTok from a single POST endpoint at $0.005 per request. This tutorial walks through every step: auditing your current CSE usage, mapping CSE parameters to Scavio equivalents, updating your code, and validating results.
Prerequisites
- An existing Google CSE integration you need to migrate
- Python 3.9+ or Node.js 18+ installed
- A Scavio API key from scavio.dev
- Access to your current CSE API key and search engine ID
Walkthrough
Step 1: Audit your current CSE usage
Before migrating, document every CSE call in your codebase. Record the query parameters you use, the response fields you parse, and your monthly request volume.
# Typical Google CSE call you need to replace
import requests
CSE_KEY = 'your-google-api-key'
CSE_ID = 'your-search-engine-id'
def old_cse_search(query: str) -> list:
resp = requests.get('https://www.googleapis.com/customsearch/v1', params={
'key': CSE_KEY, 'cx': CSE_ID, 'q': query, 'num': 10
})
items = resp.json().get('items', [])
return [{'title': i['title'], 'link': i['link'],
'snippet': i.get('snippet', '')} for i in items]
# Document what you actually use from the response
results = old_cse_search('best python frameworks 2026')
for r in results:
print(f"{r['title']} -> {r['link']}")Step 2: Create the Scavio replacement function
Replace the CSE GET request with a Scavio POST request. The response structure maps cleanly: items becomes organic_results, and each result has title, link, and snippet fields.
import os, requests
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
def scavio_search(query: str, num: int = 10) -> list:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us', 'num_results': num})
resp.raise_for_status()
results = resp.json().get('organic_results', [])
return [{'title': r['title'], 'link': r['link'],
'snippet': r.get('snippet', '')} for r in results]
results = scavio_search('best python frameworks 2026')
for r in results:
print(f"{r['title']} -> {r['link']}")Step 3: Map CSE-specific parameters
If you use CSE features like site restriction, date range, or language filtering, map them to Scavio query parameters or query syntax.
# CSE site restriction -> Scavio query syntax
# Old: params={'siteSearch': 'reddit.com', 'q': query}
# New: prepend site: to query
def search_site(query: str, site: str) -> list:
return scavio_search(f'site:{site} {query}')
# CSE country -> Scavio country_code
def search_country(query: str, country: str = 'us') -> list:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': country, 'num_results': 10})
return resp.json().get('organic_results', [])
# CSE date restrict -> Scavio query syntax
def search_recent(query: str) -> list:
return scavio_search(f'{query} after:2026-01-01')
print('Site search:', len(search_site('python tutorial', 'docs.python.org')))
print('Country search:', len(search_country('AI news', 'gb')))
print('Recent search:', len(search_recent('AI frameworks')))Step 4: Build a compatibility wrapper for zero-downtime swap
Create a wrapper that matches the exact CSE response format so downstream code needs no changes. This lets you swap the backend without touching consumers.
def cse_compatible_search(query: str, **kwargs) -> dict:
"""Drop-in replacement that returns CSE-format response."""
num = kwargs.get('num', 10)
results = scavio_search(query, num=num)
# Return CSE-compatible response shape
return {
'searchInformation': {
'totalResults': str(len(results)),
'searchTime': 0.3
},
'items': [{
'title': r['title'],
'link': r['link'],
'snippet': r['snippet'],
'displayLink': r['link'].split('/')[2] if '/' in r['link'] else ''
} for r in results]
}
# Your existing code works unchanged
response = cse_compatible_search('best python frameworks 2026')
for item in response['items']:
print(f"{item['title']} ({item['displayLink']})")
print(f"Total: {response['searchInformation']['totalResults']}")Step 5: Validate results and compare quality
Run both APIs in parallel for a week to verify result quality before cutting over completely. Log any discrepancies.
import json
from datetime import datetime
def compare_search(query: str) -> dict:
scavio_results = scavio_search(query, num=5)
scavio_urls = set(r['link'] for r in scavio_results)
comparison = {
'query': query,
'timestamp': datetime.now().isoformat(),
'scavio_count': len(scavio_results),
'scavio_top_3': [r['title'] for r in scavio_results[:3]],
}
return comparison
test_queries = [
'best python web framework 2026',
'how to deploy fastapi production',
'typescript vs python performance'
]
for q in test_queries:
result = compare_search(q)
print(f"Query: {q}")
print(f" Scavio: {result['scavio_count']} results")
print(f" Top 3: {result['scavio_top_3']}")
print()Python Example
import os, requests
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
def search(query, num=10):
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us', 'num_results': num})
resp.raise_for_status()
return resp.json().get('organic_results', [])
def cse_compat(query, **kw):
results = search(query, num=kw.get('num', 10))
return {'items': [{'title': r['title'], 'link': r['link'],
'snippet': r.get('snippet', '')} for r in results]}
resp = cse_compat('python frameworks 2026')
for item in resp['items']:
print(f"{item['title']} -> {item['link']}")
print(f'Total: {len(resp["items"])} results at $0.005')JavaScript Example
const SCAVIO_KEY = process.env.SCAVIO_API_KEY;
async function search(query, num = 10) {
const resp = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': SCAVIO_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, country_code: 'us', num_results: num })
});
return (await resp.json()).organic_results || [];
}
async function cseCompat(query) {
const results = await search(query);
return { items: results.map(r => ({ title: r.title, link: r.link, snippet: r.snippet || '' })) };
}
cseCompat('python frameworks 2026').then(resp => {
resp.items.forEach(i => console.log(`${i.title} -> ${i.link}`));
console.log(`Total: ${resp.items.length} results at $0.005`);
});Expected Output
best python frameworks 2026 -> https://example.com/...
Django vs FastAPI in 2026: Which Should You Choose?
-> https://blog.example.com/django-vs-fastapi-2026
Top 10 Python Web Frameworks for Production
-> https://example.com/top-python-frameworks
Total: 10 results at $0.005