Developers
VAROPS Operations API
Read your airline's flights, pilots, routes, fleet and live map from your own website, Discord bot or tools.
Getting a key
Airline owners and admins create keys in Stratus → Community → API, choosing what each key can read. The key is shown once; keep it secret and use it only from a server, never in a web page. Revoke a key at any time and it stops working straight away.
Making requests
Send the key in the Authorization header. Every key belongs to one airline, so there is no airline ID in the address.
curl https://varops.net/v1/ops/pireps?status=approved \
-H "Authorization: Bearer varops_your_key_here"
- Responses are JSON. Lists look like
{"data": [...], "page": 1, "per_page": 50, "total": 123}; single items like{"data": {...}}. - Errors return
{"error": "..."}with status 400 (bad request), 401 (missing or revoked key), 403 (key lacks a scope, or the airline isn't active), 404 or 429. - Each key can make 120 requests a minute. The
RateLimitheaders show what's left. - Times are UTC ISO 8601. Durations are minutes; distances are nautical miles; landing rates are feet per minute.
- Pilots' email addresses are never returned.
Endpoints
GET /v1/ops/airline
Your airline's details and running totals. Needs read:airline
{"data": {"name": "Test Air", "icao": "TST", "hub": "EGLL", "stats": {"pilots": 42, "flights": 1280, "hours": 2310.5, "distance_nm": 512000, "routes": 120, "aircraft": 18}}}GET /v1/ops/pilots
Pilots, by pilot ID. Needs read:pilots
| Query | What it does |
|---|---|
page | Page number, from 1 |
per_page | Results per page, 1–100 (default 50) |
{"data": [{"pilot_id": "TST001", "name": "Ann Pilot", "role": "pilot", "status": "off_duty", "hub": "EGLL", "rank": "Captain", "hours": 310.2, "flights": 120, "points": 9050, "joined_at": "2021-03-01T00:00:00Z", "inactive_since": null}], "page": 1, "per_page": 50, "total": 42}GET /v1/ops/pilots/{pilot_id}
One pilot. Needs read:pilots
{"data": {"pilot_id": "TST001", "name": "Ann Pilot", ...}}GET /v1/ops/pireps
Flight reports, newest first. Needs read:pireps
| Query | What it does |
|---|---|
status | pending, approved, rejected, under_review or invalidated |
pilot_id | Only this pilot's reports |
since / until | Filed on or after / before this date or time |
page | Page number, from 1 |
per_page | Results per page, 1–100 (default 50) |
{"data": [{"number": 1000123, "pilot_id": "TST001", "flight_number": "TST210", "dep": "EGCC", "arr": "LEPA", "aircraft_reg": "G-TSTA", "status": "approved", "network": "vatsim", "block_time_mins": 142, "distance_nm": 860, "landing_rate": -142, "landing_g": 1.08, "points": 222, "tracked": true, "created_at": "..."}], ...}GET /v1/ops/pireps/{number}
One flight report by its 7-digit number, with remarks and score breakdown. Needs read:pireps
{"data": {"number": 1000123, ..., "remarks": "Smooth", "score_breakdown": [...]}}GET /v1/ops/routes
Routes, by flight number. Needs read:routes
| Query | What it does |
|---|---|
page | Page number, from 1 |
per_page | Results per page, 1–100 (default 50) |
{"data": [{"flight_number": "TST210", "dep": "EGCC", "arr": "LEPA", "distance_nm": 860, "frequency": "Daily", "active": true, "fleet": "Airbus A320neo", "min_rank": null}], ...}GET /v1/ops/fleet
Fleet types with their aircraft, including where each aircraft is and whether it's in a maintenance check. Needs read:fleet
{"data": [{"name": "Airbus A320neo", "icao_type": "A20N", "pax_capacity": 180, "aircraft": [{"registration": "G-TSTA", "status": "active", "in_check": false, "location": "LEPA", "hours": 1210.4}]}]}GET /v1/ops/live
Flights in the air now (pilots who hide themselves from the live map are left out). Needs read:live
{"data": [{"flight_number": "TST210", "pilot_id": "TST001", "dep": "EGCC", "arr": "LEPA", "lat": 47.1, "lng": -1.3, "altitude_ft": 37000, "speed_kts": 455, "heading": 192, "phase": "cruise", "progress_pct": 42}]}GET /v1/ops/bookings
Active bookings, newest first, with paging (page, per_page up to 100). Needs read:bookings
{"data": [{"reference_number": "1234", "pilot_id": "TST001", "flight_number": "TST210", "dep": "EGCC", "arr": "LEPA", "aircraft": "G-TEST", "aircraft_type": "A320", "departure_at": "2026-11-13T09:30:00.000Z", "network": "VATSIM", "trip_leg": 1, "trip_legs": 2, "planned": true}], "page": 1, "per_page": 50, "total": 1}GET /v1/ops/activities
Current and recent activities: events, tours, focus airports, community goals and team challenges, with live standings for goals and challenges. Needs read:activities
{"data": [{"name": "Fly together", "type": "collective_goal", "starts_at": "…", "ends_at": "…", "standings": {"metric": "flights", "total": 42, "contributors": 12, "tiers": [{"target": 50, "points": 100, "reached": false}]}}]}Webhooks
Instead of asking the API for changes, let VAROPS tell you. In Stratus, open the API page and add a webhook: a public https:// address on your website or bot, and the events you want. Owners and admins can add up to 10.
Each event arrives as a POST with a JSON body like this:
{
"id": "6f1c…", // delivery ID, also in X-VAROPS-Delivery
"event": "pirep_accepted", // also in X-VAROPS-Event
"airline_id": "…",
"occurred_at": "2026-11-12T18:42:07.000Z",
"data": { "title": "BAW123 · EGLL → EGCC", "description": "…", "url": "https://varops.net/…", "fields": [{ "name": "Score", "value": "98" }] }
}
Events include accepted flight reports, reports needing review, promotions, milestones, badges, bookings, take-offs and landings (Uplink), NOTAMs, alerts, activities and tour progress, applications and shared flights. The full list, with the exact names, is on the webhook form.
Check the signature
Every request has X-VAROPS-Signature: t=<unix time>,v1=<signature>. The signature is the hex HMAC-SHA256 of <t>.<raw request body>, using your webhook's signing secret (shown once when you add it, or when you make a new one). Reject requests whose signature doesn't match, or whose time is more than five minutes old.
// Node.js
const [, t, v1] = req.get('X-VAROPS-Signature').match(/^t=(\d+),v1=([0-9a-f]+)$/);
const expected = crypto.createHmac('sha256', SECRET).update(t + '.' + rawBody).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected)) && Math.abs(Date.now() / 1000 - t) < 300;
Answering and retries
Answer with any 2xx status within 8 seconds. Anything else, including redirects, counts as a failure: VAROPS tries again after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. After 25 failures in a row the webhook is switched off and Stratus says why. The last 30 days of deliveries are listed in Stratus, where you can send a test or resend a failed delivery. Deliveries can occasionally arrive twice: use the delivery ID to ignore repeats.
Public data
Some data needs no key: /v1/public/airlines/{slug} (your public page), with /flights, /leaderboard and /live, and /v1/contests for the monthly airline contests. These follow your public page settings.
Need something else?
Tell us what you're building at help@varops.net.