Tutorial

How to Build a Cost Router for the Cheapest Search Provider

Build a smart router that sends each search query to the cheapest provider with available credits. Auto-failover across Scavio, Brave, and Tavily.

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.

Python
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.

Python
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.

Python
    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

Python
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

JavaScript
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

JSON
  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)

Related Tutorials

Frequently Asked Questions

Most developers complete this tutorial in 15 to 30 minutes. You will need a Scavio API key (free tier works) and a working Python or JavaScript environment.

Python 3.9+ installed. API keys for at least two search providers. requests library installed. A Scavio API key gives you 250 free credits per month.

Yes. The free tier includes 250 credits per month, which is more than enough to complete this tutorial and prototype a working solution.

Scavio has a native LangChain package (langchain-scavio), an MCP server, and a plain REST API that works with any HTTP client. This tutorial uses the raw REST API, but you can adapt to your framework of choice.

Start Building

Build a smart router that sends each search query to the cheapest provider with available credits. Auto-failover across Scavio, Brave, and Tavily.