Quickstart Tutorial
Get started with the Vail ReRBN API in 5 minutes. This tutorial walks you through making your first API calls and understanding the responses.
Step 1: Your First API Call
Let's start with the simplest possible request: searching for spots of a specific callsign.
curl "https://vailrerbn.com/api/v1/spots?call=W1AW&mode=CW&band=20m"
import requests
response = requests.get(
"https://vailrerbn.com/api/v1/spots",
params={
"call": "W1AW",
"mode": "CW",
"band": "20m"
}
)
data = response.json()
print(f"Found {data['total']} spots")
for spot in data["spots"]:
grid = spot.get('grid', 'Unknown')
print(f"{spot['callsign']} ({grid}) on {spot['frequency']} kHz")
const params = new URLSearchParams({
call: 'W1AW',
mode: 'CW',
band: '20m'
});
const response = await fetch(
`https://vailrerbn.com/api/v1/spots?${params}`
);
const data = await response.json();
console.log(`Found ${data.total} spots`);
data.spots.forEach(spot => {
const grid = spot.grid || 'Unknown';
console.log(`${spot.callsign} (${grid}) on ${spot.frequency} kHz`);
});
<?php
$params = http_build_query([
'call' => 'W1AW',
'mode' => 'CW',
'band' => '20m'
]);
$response = file_get_contents(
"https://vailrerbn.com/api/v1/spots?$params"
);
$data = json_decode($response, true);
echo "Found {$data['total']} spots\n";
foreach ($data['spots'] as $spot) {
echo "{$spot['callsign']} on {$spot['frequency']} kHz\n";
}
require 'net/http'
require 'json'
uri = URI('https://vailrerbn.com/api/v1/spots')
uri.query = URI.encode_www_form({
call: 'W1AW',
mode: 'CW',
band: '20m'
})
response = Net::HTTP.get(uri)
data = JSON.parse(response)
puts "Found #{data['total']} spots"
data['spots'].each do |spot|
puts "#{spot['callsign']} on #{spot['frequency']} kHz"
end
Find recent spots for W1AW on the 20-meter band in CW mode
Understanding the Response
The API returns a JSON object with the following structure:
{
"spots": [
{
"id": 12345678,
"timestamp": "2025-01-09T15:30:00Z",
"spotter": "K1TTT",
"spotter_grid": "FN32",
"callsign": "W1AW",
"grid": "FN31pr",
"frequency": 14025.3,
"mode": "CW",
"snr": 25,
"wpm": 28
}
],
"total": 247,
"offset": 0,
"limit": 100
}
Field Explanations
- id: Unique spot identifier (used for pagination)
- timestamp: When the spot was received (ISO 8601 UTC)
- spotter: The RBN skimmer that detected the signal
- spotter_grid: Maidenhead grid of the skimmer (null if unknown)
- callsign: The station being spotted
- grid: Maidenhead grid of the spotted station (null if unknown)
- frequency: Frequency in kHz
- mode: Operating mode (CW, FT8, FT4, RTTY)
- snr: Signal-to-Noise Ratio in dB (higher = stronger)
- wpm: Words per minute (CW only, null for digital modes)
Step 2: Adding Filters
Let's narrow down the results by adding mode and band filters:
curl "https://vailrerbn.com/api/v1/spots?mode=CW,FT8&band=40m"
import requests
response = requests.get(
"https://vailrerbn.com/api/v1/spots",
params={
"mode": "CW,FT8",
"band": "40m"
}
)
data = response.json()
# Group by mode
for mode in ["CW", "FT8"]:
spots = [s for s in data["spots"] if s["mode"] == mode]
print(f"{mode}: {len(spots)} spots")
const response = await fetch(
'https://vailrerbn.com/api/v1/spots?mode=CW,FT8&band=40m'
);
const data = await response.json();
// Group by mode
for (const mode of ['CW', 'FT8']) {
const spots = data.spots.filter(s => s.mode === mode);
console.log(`${mode}: ${spots.length} spots`);
}
<?php
$response = file_get_contents(
"https://vailrerbn.com/api/v1/spots?mode=CW,FT8&band=40m"
);
$data = json_decode($response, true);
// Group by mode
foreach (['CW', 'FT8'] as $mode) {
$spots = array_filter($data['spots'], function($s) use ($mode) {
return $s['mode'] === $mode;
});
echo "$mode: " . count($spots) . " spots\n";
}
require 'net/http'
require 'json'
uri = URI('https://vailrerbn.com/api/v1/spots')
uri.query = URI.encode_www_form({
mode: 'CW,FT8',
band: '40m'
})
response = Net::HTTP.get(uri)
data = JSON.parse(response)
# Group by mode
['CW', 'FT8'].each do |mode|
spots = data['spots'].select { |s| s['mode'] == mode }
puts "#{mode}: #{spots.length} spots"
end
Filter for both CW and FT8 modes using comma-separated values
Available Filter Parameters
| Parameter | Description | Example |
|---|---|---|
call |
Callsign to search (partial match) | W1AW |
spotter |
Filter by skimmer callsign | K1TTT |
mode |
Operating mode (comma-separated) | CW,FT8 |
band |
Amateur band | 20m, 40m |
limit |
Max results (1-1000, default 100) | 500 |
Step 3: Understanding Time Ranges
Important: Default Time Range
By default, the API only returns spots from the last 1 hour. To query historical data, you must explicitly set the since parameter.
The API accepts timestamps in two formats:
Example: Last 24 Hours
# Get Unix timestamp for 24 hours ago
SINCE=$(date -d '24 hours ago' +%s)
curl "https://vailrerbn.com/api/v1/spots?since=$SINCE"
import requests
import time
# Get timestamp for 24 hours ago
since = int(time.time()) - (24 * 60 * 60)
response = requests.get(
"https://vailrerbn.com/api/v1/spots",
params={"since": since}
)
data = response.json()
print(f"Found {data['total']} spots in last 24 hours")
// Get timestamp for 24 hours ago
const since = Math.floor(Date.now() / 1000) - (24 * 60 * 60);
const response = await fetch(
`https://vailrerbn.com/api/v1/spots?since=${since}`
);
const data = await response.json();
console.log(`Found ${data.total} spots in last 24 hours`);
<?php
// Get timestamp for 24 hours ago
$since = time() - (24 * 60 * 60);
$response = file_get_contents(
"https://vailrerbn.com/api/v1/spots?since=$since"
);
$data = json_decode($response, true);
echo "Found {$data['total']} spots in last 24 hours\n";
require 'net/http'
require 'json'
# Get timestamp for 24 hours ago
since = Time.now.to_i - (24 * 60 * 60)
uri = URI('https://vailrerbn.com/api/v1/spots')
uri.query = URI.encode_www_form({ since: since })
response = Net::HTTP.get(uri)
data = JSON.parse(response)
puts "Found #{data['total']} spots in last 24 hours"
Get spots from the past 24 hours using Unix timestamp
Step 4: Real-Time Monitoring Preview
For continuous monitoring (like tracking when a station comes on the air), use cursor-based pagination with the after_id parameter:
How it works:
- Make initial request to get recent spots
- Track the highest
idfrom the response - Poll every 5-10 seconds with
after_id=LAST_ID - Process new spots and update tracked ID
# Not practical with curl - use Python or JavaScript for continuous polling
import requests
import time
last_id = 0
print("Monitoring for W1AW spots (Ctrl+C to stop)...")
while True:
params = {"limit": 500, "call": "W1AW"}
if last_id > 0:
params["after_id"] = last_id
response = requests.get("https://vailrerbn.com/api/v1/spots", params=params)
data = response.json()
for spot in data["spots"]:
grid = spot.get('grid', '????')
print(f"{spot['timestamp']} - {spot['callsign']} ({grid}) on {spot['frequency']} kHz by {spot['spotter']}")
last_id = max(last_id, spot["id"])
time.sleep(5) # Poll every 5 seconds
let lastId = 0;
console.log('Monitoring for W1AW spots (Ctrl+C to stop)...');
async function pollSpots() {
const params = new URLSearchParams({
limit: 500,
call: 'W1AW'
});
if (lastId > 0) {
params.set('after_id', lastId);
}
const response = await fetch(`https://vailrerbn.com/api/v1/spots?${params}`);
const data = await response.json();
for (const spot of data.spots) {
const grid = spot.grid || '????';
console.log(`${spot.timestamp} - ${spot.callsign} (${grid}) on ${spot.frequency} kHz by ${spot.spotter}`);
lastId = Math.max(lastId, spot.id);
}
}
// Poll every 5 seconds
setInterval(pollSpots, 5000);
pollSpots(); // Initial call
<?php
$lastId = 0;
echo "Monitoring for W1AW spots (Ctrl+C to stop)...\n";
while (true) {
$params = ['limit' => 500, 'call' => 'W1AW'];
if ($lastId > 0) {
$params['after_id'] = $lastId;
}
$url = "https://vailrerbn.com/api/v1/spots?" . http_build_query($params);
$response = file_get_contents($url);
$data = json_decode($response, true);
foreach ($data['spots'] as $spot) {
echo "{$spot['timestamp']} - {$spot['callsign']} on {$spot['frequency']} kHz by {$spot['spotter']}\n";
$lastId = max($lastId, $spot['id']);
}
sleep(5); // Poll every 5 seconds
}
require 'net/http'
require 'json'
last_id = 0
puts 'Monitoring for W1AW spots (Ctrl+C to stop)...'
loop do
uri = URI('https://vailrerbn.com/api/v1/spots')
params = { limit: 500, call: 'W1AW' }
params[:after_id] = last_id if last_id > 0
uri.query = URI.encode_www_form(params)
response = Net::HTTP.get(uri)
data = JSON.parse(response)
data['spots'].each do |spot|
puts "#{spot['timestamp']} - #{spot['callsign']} on #{spot['frequency']} kHz by #{spot['spotter']}"
last_id = [last_id, spot['id']].max
end
sleep 5 # Poll every 5 seconds
end
Continuously poll for new spots using cursor pagination
What's Next?
Try the API Playground
Build and test queries interactively with our visual tool
Explore All Endpoints
Complete reference for /stats, /skimmers, /charts, and more
See Common Use Cases
Real-world examples like band analysis, DX alerting, and CSV export
Troubleshooting Guide
Solutions to common errors and gotchas