Google Custom Search Engine is closed to new signups and its search entire web capability ends January 1, 2027. If you are reading this, your migration deadline is approaching. This is the emergency migration guide: swap your CSE calls for Scavio API calls in under an hour. Scavio provides Google-equivalent results plus Amazon, YouTube, Walmart, Reddit, and TikTok from the same endpoint. At $0.005 per request with 250 free monthly credits, you likely save money compared to CSE pricing.
Prerequisites
- A working CSE integration you need to replace
- Python 3.9+ or Node.js 18+ installed
- A Scavio API key from scavio.dev (free tier: 250 credits/month)
- 30-60 minutes for the migration
Walkthrough
Step 1: Map your CSE response fields to Scavio
The critical difference: CSE uses GET with query params, Scavio uses POST with JSON body. Response field mapping: items -> organic_results, searchInformation -> search_metadata, queries -> not needed.
# CSE response structure -> Scavio equivalent
field_mapping = {
# CSE field -> Scavio field
'items': 'organic_results',
'items[].title': 'organic_results[].title',
'items[].link': 'organic_results[].link',
'items[].snippet': 'organic_results[].snippet',
'items[].pagemap': 'organic_results[].rich_snippet',
'searchInformation.totalResults': 'search_metadata.total_results',
'spelling.correctedQuery': 'search_metadata.spelling_fix',
'queries.nextPage': 'pagination.next',
}
# CSE parameter mapping
param_mapping = {
# CSE param -> Scavio equivalent
'q': 'query',
'num': 'num_results',
'gl': 'country_code',
'hl': 'language',
'start': 'page (via pagination)',
'siteSearch': 'query prefix: site:domain.com',
'dateRestrict': 'query suffix: after:YYYY-MM-DD',
}
for cse, scavio in field_mapping.items():
print(f'{cse:45s} -> {scavio}')Step 2: Write the drop-in replacement function
Create a function with the same signature as your CSE wrapper. This handles the HTTP method change (GET to POST) and response field renaming.
import os, requests
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
def google_search(query: str, num: int = 10, country: str = 'us', **kwargs) -> dict:
"""Drop-in CSE replacement. Same return shape as CSE response."""
# Handle site restriction
site = kwargs.get('siteSearch', '')
if site:
query = f'site:{site} {query}'
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': num})
resp.raise_for_status()
data = resp.json()
# Return CSE-compatible structure
organic = data.get('organic_results', [])
return {
'items': [{'title': r['title'], 'link': r['link'],
'snippet': r.get('snippet', ''),
'displayLink': r['link'].split('/')[2] if '/' in r['link'] else ''}
for r in organic],
'searchInformation': {'totalResults': str(len(organic))}
}
result = google_search('python tutorial 2026')
print(f"Got {len(result['items'])} results")
for item in result['items'][:3]:
print(f" {item['title']}")Step 3: Find and replace all CSE calls
Search your codebase for CSE API calls and swap them. Common patterns include direct HTTP calls, Google API client library usage, and third-party CSE wrappers.
# Pattern 1: Direct requests call to CSE
# BEFORE:
# resp = requests.get('https://www.googleapis.com/customsearch/v1',
# params={'key': CSE_KEY, 'cx': CSE_ID, 'q': query})
# AFTER:
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': 10})
# Pattern 2: Google API Client Library
# BEFORE:
# from googleapiclient.discovery import build
# service = build('customsearch', 'v1', developerKey=CSE_KEY)
# result = service.cse().list(q=query, cx=CSE_ID).execute()
# AFTER:
result = google_search(query) # Use the wrapper from step 2
# Pattern 3: Environment variable cleanup
# Remove: GOOGLE_CSE_KEY, GOOGLE_CSE_ID
# Add: SCAVIO_API_KEY
import os
assert os.environ.get('SCAVIO_API_KEY'), 'Set SCAVIO_API_KEY env var'
print('Environment configured')Step 4: Run a side-by-side validation
Test your most common queries through both the old and new paths. Verify that result count, top results, and any fields you parse downstream all look correct.
def validate_migration(queries: list[str]):
print('Migration Validation Report')
print('=' * 50)
all_pass = True
for q in queries:
result = google_search(q)
items = result.get('items', [])
checks = {
'has_results': len(items) > 0,
'has_titles': all(i.get('title') for i in items),
'has_links': all(i.get('link') for i in items),
'has_snippets': all(i.get('snippet') for i in items[:3]),
}
status = 'PASS' if all(checks.values()) else 'FAIL'
if status == 'FAIL':
all_pass = False
print(f'\n[{status}] {q}')
print(f' Results: {len(items)}')
for check, passed in checks.items():
print(f' {check}: {"ok" if passed else "FAILED"}')
print(f'\nOverall: {"ALL PASS" if all_pass else "SOME FAILURES"}')
validate_migration([
'python web framework 2026',
'machine learning tutorial',
'best laptop for coding',
])Step 5: Update environment and deploy
Remove CSE credentials, add Scavio key, and deploy. The free tier gives you 250 requests per month to verify in production before committing to a paid plan.
# .env file update
# REMOVE these:
# GOOGLE_CSE_API_KEY=AIza...
# GOOGLE_CSE_ENGINE_ID=017...
# ADD this:
# SCAVIO_API_KEY=your_key_here
# Verify the migration
import os
def verify_deployment():
key = os.environ.get('SCAVIO_API_KEY')
assert key, 'SCAVIO_API_KEY not set'
# Quick health check
result = google_search('test query')
assert len(result['items']) > 0, 'No results returned'
print('Migration verified:')
print(f' API key: {key[:8]}...')
print(f' Test query returned {len(result["items"])} results')
print(f' Cost per request: $0.005')
print(f' Free tier: 250 requests/month')
print(f' Paid plan: $30/month for 7,000 credits')
# Cost comparison
print(f'\nCSE was: $5 per 1,000 queries')
print(f'Scavio: $5 per 1,000 queries (same price, more platforms)')
verify_deployment()Python Example
import os, requests
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
def google_search(query, num=10, **kw):
site = kw.get('siteSearch', '')
if site:
query = f'site:{site} {query}'
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 {'items': [{'title': r['title'], 'link': r['link'],
'snippet': r.get('snippet', '')} for r in results]}
result = google_search('best python frameworks 2026')
for item in result['items'][:5]:
print(f"{item['title']}\n {item['link']}")
print(f'\nCost: ${len(result["items"]) * 0.005:.3f} for this search')JavaScript Example
const SCAVIO_KEY = process.env.SCAVIO_API_KEY;
async function googleSearch(query, { num = 10, siteSearch = '' } = {}) {
if (siteSearch) query = `site:${siteSearch} ${query}`;
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 })
});
const data = await resp.json();
return { items: (data.organic_results || []).map(r => ({
title: r.title, link: r.link, snippet: r.snippet || ''
}))};
}
googleSearch('best python frameworks 2026').then(r => {
r.items.slice(0, 5).forEach(i => console.log(`${i.title}\n ${i.link}`));
});Expected Output
Migration Validation Report
==================================================
[PASS] python web framework 2026
Results: 10
has_results: ok
has_titles: ok
has_links: ok
has_snippets: ok
[PASS] machine learning tutorial
Results: 10
Overall: ALL PASS
Migration verified:
API key: sk-12345...
Test query returned 10 results
Cost per request: $0.005