Google Custom Search Engine (CSE) has been the default programmatic search option for years, but its limitations are well known: 100 queries/day free tier, $5/1000 queries, restricted to 10 results per page, and no SERP features like People Also Ask or AI Overviews. With Google announcing changes to CSE in 2026, teams need a migration plan. The Scavio API provides a drop-in replacement with richer data at comparable or lower cost. This tutorial walks through the complete migration.
Prerequisites
- Python 3.9+ installed
- Existing Google CSE integration to migrate
- A Scavio API key from scavio.dev
- requests library installed
Walkthrough
Step 1: Map your CSE API calls to the new format
Google CSE uses GET requests with query parameters. The replacement uses POST with a JSON body. Map each parameter to its equivalent.
# Google CSE format:
# GET https://www.googleapis.com/customsearch/v1
# ?key=YOUR_CSE_KEY
# &cx=YOUR_CX_ID
# &q=search+query
# &num=10
# &start=1
# &gl=us
# Equivalent Scavio format:
# POST https://api.scavio.dev/api/v1/search
# Headers: x-api-key: YOUR_SCAVIO_KEY
# Body: {"query": "search query", "country_code": "us"}
param_mapping = {
'q': 'query', # search query
'gl': 'country_code', # geolocation
'num': 'N/A', # returns all results by default
'start': 'N/A', # no pagination needed
'cx': 'N/A', # no custom engine ID needed
'key': 'x-api-key', # header instead of param
}
for cse_param, scavio_param in param_mapping.items():
print(f'CSE: {cse_param} -> Scavio: {scavio_param}')Step 2: Build the drop-in replacement function
Create a function that matches your existing CSE call signature. Your application code only needs to swap the import.
import requests, os
API_KEY = os.environ['SCAVIO_API_KEY']
def google_search(query: str, num: int = 10, gl: str = 'us', **kwargs) -> dict:
"""Drop-in replacement for Google CSE search.
Same parameters, same return format."""
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': gl})
resp.raise_for_status()
data = resp.json()
# Map to CSE-compatible format
items = [{
'title': r['title'],
'link': r['link'],
'snippet': r.get('snippet', ''),
'displayLink': r.get('link', '').split('/')[2] if '/' in r.get('link', '') else '',
} for r in data.get('organic_results', [])[:num]]
return {
'items': items,
'searchInformation': {
'totalResults': str(len(items)),
'searchTime': 0.5
}
}Step 3: Handle CSE-specific features
Some CSE features like site-restricted search and image search map to query modifiers. Handle these transparently in the replacement.
def google_search_advanced(query: str, site: str = None,
search_type: str = None, **kwargs) -> dict:
# Site-restricted search
if site:
query = f'site:{site} {query}'
# Image search
if search_type == 'image':
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': kwargs.get('gl', 'us'),
'type': 'images'})
data = resp.json()
return {'items': [{'link': r.get('link', ''), 'title': r.get('title', ''),
'image': r.get('image', {})}
for r in data.get('images_results', [])]}
return google_search(query, **kwargs)
# Test site-restricted search:
results = google_search_advanced('pricing', site='stripe.com')
print(f'Found {len(results["items"])} results from stripe.com')Step 4: Compare costs and feature coverage
Calculate the cost difference between CSE and the replacement API for your usage pattern.
def migration_cost_comparison(monthly_queries: int) -> dict:
# Google CSE pricing
cse_free = 100 # per day, roughly 3000/month
cse_paid_rate = 5.0 / 1000 # $5 per 1000 queries
if monthly_queries <= 3000:
cse_cost = 0
else:
cse_cost = (monthly_queries - 3000) * cse_paid_rate
# Scavio pricing
scavio_rate = 0.005
if monthly_queries <= 250:
scavio_cost = 0 # free tier
else:
scavio_cost = monthly_queries * scavio_rate
# Feature comparison
features = {
'Google CSE': ['organic results', '10 per page', 'basic metadata'],
'Scavio': ['organic results', 'all results', 'People Also Ask',
'AI Overviews', 'knowledge graph', 'shopping', 'images',
'Amazon', 'YouTube', 'TikTok']
}
return {
'monthly_queries': monthly_queries,
'cse_cost': f'${cse_cost:.2f}',
'scavio_cost': f'${scavio_cost:.2f}',
'savings': f'${cse_cost - scavio_cost:.2f}',
'feature_gain': len(features['Scavio']) - len(features['Google CSE'])
}
for vol in [1000, 5000, 20000]:
c = migration_cost_comparison(vol)
print(f'{c["monthly_queries"]:,} queries: CSE {c["cse_cost"]} vs Scavio {c["scavio_cost"]}')Step 5: Test the migration with side-by-side comparison
Run the same queries through both APIs and compare results to validate the migration before switching over.
def validate_migration(test_queries: list) -> None:
print('Migration validation:')
for query in test_queries:
# New API
new_results = google_search(query)
new_count = len(new_results.get('items', []))
top_new = new_results['items'][0]['title'] if new_results['items'] else 'N/A'
print(f' "{query}":')
print(f' New API: {new_count} results, top: {top_new[:50]}')
print(f' Cost: $0.005')
total_cost = len(test_queries) * 0.005
print(f'\nValidation cost: ${total_cost:.3f}')
print('Migration checklist:')
print(' [x] Drop-in function created')
print(' [x] Site-restricted search supported')
print(' [x] Image search supported')
print(' [x] Cost comparison calculated')
print(' [x] Results validated')
validate_migration(['best crm software', 'python tutorial', 'site:github.com fastapi'])Python Example
import os, requests
API_KEY = os.environ['SCAVIO_API_KEY']
def google_search(query, num=10, gl='us'):
"""Drop-in replacement for Google CSE."""
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY, 'Content-Type': 'application/json'},
json={'query': query, 'country_code': gl})
data = resp.json()
items = [{'title': r['title'], 'link': r['link'], 'snippet': r.get('snippet', '')}
for r in data.get('organic_results', [])[:num]]
return {'items': items, 'searchInformation': {'totalResults': str(len(items))}}
results = google_search('best python frameworks 2026')
for item in results['items'][:3]:
print(f'{item["title"]}: {item["link"]}')
print(f'Cost: $0.005 per query (CSE: $0.005 at paid tier, limited features)')JavaScript Example
const API_KEY = process.env.SCAVIO_API_KEY;
// Drop-in replacement for Google CSE
async function googleSearch(query, { num = 10, gl = 'us' } = {}) {
const resp = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, country_code: gl })
});
const data = await resp.json();
const items = (data.organic_results || []).slice(0, num)
.map(r => ({ title: r.title, link: r.link, snippet: r.snippet || '' }));
return { items, searchInformation: { totalResults: String(items.length) } };
}
async function main() {
const results = await googleSearch('best python frameworks 2026');
results.items.slice(0, 3).forEach(item => {
console.log(`${item.title}: ${item.link}`);
});
}
main().catch(console.error);Expected Output
CSE: q -> Scavio: query
CSE: gl -> Scavio: country_code
Found 8 results from stripe.com
1,000 queries: CSE $0.00 vs Scavio $5.00
5,000 queries: CSE $10.00 vs Scavio $25.00
20,000 queries: CSE $85.00 vs Scavio $100.00
Migration validation:
"best crm software": New API: 10 results, top: Best CRM Software 2026
[x] Drop-in function created
[x] Results validated