Retool lets you build internal dashboards without frontend code. This tutorial connects Scavio as the data source to build an SEO dashboard with rank tracking, SERP feature monitoring, and competitor comparison. Retool handles the UI while Scavio provides real-time SERP data at $0.005/query.
Prerequisites
- A Retool account (free tier works)
- A Scavio API key from scavio.dev
- Basic SQL or JavaScript knowledge
Walkthrough
Step 1: Configure Scavio as a Retool REST API resource
Add Scavio as a REST API resource in Retool so queries can call it directly.
// In Retool: Settings > Resources > Add Resource > REST API
// Name: Scavio Search API
// Base URL: https://api.scavio.dev/api/v1
// Headers:
// x-api-key: {{SCAVIO_API_KEY}}
// Content-Type: application/json
// Test query in Retool Query Editor:
// Method: POST
// URL: /search
// Body:
{
"query": "{{ textInput1.value }}",
"country_code": "us"
}
// Retool will show the response with organic_results, people_also_ask, etc.
// Map these to table and chart components.
// For server-side testing:
import os, requests
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': 'best search api 2026', 'country_code': 'us'}).json()
print(f'Organic results: {len(data.get("organic_results", []))}')
print(f'PAA questions: {len(data.get("people_also_ask", []))}')
print(f'Has featured snippet: {bool(data.get("featured_snippet"))}')Step 2: Build the rank tracking view
Create a Retool table that shows keyword positions and trends over time.
import os, requests, json
from datetime import datetime
API_KEY = os.environ['SCAVIO_API_KEY']
SH = {'x-api-key': API_KEY, 'Content-Type': 'application/json'}
def retool_rank_data(keywords, domain):
"""Generate data formatted for Retool table component."""
rows = []
for kw in keywords:
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': kw, 'country_code': 'us'}, timeout=10).json()
organic = data.get('organic_results', [])
pos = next((i+1 for i, r in enumerate(organic) if domain in r.get('link', '')), None)
featured = data.get('featured_snippet', {})
rows.append({
'keyword': kw,
'position': pos,
'in_top3': pos is not None and pos <= 3,
'in_featured': domain in featured.get('displayed_link', ''),
'competitor_1': organic[0].get('displayed_link', '') if organic else '',
'paa_count': len(data.get('people_also_ask', [])),
'date': datetime.now().strftime('%Y-%m-%d'),
})
return rows
keywords = ['search api python', 'mcp search tool', 'serp api pricing', 'web search api free tier']
rows = retool_rank_data(keywords, 'scavio.dev')
print(f'Retool table data: {len(rows)} rows')
for r in rows:
pos = f'#{r["position"]}' if r['position'] else '-'
print(f' {r["keyword"]:35} | {pos:5} | Top 3: {r["in_top3"]}')
print(f'\nCost: ${len(keywords) * 0.005:.3f}')Step 3: Add SERP feature monitoring
Track which SERP features appear for each keyword and how they change.
def serp_features_data(keywords):
"""Extract SERP features for Retool chart components."""
features = []
for kw in keywords:
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': kw, 'country_code': 'us'}, timeout=10).json()
feat = {
'keyword': kw,
'has_ai_overview': bool(data.get('ai_overview', data.get('answer_box'))),
'has_featured_snippet': bool(data.get('featured_snippet')),
'has_paa': len(data.get('people_also_ask', [])) > 0,
'paa_count': len(data.get('people_also_ask', [])),
'organic_count': len(data.get('organic_results', [])),
'has_knowledge_graph': bool(data.get('knowledge_graph')),
}
features.append(feat)
# Summary for Retool pie chart
ai_count = sum(1 for f in features if f['has_ai_overview'])
fs_count = sum(1 for f in features if f['has_featured_snippet'])
paa_count = sum(1 for f in features if f['has_paa'])
print(f'\n=== SERP Features Summary (for Retool) ===')
print(f' Keywords scanned: {len(features)}')
print(f' AI Overview: {ai_count}/{len(features)} ({ai_count/len(features)*100:.0f}%)')
print(f' Featured Snippet: {fs_count}/{len(features)} ({fs_count/len(features)*100:.0f}%)')
print(f' People Also Ask: {paa_count}/{len(features)} ({paa_count/len(features)*100:.0f}%)')
print(f'\n Retool: Bind this data to a pie chart component')
print(f' Cost: ${len(keywords) * 0.005:.3f}/scan')
return features
serp_features_data(keywords)Python Example
import os, requests
SH = {'x-api-key': os.environ['SCAVIO_API_KEY'], 'Content-Type': 'application/json'}
def retool_data(keyword):
data = requests.post('https://api.scavio.dev/api/v1/search',
headers=SH, json={'query': keyword, 'country_code': 'us'}, timeout=10).json()
organic = data.get('organic_results', [])[:5]
return [{'pos': i+1, 'domain': r.get('displayed_link', ''), 'title': r['title'][:40]} for i, r in enumerate(organic)]
for r in retool_data('best search api'):
print(f'#{r["pos"]} {r["domain"]:25} {r["title"]}')JavaScript Example
const SH = { 'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json' };
const data = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: SH,
body: JSON.stringify({ query: 'best search api', country_code: 'us' })
}).then(r => r.json());
const rows = (data.organic_results || []).slice(0, 5).map((r, i) => ({
position: i + 1, domain: r.displayed_link, title: r.title
}));
console.log(JSON.stringify(rows, null, 2));Expected Output
Retool table data: 4 rows
search api python | #3 | Top 3: True
mcp search tool | #2 | Top 3: True
serp api pricing | #5 | Top 3: False
web search api free tier | #4 | Top 3: False
Cost: $0.020
=== SERP Features Summary (for Retool) ===
Keywords scanned: 4
AI Overview: 3/4 (75%)
Featured Snippet: 2/4 (50%)
People Also Ask: 4/4 (100%)
Cost: $0.020/scan