OpenWebUI web search breaks frequently due to rate limits, CAPTCHAs, and provider outages. Replacing the built-in search with a structured API eliminates these failures and returns consistent JSON results. This tutorial walks through diagnosing the issue and wiring a reliable search backend into OpenWebUI at $0.005/query.
Prerequisites
- OpenWebUI instance running
- Python 3.8+
- A Scavio API key from scavio.dev
- Admin access to OpenWebUI config
Walkthrough
Step 1: Diagnose the search failure
Check OpenWebUI logs to identify the root cause of web search failures.
# Common OpenWebUI search errors:
# 1. 'Search failed: timeout' - provider rate-limited
# 2. 'No results returned' - CAPTCHA blocked request
# 3. 'Connection refused' - provider endpoint changed
# Check OpenWebUI logs:
# docker logs open-webui 2>&1 | grep -i 'search'
# Test if the issue is the search provider:
import requests, os
API_KEY = os.environ['SCAVIO_API_KEY']
SH = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
# Quick test: does a structured API return results?
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': 'python web framework', 'country_code': 'us'})
data = resp.json()
print(f'Status: {resp.status_code}')
print(f'Results: {len(data.get("organic_results", []))}')
print(f'Latency: {resp.elapsed.total_seconds():.2f}s')
if data.get('organic_results'):
print(f'First result: {data["organic_results"][0].get("title", "")}')
print('API is working. Issue is in OpenWebUI search config.')Step 2: Create a search function wrapper
Build a wrapper that OpenWebUI can call for web search.
import os, requests, json
API_KEY = os.environ['SCAVIO_API_KEY']
SH = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
def web_search(query, num_results=5):
"""Drop-in replacement for OpenWebUI web search."""
try:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': query, 'country_code': 'us', 'num_results': num_results},
timeout=10)
resp.raise_for_status()
data = resp.json()
results = []
for r in data.get('organic_results', [])[:num_results]:
results.append({
'title': r.get('title', ''),
'link': r.get('link', ''),
'snippet': r.get('snippet', ''),
})
return results
except Exception as e:
print(f'Search error: {e}')
return []
# Test the wrapper
results = web_search('latest python release 2026')
for r in results:
print(f'{r["title"]}')
print(f' {r["link"]}')
print(f' {r["snippet"][:80]}')
print(f'Cost: $0.005 per search')Step 3: Configure OpenWebUI to use the API
Update OpenWebUI environment variables to point to the new search backend.
# Option 1: Set via environment variables in docker-compose.yml
# services:
# open-webui:
# environment:
# - ENABLE_RAG_WEB_SEARCH=true
# - RAG_WEB_SEARCH_ENGINE=custom
# - SCAVIO_API_KEY=your_key_here
# Option 2: Create a custom search tool in OpenWebUI
# Navigate to: Settings > Tools > Add Tool
# Paste this as a tool function:
def openwebui_search_tool(query: str) -> str:
"""Search the web for current information."""
import requests, os
SH = {'x-api-key': os.environ.get('SCAVIO_API_KEY', ''),
'Content-Type': 'application/json'}
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': query, 'country_code': 'us', 'num_results': 5},
timeout=10)
data = resp.json()
results = data.get('organic_results', [])
formatted = []
for r in results[:5]:
formatted.append(f"Title: {r.get('title', '')}\nURL: {r.get('link', '')}\nSnippet: {r.get('snippet', '')}")
return '\n\n'.join(formatted) if formatted else 'No results found.'
# Test the tool function
result = openwebui_search_tool('best search api 2026')
print(result)Step 4: Verify search is working end to end
Run a batch test to confirm reliability across different query types.
import time
def verify_search(queries):
print('=== OpenWebUI Search Verification ===')
successes = 0
total_time = 0
for q in queries:
start = time.time()
results = web_search(q)
elapsed = time.time() - start
total_time += elapsed
status = 'OK' if results else 'FAIL'
if results:
successes += 1
print(f' [{status}] "{q}" -> {len(results)} results in {elapsed:.2f}s')
print(f'\nSuccess rate: {successes}/{len(queries)} ({successes/len(queries)*100:.0f}%)')
print(f'Avg latency: {total_time/len(queries):.2f}s')
print(f'Total cost: ${len(queries) * 0.005:.3f}')
verify_search([
'python asyncio tutorial',
'react server components 2026',
'kubernetes best practices',
'machine learning framework comparison',
'latest docker release notes',
])Python Example
import os, requests
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
def web_search(query, n=5):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': query, 'country_code': 'us', 'num_results': n}).json()
return [{'title': r['title'], 'link': r['link'], 'snippet': r.get('snippet', '')}
for r in data.get('organic_results', [])[:n]]
for r in web_search('openwebui search fix 2026'):
print(f'{r["title"]}: {r["link"]}')
print('Cost: $0.005')JavaScript Example
const SH = { 'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json' };
async function webSearch(query, n = 5) {
const data = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: SH,
body: JSON.stringify({ query, country_code: 'us', num_results: n })
}).then(r => r.json());
return (data.organic_results || []).slice(0, n);
}
const results = await webSearch('openwebui search fix 2026');
results.forEach(r => console.log(`${r.title}: ${r.link}`));Expected Output
Status: 200
Results: 10
Latency: 0.45s
First result: Python Web Frameworks Guide 2026
API is working. Issue is in OpenWebUI search config.
=== OpenWebUI Search Verification ===
[OK] "python asyncio tutorial" -> 5 results in 0.42s
[OK] "react server components 2026" -> 5 results in 0.38s
[OK] "kubernetes best practices" -> 5 results in 0.40s
[OK] "machine learning framework comparison" -> 5 results in 0.39s
[OK] "latest docker release notes" -> 5 results in 0.41s
Success rate: 5/5 (100%)
Avg latency: 0.40s
Total cost: $0.025