When you use multiple search APIs, each has different pricing tiers, free credits, and rate limits. A cost router automatically selects the cheapest available provider for each query, exhausting free tiers first before spending money. This tutorial builds a router across Scavio (250 free/mo, $0.005 after), Brave ($5 free monthly credit), and Tavily (1K free/mo). The router tracks usage, respects rate limits, and falls back gracefully.
Prerequisites
- Python 3.9+ installed
- API keys for at least two search providers
- requests library installed
Walkthrough
Step 1: Define provider configurations
Set up each provider with its pricing, free tier, rate limits, and API call format.
import os
providers = {
'scavio': {
'cost_per_query': 0.005,
'free_monthly': 250,
'rate_limit_per_sec': 10,
'api_key': os.environ.get('SCAVIO_API_KEY', ''),
'priority': 1, # lower = preferred
},
'brave': {
'cost_per_query': 0.005,
'free_monthly': 1000, # $5 free credit = 1K queries
'rate_limit_per_sec': 5,
'api_key': os.environ.get('BRAVE_API_KEY', ''),
'priority': 2,
},
'tavily': {
'cost_per_query': 0.03,
'free_monthly': 1000,
'rate_limit_per_sec': 5,
'api_key': os.environ.get('TAVILY_API_KEY', ''),
'priority': 3,
},
}
for name, p in providers.items():
status = 'configured' if p['api_key'] else 'no key'
print(f' {name}: ${p["cost_per_query"]}/query, {p["free_monthly"]} free/mo [{status}]')Step 2: Build the cost router with usage tracking
The router tracks monthly usage per provider, uses free credits first, then routes to the cheapest paid provider.
import time, requests
from datetime import datetime
class CostRouter:
def __init__(self, providers: dict):
self.providers = providers
self.usage = {name: 0 for name in providers}
self.month = datetime.now().month
self.last_call = {name: 0.0 for name in providers}
def _reset_if_new_month(self):
current_month = datetime.now().month
if current_month != self.month:
self.usage = {name: 0 for name in self.providers}
self.month = current_month
def _get_cost(self, name: str) -> float:
p = self.providers[name]
if self.usage[name] < p['free_monthly']:
return 0.0
return p['cost_per_query']
def _can_call(self, name: str) -> bool:
p = self.providers[name]
if not p['api_key']:
return False
elapsed = time.time() - self.last_call[name]
return elapsed >= (1.0 / p['rate_limit_per_sec'])
def select_provider(self) -> str:
self._reset_if_new_month()
available = [(name, self._get_cost(name), self.providers[name]['priority'])
for name in self.providers if self._can_call(name)]
if not available:
return list(self.providers.keys())[0] # fallback to first
# Sort by cost, then priority
available.sort(key=lambda x: (x[1], x[2]))
return available[0][0]
def search(self, query: str) -> dict:
provider = self.select_provider()
cost = self._get_cost(provider)
results = self._call_provider(provider, query)
self.usage[provider] += 1
self.last_call[provider] = time.time()
return {'results': results, 'provider': provider,
'cost': cost, 'usage': dict(self.usage)}Step 3: Implement provider-specific API calls
Add the actual API call logic for each provider. The router dispatches to the correct one automatically.
def _call_provider(self, name: str, query: str) -> list:
p = self.providers[name]
try:
if name == 'scavio':
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': p['api_key'], 'Content-Type': 'application/json'},
json={'query': query, 'country_code': 'us', 'num_results': 10},
timeout=10)
return [{'title': r['title'], 'link': r['link'], 'snippet': r.get('snippet', '')}
for r in resp.json().get('organic_results', [])]
elif name == 'brave':
resp = requests.get('https://api.search.brave.com/res/v1/web/search',
headers={'X-Subscription-Token': p['api_key']},
params={'q': query, 'count': 10}, timeout=10)
return [{'title': r['title'], 'link': r['url'], 'snippet': r.get('description', '')}
for r in resp.json().get('web', {}).get('results', [])]
elif name == 'tavily':
resp = requests.post('https://api.tavily.com/search',
json={'api_key': p['api_key'], 'query': query, 'max_results': 10},
timeout=10)
return [{'title': r.get('title', ''), 'link': r['url'], 'snippet': r.get('content', '')}
for r in resp.json().get('results', [])]
except Exception as e:
print(f'{name} failed: {e}')
return []
return []
# Initialize and test
router = CostRouter(providers)
result = router.search('best search API 2026')
print(f'Provider: {result["provider"]}, Cost: ${result["cost"]:.3f}')
print(f'Results: {len(result["results"])}')
print(f'Usage: {result["usage"]}')Python Example
import requests, os, time
SCAVIO_KEY = os.environ.get('SCAVIO_API_KEY', '')
BRAVE_KEY = os.environ.get('BRAVE_API_KEY', '')
usage = {'scavio': 0, 'brave': 0}
def route_search(query):
# Use free credits first
if usage['scavio'] < 250 and SCAVIO_KEY:
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})
usage['scavio'] += 1
return {'results': resp.json().get('organic_results', []), 'provider': 'scavio', 'cost': 0}
if usage['brave'] < 1000 and BRAVE_KEY:
resp = requests.get('https://api.search.brave.com/res/v1/web/search',
headers={'X-Subscription-Token': BRAVE_KEY}, params={'q': query, 'count': 10})
usage['brave'] += 1
return {'results': resp.json().get('web', {}).get('results', []), 'provider': 'brave', 'cost': 0}
# Paid fallback
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})
usage['scavio'] += 1
return {'results': resp.json().get('organic_results', []), 'provider': 'scavio', 'cost': 0.005}
result = route_search('best search api 2026')
print(f'{result["provider"]}: {len(result["results"])} results, ${result["cost"]}')JavaScript Example
const SCAVIO_KEY = process.env.SCAVIO_API_KEY;
const BRAVE_KEY = process.env.BRAVE_API_KEY;
const usage = { scavio: 0, brave: 0 };
async function routeSearch(query) {
if (usage.scavio < 250 && SCAVIO_KEY) {
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: 10 })
});
usage.scavio++;
return { results: (await resp.json()).organic_results || [], provider: 'scavio', cost: 0 };
}
if (usage.brave < 1000 && BRAVE_KEY) {
const resp = await fetch(`https://api.search.brave.com/res/v1/web/search?q=${encodeURIComponent(query)}&count=10`, {
headers: { 'X-Subscription-Token': BRAVE_KEY }
});
usage.brave++;
return { results: (await resp.json()).web?.results || [], provider: 'brave', cost: 0 };
}
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: 10 })
});
usage.scavio++;
return { results: (await resp.json()).organic_results || [], provider: 'scavio', cost: 0.005 };
}
routeSearch('best search api').then(r => console.log(`${r.provider}: ${r.results.length} results`));Expected Output
scavio: $0.005/query, 250 free/mo [configured]
brave: $0.005/query, 1000 free/mo [configured]
tavily: $0.030/query, 1000 free/mo [no key]
Provider: scavio, Cost: $0.000
Results: 10
Usage: {'scavio': 1, 'brave': 0, 'tavily': 0}
First 250 queries: $0.00 (Scavio free)
Next 1000 queries: $0.00 (Brave free)
Remaining: $0.005/query (cheapest paid)