Tracking local search rankings without UULE parameter hacks is now possible through geo-targeted search APIs that handle location simulation server-side. UULE parameters -- the encoded location strings appended to Google search URLs -- are fragile, undocumented, and break when Google changes their format. The Scavio search API accepts a location field alongside your query, returning SERPs as they appear from that specific city or region without any client-side UULE encoding. This tutorial builds a local rank tracker that monitors keyword positions across multiple locations and detects rank differences between cities.
Prerequisites
- Python 3.8 or higher installed
- requests library installed (pip install requests)
- A Scavio API key from scavio.dev
- Target keywords and locations to track
Walkthrough
Step 1: Define keywords and target locations
Create a configuration mapping keywords to the locations you want to track. Each location is a city or region string that the API uses for geo-targeting. Include your own domain to track its position.
TRACKING_CONFIG = {
'keywords': [
'plumber near me',
'emergency plumbing service',
'water heater repair'
],
'locations': [
'Austin, Texas, United States',
'Dallas, Texas, United States',
'Houston, Texas, United States',
'San Antonio, Texas, United States'
],
'my_domain': 'austinplumbingpros.com'
}Step 2: Fetch geo-targeted search results
POST to the Scavio API with the location field set to the target city. The API returns SERPs as seen from that location, including local pack results, organic positions, and map listings.
import requests
import os
API_KEY = os.environ.get('SCAVIO_API_KEY', 'your_scavio_api_key')
def fetch_local_serp(query: str, location: str) -> dict:
response = requests.post(
'https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY},
json={
'query': query,
'country_code': 'us',
'location': location
}
)
response.raise_for_status()
return response.json()Step 3: Extract position for your domain
Search the organic results and local pack for your domain. Return the position if found, or None if your domain does not appear in the top results for that location.
def find_domain_position(data: dict, domain: str) -> dict:
result = {'organic_position': None, 'local_pack_position': None}
for r in data.get('organic_results', []):
if domain in r.get('link', ''):
result['organic_position'] = r.get('position')
break
for i, r in enumerate(data.get('local_pack', []), 1):
if domain in r.get('link', '') or domain in r.get('website', ''):
result['local_pack_position'] = i
break
return resultStep 4: Run the tracker across all keyword-location pairs
Iterate all combinations of keywords and locations, fetch results, extract positions, and print a comparison table showing how rankings differ by city.
from datetime import datetime
import json
from pathlib import Path
def run_local_tracker():
results = []
config = TRACKING_CONFIG
print(f'Local Rank Tracker - {datetime.now().strftime("%Y-%m-%d %H:%M")}')
print(f'{"Keyword":<30} {"Location":<35} {"Organic":<10} {"Local Pack"}')
print('-' * 90)
for keyword in config['keywords']:
for location in config['locations']:
data = fetch_local_serp(keyword, location)
pos = find_domain_position(data, config['my_domain'])
organic = str(pos['organic_position']) if pos['organic_position'] else '--'
local_pack = str(pos['local_pack_position']) if pos['local_pack_position'] else '--'
print(f'{keyword:<30} {location:<35} {organic:<10} {local_pack}')
results.append({
'keyword': keyword, 'location': location,
'organic': pos['organic_position'],
'local_pack': pos['local_pack_position'],
'timestamp': datetime.now().isoformat()
})
Path('local_rankings.json').write_text(json.dumps(results, indent=2))
print(f'\nSaved {len(results)} data points to local_rankings.json')
run_local_tracker()Python Example
import os
import json
import requests
from pathlib import Path
from datetime import datetime
API_KEY = os.environ.get('SCAVIO_API_KEY', 'your_scavio_api_key')
ENDPOINT = 'https://api.scavio.dev/api/v1/search'
TRACKING_CONFIG = {
'keywords': ['plumber near me', 'emergency plumbing service', 'water heater repair'],
'locations': [
'Austin, Texas, United States',
'Dallas, Texas, United States',
'Houston, Texas, United States',
'San Antonio, Texas, United States'
],
'my_domain': 'austinplumbingpros.com'
}
def fetch_local_serp(query: str, location: str) -> dict:
response = requests.post(
ENDPOINT,
headers={'x-api-key': API_KEY},
json={'query': query, 'country_code': 'us', 'location': location}
)
response.raise_for_status()
return response.json()
def find_domain_position(data: dict, domain: str) -> dict:
result = {'organic_position': None, 'local_pack_position': None}
for r in data.get('organic_results', []):
if domain in r.get('link', ''):
result['organic_position'] = r.get('position')
break
for i, r in enumerate(data.get('local_pack', []), 1):
if domain in r.get('link', '') or domain in r.get('website', ''):
result['local_pack_position'] = i
break
return result
def run_local_tracker():
config = TRACKING_CONFIG
results = []
print(f'Local Rank Tracker - {datetime.now().strftime("%Y-%m-%d %H:%M")}')
print(f'{"Keyword":<30} {"Location":<35} {"Organic":<10} {"Local Pack"}')
print('-' * 90)
for keyword in config['keywords']:
for location in config['locations']:
data = fetch_local_serp(keyword, location)
pos = find_domain_position(data, config['my_domain'])
organic = str(pos['organic_position']) if pos['organic_position'] else '--'
local_pack = str(pos['local_pack_position']) if pos['local_pack_position'] else '--'
print(f'{keyword:<30} {location:<35} {organic:<10} {local_pack}')
results.append({
'keyword': keyword, 'location': location,
'organic': pos['organic_position'],
'local_pack': pos['local_pack_position'],
'timestamp': datetime.now().isoformat()
})
Path('local_rankings.json').write_text(json.dumps(results, indent=2))
print(f'\nSaved {len(results)} data points to local_rankings.json')
if __name__ == '__main__':
run_local_tracker()JavaScript Example
const API_KEY = process.env.SCAVIO_API_KEY || 'your_scavio_api_key';
const ENDPOINT = 'https://api.scavio.dev/api/v1/search';
const fs = require('fs');
const TRACKING_CONFIG = {
keywords: ['plumber near me', 'emergency plumbing service', 'water heater repair'],
locations: [
'Austin, Texas, United States',
'Dallas, Texas, United States',
'Houston, Texas, United States',
'San Antonio, Texas, United States'
],
myDomain: 'austinplumbingpros.com'
};
async function fetchLocalSerp(query, location) {
const response = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, country_code: 'us', location })
});
if (!response.ok) throw new Error('HTTP ' + response.status);
return response.json();
}
function findDomainPosition(data, domain) {
const result = { organicPosition: null, localPackPosition: null };
for (const r of (data.organic_results || [])) {
if ((r.link || '').includes(domain)) {
result.organicPosition = r.position;
break;
}
}
(data.local_pack || []).forEach((r, i) => {
if ((r.link || '').includes(domain) || (r.website || '').includes(domain)) {
if (!result.localPackPosition) result.localPackPosition = i + 1;
}
});
return result;
}
async function main() {
const config = TRACKING_CONFIG;
const results = [];
console.log('Local Rank Tracker - ' + new Date().toISOString().slice(0, 16));
for (const keyword of config.keywords) {
for (const location of config.locations) {
const data = await fetchLocalSerp(keyword, location);
const pos = findDomainPosition(data, config.myDomain);
const organic = pos.organicPosition !== null ? String(pos.organicPosition) : '--';
const localPack = pos.localPackPosition !== null ? String(pos.localPackPosition) : '--';
console.log(keyword.padEnd(30) + location.padEnd(35) + organic.padEnd(10) + localPack);
results.push({ keyword, location, organic: pos.organicPosition, localPack: pos.localPackPosition, timestamp: new Date().toISOString() });
}
}
fs.writeFileSync('local_rankings.json', JSON.stringify(results, null, 2));
console.log('Saved ' + results.length + ' data points');
}
main().catch(console.error);Expected Output
Local Rank Tracker - 2026-05-12 14:30
Keyword Location Organic Local Pack
------------------------------------------------------------------------------------------
plumber near me Austin, Texas, United States 3 1
plumber near me Dallas, Texas, United States -- --
plumber near me Houston, Texas, United States -- --
plumber near me San Antonio, Texas, United States 12 --
emergency plumbing service Austin, Texas, United States 2 1
emergency plumbing service Dallas, Texas, United States -- --
Saved 12 data points to local_rankings.json