Understanding Pagination
Learn the difference between offset and cursor-based pagination, and when to use each approach.
Why Pagination Matters
The Vail ReRBN database contains hundreds of thousands of spots. Returning all matching results in a single response would be slow, consume excessive bandwidth, and potentially crash clients. Pagination allows you to retrieve data in manageable chunks.
Two Pagination Methods
Offset Pagination
Best for: Historical browsing, page-by-page navigation
Database (newest to oldest):
1
2
3
4
5
6
7
8
9
10
Request 1
offset=0, limit=3
1
2
3
Request 2
offset=3, limit=3
4
5
6
Pros
- Simple to understand
- Can jump to any page
- Good for static datasets
Cons
- Can miss/duplicate spots if data changes
- Slower for large offsets
- Not ideal for real-time data
Cursor-Based Pagination (after_id)
Best for: Real-time monitoring, continuous polling
Timeline (old → new):
ID:100
ID:101
ID:102
Cursor
ID:103
ID:104
ID:105
Initial Request
limit=3
ID:100
ID:101
ID:102
Next Request (5 sec later)
after_id=102, limit=3
ID:103
ID:104
ID:105
Pros
- Never misses new data
- Perfect for real-time streaming
- Consistent performance
Cons
- Can't jump to arbitrary positions
- Must track last seen ID
- Only moves forward in time
Which Method Should I Use?
| Scenario | Use offset | Use after_id |
|---|---|---|
| Real-time monitoring / Live feed | ❌ | ✅ |
| Historical browsing / Data browser | ✅ | ❌ |
| Continuous polling | ❌ | ✅ |
| Need page numbers (e.g., "Page 5 of 10") | ✅ | ❌ |
Code Examples
Offset Pagination
Offset pagination
# Page 1
curl "https://vailrerbn.com/api/v1/spots?limit=100&offset=0"
# Page 2
curl "https://vailrerbn.com/api/v1/spots?limit=100&offset=100"
# Page 3
curl "https://vailrerbn.com/api/v1/spots?limit=100&offset=200"
Offset pagination
import requests
page_size = 100
page = 1
while True:
offset = (page - 1) * page_size
response = requests.get(
"https://vailrerbn.com/api/v1/spots",
params={
"limit": page_size,
"offset": offset
}
)
data = response.json()
print(f"Page {page}: {len(data['spots'])} spots")
# Process spots
for spot in data["spots"]:
print(f" {spot['callsign']} on {spot['frequency']} kHz")
# Check if we have more pages
if offset + len(data["spots"]) >= data["total"]:
print("No more pages")
break
page += 1
Offset pagination
const pageSize = 100;
let page = 1;
while (true) {
const offset = (page - 1) * pageSize;
const response = await fetch(
`https://vailrerbn.com/api/v1/spots?limit=${pageSize}&offset=${offset}`
);
const data = await response.json();
console.log(`Page ${page}: ${data.spots.length} spots`);
// Process spots
for (const spot of data.spots) {
console.log(` ${spot.callsign} on ${spot.frequency} kHz`);
}
// Check if we have more pages
if (offset + data.spots.length >= data.total) {
console.log('No more pages');
break;
}
page++;
}
Offset pagination
<?php
$pageSize = 100;
$page = 1;
while (true) {
$offset = ($page - 1) * $pageSize;
$url = "https://vailrerbn.com/api/v1/spots?" . http_build_query([
'limit' => $pageSize,
'offset' => $offset
]);
$response = file_get_contents($url);
$data = json_decode($response, true);
echo "Page $page: " . count($data['spots']) . " spots\n";
// Process spots
foreach ($data['spots'] as $spot) {
echo " {$spot['callsign']} on {$spot['frequency']} kHz\n";
}
// Check if we have more pages
if ($offset + count($data['spots']) >= $data['total']) {
echo "No more pages\n";
break;
}
$page++;
}
Offset pagination
require 'net/http'
require 'json'
page_size = 100
page = 1
loop do
offset = (page - 1) * page_size
uri = URI('https://vailrerbn.com/api/v1/spots')
uri.query = URI.encode_www_form({
limit: page_size,
offset: offset
})
response = Net::HTTP.get(uri)
data = JSON.parse(response)
puts "Page #{page}: #{data['spots'].length} spots"
# Process spots
data['spots'].each do |spot|
puts " #{spot['callsign']} on #{spot['frequency']} kHz"
end
# Check if we have more pages
if offset + data['spots'].length >= data['total']
puts 'No more pages'
break
end
page += 1
end
Browse through historical spots page by page
Cursor-Based Pagination (Real-Time)
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