How-To Guides

Common use cases and complete workflows for the Vail ReRBN API.

Monitor a station in real-time

Set up continuous monitoring to be notified whenever a specific callsign is spotted.

1

Make initial request

Get recent spots and track the highest ID

2

Poll with after_id

Every 5-10 seconds, request new spots

3

Process and update

Handle new spots and update your tracked ID

Real-time monitoring with after_id
# Not practical with curl - use Python or JavaScript for continuous polling
Real-time monitoring with after_id
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
Real-time monitoring with after_id
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
Real-time monitoring with after_id
<?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
}
Real-time monitoring with after_id
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

Expected Output

2025-01-09 15:30:00 - W1AW on 14.025 MHz by K1TTT
2025-01-09 15:32:15 - W1AW on 14.027 MHz by N2IC
2025-01-09 15:35:42 - W1AW on 21.025 MHz by W3LPL

Find all DX stations on a specific band

Discover what stations are active on a particular band right now.

# Get all spots on 20m in the last hour
curl "https://vailrerbn.com/api/v1/spots?band=20m&limit=1000"

# Filter by mode as well
curl "https://vailrerbn.com/api/v1/spots?band=20m&mode=FT8&limit=1000"

Tip: Sort by SNR to find the strongest signals

Analyze band conditions over time

Use the /stats endpoint to understand propagation patterns.

Get aggregate statistics
curl "https://vailrerbn.com/api/v1/stats?hours=1"
Get aggregate statistics
import requests

response = requests.get(
    "https://vailrerbn.com/api/v1/stats",
    params={"hours": 1}
)
data = response.json()

print(f"Total spots: {data['totalSpots']}")
print(f"Spots/min: {data['spotsPerMinute']:.1f}")
print(f"Unique callsigns: {data['uniqueCallsigns']}")
print(f"\nMode breakdown:")
for mode, count in data['modeBreakdown'].items():
    print(f"  {mode}: {count}")
Get aggregate statistics
const response = await fetch(
  'https://vailrerbn.com/api/v1/stats?hours=1'
);
const data = await response.json();

console.log(`Total spots: ${data.totalSpots}`);
console.log(`Spots/min: ${data.spotsPerMinute.toFixed(1)}`);
console.log(`Unique callsigns: ${data.uniqueCallsigns}`);
console.log(`\nMode breakdown:`);

for (const [mode, count] of Object.entries(data.modeBreakdown)) {
  console.log(`  ${mode}: ${count}`);
}
Get aggregate statistics
<?php
$response = file_get_contents(
    "https://vailrerbn.com/api/v1/stats?hours=1"
);
$data = json_decode($response, true);

echo "Total spots: {$data['totalSpots']}\n";
echo "Spots/min: " . number_format($data['spotsPerMinute'], 1) . "\n";
echo "Unique callsigns: {$data['uniqueCallsigns']}\n";
echo "\nMode breakdown:\n";

foreach ($data['modeBreakdown'] as $mode => $count) {
    echo "  $mode: $count\n";
}
Get aggregate statistics
require 'net/http'
require 'json'

uri = URI('https://vailrerbn.com/api/v1/stats')
uri.query = URI.encode_www_form({ hours: 1 })

response = Net::HTTP.get(uri)
data = JSON.parse(response)

puts "Total spots: #{data['totalSpots']}"
puts "Spots/min: #{'%.1f' % data['spotsPerMinute']}"
puts "Unique callsigns: #{data['uniqueCallsigns']}"
puts "\nMode breakdown:"

data['modeBreakdown'].each do |mode, count|
  puts "  #{mode}: #{count}"
end

Fetch aggregated stats for the past hour

Use case: Compare mode breakdown at different times of day to identify optimal operating hours for each mode.

Build a skimmer coverage map

See which skimmers are active and how many spots each is reporting.

curl "https://vailrerbn.com/api/v1/skimmers?hours=24"

Returns up to 100 skimmers sorted by spot count. Great for identifying propagation paths and active skimmer regions.

Export spots to CSV for analysis

Transform JSON responses into CSV format for Excel or data analysis tools.

import requests
import csv

# Fetch spots
response = requests.get(
    "https://vailrerbn.com/api/v1/spots",
    params={"call": "W1AW", "since": 1704739200}
)
data = response.json()

# Write to CSV
with open('spots.csv', 'w', newline='') as f:
    writer = csv.DictWriter(f,
        fieldnames=['timestamp', 'callsign', 'frequency',
                    'mode', 'snr', 'spotter'])
    writer.writeheader()
    for spot in data['spots']:
        writer.writerow({
            'timestamp': spot['timestamp'],
            'callsign': spot['callsign'],
            'frequency': spot['frequency'],
            'mode': spot['mode'],
            'snr': spot['snr'],
            'spotter': spot['spotter']
        })

Integrate with logging software

Pattern for continuous integration with ham radio logging applications.

1

Use after_id polling

Poll every 5-10 seconds to avoid rate limits

2

Deduplicate spots

Track seen IDs to avoid processing duplicates

3

Filter locally

Apply additional filters client-side (frequency ranges, SNR thresholds)

Best practice: Store the last processed ID persistently so you can resume after restarts.