Google Custom Search Engine free tier is limited to 50 domains and shuts down entirely in January 2027. If your app relies on Google CSE for free search, you need a migration plan now. This tutorial builds a fallback layer that tries your existing Google CSE first, then routes to Scavio when Google returns errors or when you exceed limits. The final step drops Google entirely. Scavio costs $0.005 per query with the free tier covering 250 credits per month.
Prerequisites
- Python 3.9+ or Node.js 18+
- An existing Google CSE integration
- A Scavio API key from scavio.dev
- requests library (Python) or fetch (Node.js)
Walkthrough
Step 1: Audit your current Google CSE usage
Before migrating, measure how many queries you send and which parameters you use. This determines cost and helps map Google CSE fields to Scavio fields.
import os
# Your existing Google CSE config
GOOGLE_CSE_KEY = os.environ.get('GOOGLE_CSE_KEY', '')
GOOGLE_CSE_CX = os.environ.get('GOOGLE_CSE_CX', '')
# Typical Google CSE call for reference
# GET https://www.googleapis.com/customsearch/v1
# ?key=KEY&cx=CX&q=QUERY&num=10
#
# Returns: items[].title, items[].link, items[].snippet
# Map Google CSE fields to Scavio fields:
field_map = {
'items[].title': 'organic_results[].title',
'items[].link': 'organic_results[].link',
'items[].snippet': 'organic_results[].snippet',
'items[].pagemap': 'not available (use scrape instead)',
'searchInformation.totalResults': 'not available',
}
for google_field, scavio_field in field_map.items():
print(f' {google_field:40s} -> {scavio_field}')Step 2: Build the dual-provider search function
Create a search function that tries Google CSE first and falls back to Scavio on any error, rate limit, or empty result.
import requests, os, time
GOOGLE_KEY = os.environ.get('GOOGLE_CSE_KEY', '')
GOOGLE_CX = os.environ.get('GOOGLE_CSE_CX', '')
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
def search_google_cse(query: str, num: int = 10) -> list:
if not GOOGLE_KEY or not GOOGLE_CX:
return []
resp = requests.get('https://www.googleapis.com/customsearch/v1',
params={'key': GOOGLE_KEY, 'cx': GOOGLE_CX, 'q': query, 'num': num},
timeout=10)
if resp.status_code != 200:
print(f'Google CSE error: {resp.status_code}')
return []
items = resp.json().get('items', [])
return [{'title': r['title'], 'link': r['link'],
'snippet': r.get('snippet', '')} for r in items]
def search_scavio(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()
return [{'title': r['title'], 'link': r['link'],
'snippet': r.get('snippet', '')} for r in resp.json().get('organic_results', [])]
def search(query: str, num: int = 10) -> dict:
start = time.time()
results = search_google_cse(query, num)
if results:
return {'results': results, 'provider': 'google_cse',
'latency_ms': int((time.time() - start) * 1000), 'cost': 0}
results = search_scavio(query, num)
return {'results': results, 'provider': 'scavio',
'latency_ms': int((time.time() - start) * 1000), 'cost': 0.005}Step 3: Test both providers side by side
Run the same queries through both providers to verify Scavio returns equivalent results before cutting over.
test_queries = [
'best CRM software 2026',
'python web scraping tutorial',
'react server components explained',
]
for query in test_queries:
google_results = search_google_cse(query, 5)
scavio_results = search_scavio(query, 5)
print(f'Query: {query}')
print(f' Google CSE: {len(google_results)} results')
print(f' Scavio: {len(scavio_results)} results')
if scavio_results:
print(f' Top Scavio: {scavio_results[0]["title"][:60]}')
print()Step 4: Drop-in replacement: remove Google CSE entirely
Once validated, replace your search function with Scavio only. This is your post-migration state -- no Google dependency.
import requests, os
SCAVIO_KEY = os.environ['SCAVIO_API_KEY']
def search(query: str, num: int = 10) -> list:
"""Drop-in replacement for Google CSE. Same return format."""
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 [{'title': r['title'], 'link': r['link'],
'snippet': r.get('snippet', '')} for r in resp.json().get('organic_results', [])]
# Usage is identical to before:
results = search('best CRM software 2026')
for r in results:
print(f'{r["title"]}: {r["link"]}')
print(f'Cost: $0.005 per query, 250 free/month')Python Example
import requests, os
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 [{'title': r['title'], 'link': r['link'], 'snippet': r.get('snippet', '')}
for r in resp.json().get('organic_results', [])]
results = search('best CRM software 2026')
for r in results:
print(f'{r["title"]}: {r["link"]}')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 })
});
const data = await resp.json();
return (data.organic_results || []).map(r => ({
title: r.title, link: r.link, snippet: r.snippet || ''
}));
}
search('best CRM software 2026').then(results =>
results.forEach(r => console.log(`${r.title}: ${r.link}`)));Expected Output
Query: best CRM software 2026
Google CSE: 0 results
Scavio: 10 results
Top Scavio: Best CRM Software of 2026 - Forbes Advisor
Cost: $0.005 per query, 250 free/month