Switching from the Brave Search API free tier to Scavio takes about 15 minutes of code changes and removes the 2,000 queries/month cap that blocks most production workloads. This tutorial walks you through the migration step by step: mapping Brave response fields to Scavio equivalents, updating your HTTP calls, adjusting error handling, and validating that your downstream logic works with the new response format.
Prerequisites
- Existing Brave Search API integration you want to migrate
- Scavio API key (free 250 credits/mo at scavio.dev)
- Python 3.9+ or Node.js 18+
Walkthrough
Step 1: Audit your current Brave Search usage
Before migrating, audit how you currently call the Brave API. Note the endpoint, headers, query parameters, and which response fields your code actually uses. Most integrations only use title, url, and description from the web search results, which makes migration straightforward.
# Typical Brave Search API call you are replacing
import requests
BRAVE_API_KEY = 'your_brave_key'
resp = requests.get(
'https://api.search.brave.com/res/v1/web/search',
headers={'X-Subscription-Token': BRAVE_API_KEY},
params={'q': 'best crm for startups 2026', 'count': 10}
)
data = resp.json()
for result in data.get('web', {}).get('results', []):
print(result['title'], result['url'], result.get('description', ''))Step 2: Map Brave response fields to Scavio equivalents
Scavio returns results under an organic array instead of web.results. The field names differ slightly: Brave uses description while Scavio uses snippet. Create a mapping so you can update all references in one pass.
# Field mapping: Brave -> Scavio
# data.web.results -> data.organic
# result.title -> item.title (same)
# result.url -> item.url (same)
# result.description -> item.snippet
# result.extra_snippets -> (not applicable)
# params.q -> json body: query
# params.count -> json body: num
# header X-Subscription-Token -> header x-api-keyStep 3: Replace the API call
Swap the Brave GET request for a Scavio POST request. The Scavio API uses a JSON body instead of query parameters, and an x-api-key header instead of X-Subscription-Token.
import requests
SCAVIO_API_KEY = 'your_scavio_api_key'
def search(query, num=10):
resp = requests.post(
'https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_API_KEY},
json={'query': query, 'num': num}
)
resp.raise_for_status()
data = resp.json()
return [
{
'title': item.get('title', ''),
'url': item.get('url', ''),
'description': item.get('snippet', ''),
}
for item in data.get('organic', [])
]Step 4: Update error handling and rate limits
Brave returns 429 when you hit the free tier cap. Scavio returns standard HTTP error codes and includes rate limit headers. Update your retry logic to read the x-ratelimit-remaining header and back off before hitting the limit rather than after.
import time
def search_with_retry(query, num=10, max_retries=3):
for attempt in range(max_retries):
resp = requests.post(
'https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_API_KEY},
json={'query': query, 'num': num}
)
if resp.status_code == 200:
return resp.json()
if resp.status_code == 429:
wait = 2 ** attempt
print(f'Rate limited, waiting {wait}s...')
time.sleep(wait)
continue
resp.raise_for_status()
raise Exception('Max retries exceeded')Step 5: Validate output parity
Run both APIs side by side on a sample of queries and compare the results to verify your downstream logic works correctly. Check that titles, URLs, and descriptions are populated and that your parsing code handles the new format without errors.
test_queries = [
'best crm for startups 2026',
'python web scraping tutorial',
'saas pricing page examples',
]
for q in test_queries:
results = search(q, num=5)
print(f'Query: {q}')
print(f' Results: {len(results)}')
for r in results:
assert r['title'], 'Missing title'
assert r['url'], 'Missing url'
print(f' - {r["title"][:60]}')
print()Python Example
import requests
import time
SCAVIO_API_KEY = 'your_scavio_api_key'
def search(query, num=10, max_retries=3):
for attempt in range(max_retries):
resp = requests.post(
'https://api.scavio.dev/api/v1/search',
headers={'x-api-key': SCAVIO_API_KEY},
json={'query': query, 'num': num}
)
if resp.status_code == 200:
data = resp.json()
return [
{
'title': item.get('title', ''),
'url': item.get('url', ''),
'description': item.get('snippet', ''),
}
for item in data.get('organic', [])
]
if resp.status_code == 429:
time.sleep(2 ** attempt)
continue
resp.raise_for_status()
raise Exception('Max retries exceeded')
# Validate migration
test_queries = [
'best crm for startups 2026',
'python web scraping tutorial',
'saas pricing page examples',
]
for q in test_queries:
results = search(q, num=5)
print(f'Query: {q} -> {len(results)} results')
for r in results:
print(f' {r["title"][:60]}')JavaScript Example
const SCAVIO_API_KEY = 'your_scavio_api_key';
async function search(query, num = 10, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const resp = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST',
headers: { 'x-api-key': SCAVIO_API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, num }),
});
if (resp.ok) {
const data = await resp.json();
return (data.organic || []).map(item => ({
title: item.title || '',
url: item.url || '',
description: item.snippet || '',
}));
}
if (resp.status === 429) {
await new Promise(r => setTimeout(r, 2 ** attempt * 1000));
continue;
}
throw new Error(`API error: ${resp.status}`);
}
throw new Error('Max retries exceeded');
}
async function main() {
const testQueries = [
'best crm for startups 2026',
'python web scraping tutorial',
'saas pricing page examples',
];
for (const q of testQueries) {
const results = await search(q, 5);
console.log(`Query: ${q} -> ${results.length} results`);
for (const r of results) {
console.log(` ${r.title.slice(0, 60)}`);
}
}
}
main();Expected Output
Query: best crm for startups 2026 -> 5 results
The 10 Best CRMs for Startups in 2026 - Detailed Comparison
...
Query: python web scraping tutorial -> 5 results
...
Query: saas pricing page examples -> 5 results
...