API Endpoints Reference

Complete reference for all Vail ReRBN API endpoints with parameters, examples, and response formats.

GET /spots

Search for spots with optional filters. This is the primary endpoint for querying spot data.

Parameters

Parameter Type Default Description
call string Callsign to search (partial match, case-insensitive)
spotter string Filter by spotter/skimmer callsign
mode string Mode filter: CW, RTTY, FT8, FT4 (comma-separated)
band string Amateur band: 160m, 80m, 60m, 40m, 30m, 20m, 17m, 15m, 12m, 10m, 6m
since timestamp 1 hour ago Start time (Unix timestamp or ISO 8601)
until timestamp now End time (Unix timestamp or ISO 8601)
limit integer 100 Max results (1-1000)
offset integer 0 Pagination offset
after_id integer Return spots with ID > this value (for real-time polling)

Response Fields

Field Type Description
id integer Unique spot identifier
timestamp string ISO 8601 timestamp
spotter string RBN skimmer callsign
spotter_grid string | null Maidenhead grid of the skimmer (e.g., "FM19"). Null if unknown.
callsign string Spotted station (DX) callsign
grid string | null Maidenhead grid of the spotted station (e.g., "FN31pr"). Null if unknown.
frequency number Frequency in kHz
mode string Mode: CW, RTTY, FT8, FT4, etc.
snr integer Signal-to-noise ratio in dB
wpm integer | null Words per minute (CW only)
Gridsquare Lookup:

Gridsquares are looked up from HamDB.org. Coverage is best for US callsigns. International callsigns may return null. New callsigns are queued for background lookup and will be available on subsequent requests.

Example

Search for W1AW on 20m CW
curl "https://vailrerbn.com/api/v1/spots?call=W1AW&mode=CW&band=20m"
Search for W1AW on 20m CW
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")
Search for W1AW on 20m CW
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`);
});
Search for W1AW on 20m CW
<?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";
}
Search for W1AW on 20m CW
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

GET /spots/:callsign

Get all spots for a specific callsign (exact match).

Parameters

Parameter Type Default Description
hours integer 24 Time range in hours
mode string Filter by mode

Example

Get spots for specific callsign
curl "https://vailrerbn.com/api/v1/spots/W1AW?hours=48"
Get spots for specific callsign
import requests

callsign = "W1AW"
hours = 48

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

print(f"Found {data['total']} spots for {callsign} in last {hours} hours")

for spot in data["spots"]:
    spotter_grid = spot.get('spotter_grid', '????')
    print(f"{spot['timestamp']} - {spot['spotter']} ({spotter_grid}) - {spot['frequency']} kHz - {spot['mode']} - {spot['snr']} dB")
Get spots for specific callsign
const callsign = 'W1AW';
const hours = 48;

const response = await fetch(
  `https://vailrerbn.com/api/v1/spots/${callsign}?hours=${hours}`
);
const data = await response.json();

console.log(`Found ${data.total} spots for ${callsign} in last ${hours} hours`);

data.spots.forEach(spot => {
  const spotterGrid = spot.spotter_grid || '????';
  console.log(`${spot.timestamp} - ${spot.spotter} (${spotterGrid}) - ${spot.frequency} kHz - ${spot.mode} - ${spot.snr} dB`);
});
Get spots for specific callsign
<?php
$callsign = "W1AW";
$hours = 48;

$url = "https://vailrerbn.com/api/v1/spots/$callsign?" . http_build_query(['hours' => $hours]);
$response = file_get_contents($url);
$data = json_decode($response, true);

echo "Found {$data['total']} spots for $callsign in last $hours hours\n";

foreach ($data['spots'] as $spot) {
    echo "{$spot['timestamp']} - {$spot['frequency']} kHz - {$spot['mode']} - {$spot['snr']} dB\n";
}
Get spots for specific callsign
require 'net/http'
require 'json'

callsign = 'W1AW'
hours = 48

uri = URI("https://vailrerbn.com/api/v1/spots/#{callsign}")
uri.query = URI.encode_www_form({ hours: hours })

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

puts "Found #{data['total']} spots for #{callsign} in last #{hours} hours"

data['spots'].each do |spot|
  puts "#{spot['timestamp']} - #{spot['frequency']} kHz - #{spot['mode']} - #{spot['snr']} dB"
end

Retrieve all spots for W1AW in the past 48 hours

GET /activity

Batch activity check for multiple callsigns in a single request. Returns whether each callsign has been spotted, along with spot count and latest spot details. Designed for monitoring a list of callsigns without making individual API calls.

Why use this endpoint?

If you're monitoring 50-100 callsigns, using /spots/:callsign for each one means 50-100 separate HTTP requests. This endpoint handles all of them in a single request with a single efficient database query. Much faster for both you and the server.

Parameters

Parameter Type Default Description
call string required Comma-separated callsigns (max 100). Example: W1AW,K1TTT,N3LLO
hours integer 1 Time window in hours (1-168)

Response Fields

Field Type Description
callsigns[].callsign string The callsign (uppercase)
callsigns[].spotted boolean true if at least one spot exists in the time window
callsigns[].count integer Number of spots in the time window (0 if not spotted)
callsigns[].last_spotted string | null ISO 8601 timestamp of the most recent spot
callsigns[].last_frequency number | null Frequency (kHz) of the most recent spot
callsigns[].last_mode string | null Mode of the most recent spot (CW, FT8, etc.)
callsigns[].last_snr integer | null SNR (dB) of the most recent spot
summary.requested integer Total callsigns requested
summary.spotted integer Number of callsigns that were spotted
summary.not_spotted integer Number of callsigns with no spots

Example Response

{
  "callsigns": [
    {
      "callsign": "W1AW",
      "spotted": true,
      "count": 12,
      "last_spotted": "2026-02-17T15:32:00.000Z",
      "last_frequency": 14025.3,
      "last_mode": "CW",
      "last_snr": 25
    },
    {
      "callsign": "K1TTT",
      "spotted": false,
      "count": 0,
      "last_spotted": null,
      "last_frequency": null,
      "last_mode": null,
      "last_snr": null
    }
  ],
  "summary": {
    "requested": 2,
    "spotted": 1,
    "not_spotted": 1
  },
  "hours": 1
}

Example

Batch activity check for multiple callsigns
# Check 5 callsigns in a single request
curl "https://vailrerbn.com/api/v1/activity?call=W1AW,K1TTT,N3LLO,WB9Z,VE3EN&hours=1"
Batch activity check for multiple callsigns
import requests

# List of callsigns to monitor
callsigns = ["W1AW", "K1TTT", "N3LLO", "WB9Z", "VE3EN"]

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

print(f"{data['summary']['spotted']}/{data['summary']['requested']} callsigns active")

for entry in data["callsigns"]:
    if entry["spotted"]:
        print(f"  {entry['callsign']}: {entry['count']} spots, "
              f"last on {entry['last_frequency']} kHz ({entry['last_mode']})")
    else:
        print(f"  {entry['callsign']}: not spotted")
Batch activity check for multiple callsigns
const callsigns = ['W1AW', 'K1TTT', 'N3LLO', 'WB9Z', 'VE3EN'];

const response = await fetch(
  `https://vailrerbn.com/api/v1/activity?call=${callsigns.join(',')}&hours=1`
);
const data = await response.json();

console.log(`${data.summary.spotted}/${data.summary.requested} callsigns active`);

for (const entry of data.callsigns) {
  if (entry.spotted) {
    console.log(`  ${entry.callsign}: ${entry.count} spots, `
      + `last on ${entry.last_frequency} kHz (${entry.last_mode})`);
  } else {
    console.log(`  ${entry.callsign}: not spotted`);
  }
}
Batch activity check for multiple callsigns
<?php
$callsigns = ["W1AW", "K1TTT", "N3LLO", "WB9Z", "VE3EN"];

$params = http_build_query([
    'call' => implode(',', $callsigns),
    'hours' => 1
]);

$response = file_get_contents(
    "https://vailrerbn.com/api/v1/activity?$params"
);
$data = json_decode($response, true);

echo "{$data['summary']['spotted']}/{$data['summary']['requested']} callsigns active\n";

foreach ($data['callsigns'] as $entry) {
    if ($entry['spotted']) {
        echo "  {$entry['callsign']}: {$entry['count']} spots, "
           . "last on {$entry['last_frequency']} kHz ({$entry['last_mode']})\n";
    } else {
        echo "  {$entry['callsign']}: not spotted\n";
    }
}
Batch activity check for multiple callsigns
require 'net/http'
require 'json'

callsigns = %w[W1AW K1TTT N3LLO WB9Z VE3EN]

uri = URI('https://vailrerbn.com/api/v1/activity')
uri.query = URI.encode_www_form({
  call: callsigns.join(','),
  hours: 1
})

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

puts "#{data['summary']['spotted']}/#{data['summary']['requested']} callsigns active"

data['callsigns'].each do |entry|
  if entry['spotted']
    puts "  #{entry['callsign']}: #{entry['count']} spots, " \
         "last on #{entry['last_frequency']} kHz (#{entry['last_mode']})"
  else
    puts "  #{entry['callsign']}: not spotted"
  end
end

Check if multiple callsigns have been spotted recently - much more efficient than individual calls

Callsign Groups

Create and manage persistent, named groups of callsigns for batch monitoring. Groups are public — anyone can view and query them. Modifications require an edit key returned at creation time.

Why use Groups?

Instead of passing a long list of callsigns to /activity every time, create a group once and query it by name. Perfect for contest teams, DXpedition monitoring, club rosters, or any recurring set of callsigns.

Web Interface:

You can also browse and view groups on the Groups page.

POST /groups — Create a Group

Create a new named callsign group. Returns an edit_key that is shown only once — save it to modify or delete the group later.

Request Body (JSON)

Field Type Required Description
name string Yes 3-50 chars, lowercase alphanumeric + hyphens (e.g., my-contest-team)
callsigns string[] Yes Array of callsigns (1-500)
creator_name string Yes Your name or callsign (max 100 chars). Visible to others so they can contact you about group changes.
creator_email string Yes Your email address. Visible on the group page so others can reach you for adjustment requests.
description string No Optional description (max 200 chars)

Example

curl -X POST https://vailrerbn.com/api/v1/groups \
  -H "Content-Type: application/json" \
  -d '{
    "name": "contest-team-alpha",
    "callsigns": ["W1AW", "K1TTT", "N3LLO"],
    "creator_name": "Brett KE9BOS",
    "creator_email": "ke9bos@pigletradio.org",
    "description": "Our ARRL DX contest team"
  }'

Response (201 Created)

{
  "group": {
    "name": "contest-team-alpha",
    "callsigns": ["W1AW", "K1TTT", "N3LLO"],
    "description": "Our ARRL DX contest team",
    "creator_name": "Brett KE9BOS",
    "creator_email": "ke9bos@pigletradio.org",
    "callsign_count": 3,
    "created_at": "2026-02-17T12:00:00.000Z",
    "updated_at": "2026-02-17T12:00:00.000Z"
  },
  "edit_key": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
  "notice": "Save this edit_key! It is required to modify or delete this group and will not be shown again."
}
Important: Save your edit_key!

The edit_key is only shown once at creation time. You need it to update or delete the group. If you lose it, contact ke9bos@pigletradio.org for recovery.

GET /groups — List All Groups

Returns all groups with name, description, and callsign count. No authentication required.

Example

curl https://vailrerbn.com/api/v1/groups

Response

{
  "groups": [
    {
      "name": "contest-team-alpha",
      "description": "Our ARRL DX contest team",
      "creator_name": "Brett KE9BOS",
      "callsign_count": 3,
      "created_at": "2026-02-17T12:00:00.000Z"
    }
  ],
  "total": 1
}

GET /groups/:name — Get Group Details

Returns the full group details including the callsign list, creator info, and timestamps.

Example

curl https://vailrerbn.com/api/v1/groups/contest-team-alpha

Response

{
  "group": {
    "name": "contest-team-alpha",
    "description": "Our ARRL DX contest team",
    "creator_name": "Brett KE9BOS",
    "creator_email": "ke9bos@pigletradio.org",
    "callsigns": ["K1TTT", "N3LLO", "W1AW"],
    "callsign_count": 3,
    "created_at": "2026-02-17T12:00:00.000Z",
    "updated_at": "2026-02-17T12:00:00.000Z"
  }
}

PUT /groups/:name — Update a Group

Update callsigns and/or description. Supports three modes for managing callsigns: full replacement, adding, or removing individual callsigns. Requires edit_key via header or query parameter.

Authentication

Provide the edit key in one of two ways:

  • Header: X-Edit-Key: your_key_here
  • Query parameter: ?edit_key=your_key_here

Request Body (JSON, all fields optional)

Field Type Description
callsigns string[] Full replacement — replaces the entire callsign list with this array
add_callsigns string[] Incremental add — adds these callsigns to the existing list (duplicates ignored)
remove_callsigns string[] Incremental remove — removes these callsigns from the existing list
description string New description (can be combined with any callsign operation)
Note: You cannot use callsigns together with add_callsigns or remove_callsigns in the same request. Use callsigns for full replacement, or add_callsigns/remove_callsigns for incremental changes. You may use add_callsigns and remove_callsigns together in one request.

Example: Full Replacement

curl -X PUT https://vailrerbn.com/api/v1/groups/contest-team-alpha \
  -H "Content-Type: application/json" \
  -H "X-Edit-Key: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" \
  -d '{
    "callsigns": ["W1AW", "K1TTT", "N3LLO", "WB9Z"],
    "description": "Updated team roster"
  }'

Example: Add Callsigns

curl -X PUT https://vailrerbn.com/api/v1/groups/contest-team-alpha \
  -H "Content-Type: application/json" \
  -H "X-Edit-Key: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" \
  -d '{
    "add_callsigns": ["N3LLO", "WB9Z"]
  }'

Example: Remove Callsigns

curl -X PUT https://vailrerbn.com/api/v1/groups/contest-team-alpha \
  -H "Content-Type: application/json" \
  -H "X-Edit-Key: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6" \
  -d '{
    "remove_callsigns": ["K1TTT"]
  }'

DELETE /groups/:name — Delete a Group

Permanently delete a group. Requires edit_key.

Example

curl -X DELETE https://vailrerbn.com/api/v1/groups/contest-team-alpha \
  -H "X-Edit-Key: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"

GET /groups/:name/activity — Group Activity Check

Check which callsigns in the group have been spotted. Same response format as /activity with an added group field.

Parameters

Parameter Type Default Description
hours integer 1 Time window in hours (1-168)

Example

curl "https://vailrerbn.com/api/v1/groups/contest-team-alpha/activity?hours=1"

Response

{
  "group": "contest-team-alpha",
  "callsigns": [
    {
      "callsign": "W1AW",
      "spotted": true,
      "count": 12,
      "last_spotted": "2026-02-17T15:32:00.000Z",
      "last_frequency": 14025.3,
      "last_mode": "CW",
      "last_snr": 25
    },
    {
      "callsign": "K1TTT",
      "spotted": false,
      "count": 0,
      "last_spotted": null,
      "last_frequency": null,
      "last_mode": null,
      "last_snr": null
    }
  ],
  "summary": {
    "requested": 2,
    "spotted": 1,
    "not_spotted": 1
  },
  "hours": 1
}

GET /groups/:name/spots — Group Spot Data

Get full spot data for all callsigns in the group. Same response format as /spots with an added group field. Includes gridsquare enrichment.

Parameters

Parameter Type Default Description
hours integer 1 Time window in hours (1-168)
mode string Mode filter (comma-separated: CW, FT8, etc.)
band string Band filter (20m, 40m, etc.)
limit integer 100 Max results (1-1000)
offset integer 0 Pagination offset

Example

curl "https://vailrerbn.com/api/v1/groups/contest-team-alpha/spots?hours=24&mode=CW&limit=50"

Error Responses

Status Cause
400 Invalid name, callsigns, or description
401 Missing or invalid edit key (for PUT/DELETE)
404 Group not found
409 Group name already exists (on POST)
429 Global group limit reached (max 1000)

Group Name Rules

  • 3-50 characters
  • Lowercase letters, numbers, and hyphens only
  • Must start and end with a letter or number
  • No consecutive hyphens
  • Reserved names: new, list, all, admin, api, test, create, delete, update

Automatic Cleanup

Groups that have not been queried or modified in 90 days are automatically deleted. Any read or query resets the timer.

GET /stats

Get aggregate statistics including total spots, spots/min, mode breakdown, and top bands.

Parameters

Parameter Type Default Description
hours integer 1 Time range from now (use start_hours/end_hours for historical windows)
start_hours integer 1 How many hours ago to start the window (older boundary)
end_hours integer 0 How many hours ago to end the window (0 = now)
Time Window Examples:
  • hours=1 — Last 1 hour (ends at now)
  • start_hours=4&end_hours=0 — Last 4 hours (same as hours=4)
  • start_hours=4&end_hours=3 — Data from 4 hours ago to 3 hours ago (1-hour window)
  • start_hours=24&end_hours=12 — Data from 24 hours ago to 12 hours ago

Example

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

Historical Time Window Example

Get historical statistics
# Get stats from 4 hours ago to 3 hours ago (1-hour window)
curl "https://vailrerbn.com/api/v1/stats?start_hours=4&end_hours=3"
Get historical statistics
import requests

# Get stats from 4 hours ago to 3 hours ago
response = requests.get(
    "https://vailrerbn.com/api/v1/stats",
    params={
        "start_hours": 4,
        "end_hours": 3
    }
)
data = response.json()

print(f"Time range: {data['timeRange']['start']} to {data['timeRange']['end']}")
print(f"Total spots: {data['totalSpots']}")
print(f"Spots/min: {data['spotsPerMinute']:.1f}")
Get historical statistics
// Get stats from 4 hours ago to 3 hours ago
const response = await fetch(
  'https://vailrerbn.com/api/v1/stats?start_hours=4&end_hours=3'
);
const data = await response.json();

console.log(`Time range: ${data.timeRange.start} to ${data.timeRange.end}`);
console.log(`Total spots: ${data.totalSpots}`);
console.log(`Spots/min: ${data.spotsPerMinute.toFixed(1)}`);
Get historical statistics
<?php
// Get stats from 4 hours ago to 3 hours ago
$response = file_get_contents(
    "https://vailrerbn.com/api/v1/stats?start_hours=4&end_hours=3"
);
$data = json_decode($response, true);

echo "Time range: {$data['timeRange']['start']} to {$data['timeRange']['end']}\n";
echo "Total spots: {$data['totalSpots']}\n";
echo "Spots/min: " . number_format($data['spotsPerMinute'], 1) . "\n";
Get historical statistics
require 'net/http'
require 'json'

# Get stats from 4 hours ago to 3 hours ago
uri = URI('https://vailrerbn.com/api/v1/stats')
uri.query = URI.encode_www_form({
  start_hours: 4,
  end_hours: 3
})

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

puts "Time range: #{data['timeRange']['start']} to #{data['timeRange']['end']}"
puts "Total spots: #{data['totalSpots']}"
puts "Spots/min: #{'%.1f' % data['spotsPerMinute']}"

Fetch stats for a specific time window (e.g., 4 to 3 hours ago)

GET /skimmers

Get list of active skimmers/spotters, sorted by spot count.

Parameters

Parameter Type Default Description
hours integer 1 Time range in hours

Note: Returns up to 100 skimmers.

GET /charts

Get chart data for visualizations (hourly activity, mode breakdown, band breakdown, top callsigns).

Parameters

Parameter Type Default Description
hours integer 12 Time range from now (use start_hours/end_hours for historical windows)
start_hours integer 12 How many hours ago to start the window (older boundary)
end_hours integer 0 How many hours ago to end the window (0 = now)
Time Window Examples:
  • hours=12 — Last 12 hours (ends at now)
  • start_hours=24&end_hours=12 — Data from 24 hours ago to 12 hours ago
  • start_hours=48&end_hours=24 — Yesterday's full 24-hour window

Note: Hourly activity data excludes the current incomplete hour when end_hours=0.

Historical Time Window Example

Get historical chart data
# Get chart data from 48 hours ago to 24 hours ago (yesterday)
curl "https://vailrerbn.com/api/v1/charts?start_hours=48&end_hours=24"
Get historical chart data
import requests

# Get chart data from 48 hours ago to 24 hours ago (yesterday)
response = requests.get(
    "https://vailrerbn.com/api/v1/charts",
    params={
        "start_hours": 48,
        "end_hours": 24
    }
)
data = response.json()

print(f"Hourly activity ({len(data['hourlyActivity'])} hours):")
for hour in data['hourlyActivity']:
    print(f"  {hour['hour']}: {hour['count']} spots")

print(f"\nTop modes:")
for mode in data['byMode'][:3]:
    print(f"  {mode['mode']}: {mode['count']}")
Get historical chart data
// Get chart data from 48 hours ago to 24 hours ago (yesterday)
const response = await fetch(
  'https://vailrerbn.com/api/v1/charts?start_hours=48&end_hours=24'
);
const data = await response.json();

console.log(`Hourly activity (${data.hourlyActivity.length} hours):`);
data.hourlyActivity.forEach(hour => {
  console.log(`  ${hour.hour}: ${hour.count} spots`);
});

console.log(`\nTop modes:`);
data.byMode.slice(0, 3).forEach(mode => {
  console.log(`  ${mode.mode}: ${mode.count}`);
});
Get historical chart data
<?php
// Get chart data from 48 hours ago to 24 hours ago (yesterday)
$response = file_get_contents(
    "https://vailrerbn.com/api/v1/charts?start_hours=48&end_hours=24"
);
$data = json_decode($response, true);

echo "Hourly activity (" . count($data['hourlyActivity']) . " hours):\n";
foreach ($data['hourlyActivity'] as $hour) {
    echo "  {$hour['hour']}: {$hour['count']} spots\n";
}

echo "\nTop modes:\n";
foreach (array_slice($data['byMode'], 0, 3) as $mode) {
    echo "  {$mode['mode']}: {$mode['count']}\n";
}
Get historical chart data
require 'net/http'
require 'json'

# Get chart data from 48 hours ago to 24 hours ago (yesterday)
uri = URI('https://vailrerbn.com/api/v1/charts')
uri.query = URI.encode_www_form({
  start_hours: 48,
  end_hours: 24
})

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

puts "Hourly activity (#{data['hourlyActivity'].length} hours):"
data['hourlyActivity'].each do |hour|
  puts "  #{hour['hour']}: #{hour['count']} spots"
end

puts "\nTop modes:"
data['byMode'].first(3).each do |mode|
  puts "  #{mode['mode']}: #{mode['count']}"
end

Fetch chart data for a time window in the past (e.g., yesterday)

GET /health

Health check endpoint. Returns server status and database connection info.

Example Response

{
  "status": "ok",
  "database": "connected",
  "databaseSizeMB": 45.2,
  "timestamp": "2025-01-09T15:30:00Z"
}

GET /bands

Get list of amateur bands with frequency ranges.

Example Response

{
  "bands": [
    {
      "name": "160m",
      "minFreq": 1800,
      "maxFreq": 2000
    },
    {
      "name": "80m",
      "minFreq": 3500,
      "maxFreq": 4000
    }
    // ... more bands
  ]
}