Skip to content

Developers

API reference

NOREVYN, the operating system for aerial work, runs its own map and briefings on the HTTP API documented here: live ADS-B traffic, FAA airspace and restriction data, aviation weather, drone-operation briefings, and continuous site monitoring.

The API needs an account.

Every endpoint below needs a signed-in session except /api/status, /api/sources, /api/showcase, /api/geocode and /healthz, which answer anyone. An account is free during early access — create one or sign in. A request without a session gets 401 with {"error":"Sign in to use this endpoint.","signIn":"/signin"}. The examples below leave the session out for brevity.

Overview

  • Base URL — all paths are relative to the origin serving this page (e.g. /api/states). There is no separate API host or version prefix.
  • Authentication — a session. Sign in at /signin, or call POST /api/org/login with {"email","password"}: the response sets an HTTP-only session cookie and also returns the session token. From a page served by NOREVYN, send the cookie (credentials: 'include'); from your own server, send Authorization: Bearer <token>. A session expires 30 days after sign-in.
  • Cross-origin — JSON responses outside /api/org/ carry Access-Control-Allow-Origin: *, so the open endpoints can be read from any page. Signed-in calls cannot: browsers do not send the SameSite=Lax session cookie on cross-site requests, and a request with an Authorization header needs a CORS preflight (OPTIONS), which the server answers with 405. Call the API from a page served by NOREVYN, or from a server.
  • Methods — the endpoints on this page are GET/HEAD, except POST /api/org/login and the two monitoring writes. POST is accepted only on the account, pilot network, remote operations, monitoring, event and payment-webhook routes. Anything else returns 405.
  • Format — JSON with cache-control: no-store, gzip-compressed when the body exceeds 4 KB and the client accepts it. URLs longer than 2048 characters are rejected with 414.
  • Errors — non-2xx responses are JSON with an error string, e.g. {"error":"bbox is required"}. A few add context, such as signIn on a 401.

Rate limiting

Per-IP token buckets. A bucket starts full, each request spends one token, and tokens refill continuously. Pages and static files have a separate, larger bucket.

TierBurst capacityRefillApplies to
heavy40 requests0.6 / sec/api/states, /api/uas/brief, /api/uas/overlays, /api/airport/{code}, /api/weather, /api/showcase, POST /api/watch/sites, and /api/geocode for signed-out callers — anything that can cost an upstream call.
auth20 requests1 every 6 secPOST /api/org/login, POST /api/org/register and POST /api/network/register.
api240 requests8 / secEvery other /api/* route, including /api/airport/search, tracks, the monitoring reads, and /api/geocode when signed in. The allowance is per IP and shared by everyone behind it, so it is sized for a whole office rather than one person.

When a bucket is empty you get 429 with a retry-after: 30 header and {"error":"Too many requests — slow down."}. Separately, repeated failed sign-ins for one email lock that email for a while, also as a 429 with its own retry-after. Back off, and honour nextPollMs where a response provides one.

Conventions

  • Coordinates — decimal degrees, WGS-84. lat must be within −90…90 and lon within −180…180; out-of-range or non-numeric values are rejected, never silently coerced.
  • Bounding boxes — bbox=lamin,lomin,lamax,lomax (south, west, north, east). Values are clamped to the valid range; after clamping, lamin<lamax and lomin<lomax must hold or the request is a 400.
  • Units — raw state vectors use SI (altitude in metres, speed and vertical rate in m/s); derived briefing fields are labelled in aviation units (altFt, speedKt, vsFpm, distNm). Timestamps are Unix epoch milliseconds unless the field is named lastContact (seconds) or is an ISO-8601 string.
  • Traffic is live or unavailable, never invented — source is "live" when the aircraft come from an ADS-B provider. That includes an earlier real snapshot (stale: true) and the national picture cut to your box (partial: true); ageSec gives a snapshot's age when it is known. When no real picture can be had, source is "unavailable", states is empty and error says why — read that as unknown, not as an empty sky. Only a server started in demo mode (DEMO=1) answers "sim", and its aircraft carry "simulated": true.

Live data endpoints

GET/api/states

Live ADS-B state vectors for a viewport, from adsb.lol with adsb.fi as the failover (OpenSky only where the server is configured to use it). Shared server-side caching means many clients cost one upstream request.

ParameterRequiredDescription
bboxnolamin,lomin,lamax,lomax. Omit it for the widest view the provider serves — with the default providers, everything within 1,500 NM of the centre of the contiguous US. A malformed or degenerate box is a 400, not a silent wide query. Viewports are rounded to 1° so nearby clients share a cache entry. A box too large for any provider is answered from the national picture, cut to the box.
curl -s '/api/states?bbox=42.0,-83.6,42.7,-82.6'
{
  "time": 1787274674892,
  "source": "live",              // "live" | "unavailable" ("sim" only in demo mode)
  "provider": "adsblol",         // "adsblol" | "adsbfi" | "opensky" | null
  "providerLabel": "adsb.lol",
  "cached": false,               // true when served from the shared viewport cache
  "stale": false,                // true for an earlier real snapshot
  "partial": false,              // true when cut from the national picture
  "coverage": null,              // "national" when the box was too large for any provider
  "ageSec": null,                // age of that snapshot in seconds, when known
  "count": 26,
  "nextPollMs": 12000,           // poll no faster than this
  "credits": { "remaining": 400, "budget": 400, "authenticated": false },  // OpenSky only
  "error": null,                 // last upstream failure, if any
  "states": [
    {
      "icao24": "a42bd4", "callsign": "UPS2084", "country": null,
      "lastContact": 1787274675,        // unix seconds
      "lon": -83.589286, "lat": 42.054314,
      "baroAlt": 10675.62,              // metres; null when unknown
      "onGround": false,
      "speed": 245.85,                  // m/s ground speed
      "track": 93.6, "vertRate": 0,     // deg true; m/s
      "geoAlt": 11117.58, "squawk": "3142", "spi": false,
      "category": 6,                    // ADS-B emitter category (14 = UAV)
      "reg": "N368UP", "type": "B763",
      "airline": { "icao": "UPS", "name": "UPS Airlines", "iata": "5X" }
    } /* … */
  ]
}
reg comes from the feed when it carries one (adsb.lol and adsb.fi do); otherwise it is derived from the ICAO 24-bit address, which only works for US aircraft. airline is matched from the callsign prefix and may be null. A box that rounds to the national view (24,-125,50,-66) can be served pre-compressed without stale, partial, coverage and ageSec; read a missing field as false or null.

GET/api/track/{icao24}

Recent trail for one aircraft, recorded server-side from every live traffic snapshot the server takes — tracks cost no extra upstream requests.

ParameterRequiredDescription
icao24yes (path)ICAO 24-bit address, hex, case-insensitive (e.g. a42bd4).
curl -s '/api/track/a42bd4'
{
  "icao24": "a42bd4",
  "points": [            // [timeMs, lat, lon, baroAltMetres, speedMs, trackDeg]
    [1787274674892, 42.054314, -83.589286, 10675.62, 245.85, 93.6]
    /* … up to 240 points, ~20 min retention */
  ]
}
Only aircraft seen in the server's live traffic in the last ~20 minutes have points. An unknown or expired address returns 200 with an empty points array — not a 404. Trails do not survive a restart.

GET/api/airports

The bundled world airport list the map seeds itself with (major fields only). Static — cache it client-side.

curl -s '/api/airports'
[
  { "iata": "DTW", "name": "Detroit Metropolitan Wayne County", "city": "Detroit",
    "cc": "US", "lat": 42.2124, "lon": -83.3534 } /* … */
]

Type-ahead airport search across live FAA airport records plus the bundled list. Cheap tier — it is designed to be called per keystroke.

ParameterRequiredDescription
qyesIATA/FAA ident, city, or name fragment. Fewer than 2 characters returns {"results":[]}. Up to 20 results; FAA records win over bundled duplicates.
curl -s '/api/airport/search?q=detroit'
{
  "results": [
    {
      "globalId": "90EDCD5B-…", "ident": "DTW", "icao": "KDTW",
      "name": "Detroit Metro Wayne County", "city": "DETROIT",
      "state": "MI", "country": "UNITED STATES",
      "elevationFt": 645.2, "type": "AD", "military": "CIVIL",
      "privateUse": false, "status": "OPERATIONAL", "hasApproach": true,
      "lat": 42.2124, "lon": -83.3534,
      "source": "faa"            // "faa" | "builtin"
    } /* … */
  ]
}

GET/api/airport/{code}

A full operational picture for one airport: inferred live movements, runways, airspace, the LAANC grid, TFRs, and weather. Heavy tier.

ParameterRequiredDescription
codeyes (path)FAA ident or IATA code (e.g. DTW). Case-insensitive. Unknown codes return 404 with {"error":"No airport found for \"…\"","lookupError":null}; lookupError carries the FAA lookup failure when there was one.
curl -s '/api/airport/DTW'
{
  "airport": { "ident": "DTW", "icao": "KDTW", "name": "…", "lat": 42.21, "lon": -83.35, "elevationFt": 645.2, … },
  "source": "live", "time": 1787274674892,     // traffic: "live" | "unavailable"
  "movements": {
    "arrivals":    [ { "icao24": "…", "callsign": "DAL123", "reg": "N…", "airline": {…},
                       "altFt": 4200, "aglFt": 3555, "speedKt": 210, "vsFpm": -800,
                       "distNm": 12.4, "bearing": 220, "onGround": false,
                       "lat": 42.1, "lon": -83.1, "etaMin": 4 } ],
    "departures":  [ … ], "ground": [ … ], "overflights": [ … ]
  },
  "counts": { "arrivals": 3, "departures": 2, "ground": 9, "overflights": 14 },
  "airspace": [ … ], "uasfm": { "ceilingFt": 0, … },
  "runways": [ … ],
  "tfrs": [ … ], "tfrPending": 0, "tfrDetailsPending": 0,
  "tfrUnavailable": false,       // true when the TFR source could not be read
  "weather": { "observation": {…}, "assessment": {…}, … },
  "taf": { … }
}
ADS-B carries no flight plan, so arrivals and departures are inferred from altitude AGL, vertical rate, and whether the aircraft is closing on the field over its recorded trail, within 40 NM. Treat them as a picture, not a schedule. An empty tfrs list with tfrUnavailable: true means unknown, not clear.

GET/api/weather

Aviation weather (METAR/TAF) with a drone-operations assessment, by point or by station.

ParameterRequiredDescription
lat, lonyes*Point of interest, −90…90 / −180…180. Required unless station is given; otherwise 400 {"error":"lat and lon (or station) are required"}.
stationyes*ICAO station id (e.g. KDET). Returns that station's observation, forecast and assessment; 404 if it has no current observation.
radiusnoSearch radius for nearby stations, NM. Default 30.
windLimit, gustLimitnoYour aircraft's wind/gust limits in kt — folded into the assessment verdict.
curl -s '/api/weather?lat=42.33&lon=-83.05'
{
  "observation": {
    "station": "KDET", "name": "Detroit/C Young Arpt, MI, US",
    "lat": 42.4072, "lon": -83.009, "elevFt": 623,
    "observedAt": "2026-08-21T00:53:00.000Z", "ageMin": 18,
    "tempC": 22.8, "dewpointC": 11.1, "windDir": 70, "windKt": 8, "gustKt": null,
    "visibilitySm": 10, "ceilingFt": null, "clouds": [],
    "altimeterHpa": 1018.7, "flightCategory": "VFR", "densityAltitudeFt": 1508,
    "raw": "METAR KDET 210053Z 07008KT 10SM CLR 23/11 A3008 …", "distNm": 5
  },
  "otherStations": [ … ],
  "forecast": { … TAF, when available … },
  "assessment": { "verdict": "go", // "go" | "caution" | "no-go" | "unknown"
                  "findings": [ … ], "maxAltitudeFt": null, … },
  "hazards": [ … SIGMETs etc … ],
  "sun": { … sunrise and sunset … }
}

GET/api/geocode

Address and place lookup: the US Census geocoder for street addresses, OpenStreetMap Nominatim for places, plus raw lat, lon strings. Open to signed-out callers, who use the heavy tier. Nominatim allows 1 request per second, and NOREVYN enforces that server-side.

ParameterRequiredDescription
qyesFree-text query. Fewer than 3 characters returns {"results":[]}. Upstream failures return 200 with {"results":[], "error": "…"}.
curl -s '/api/geocode?q=1600+Pennsylvania+Ave+NW+Washington+DC'
{
  "results": [
    { "label": "1600 PENNSYLVANIA AVE NW, WASHINGTON, DC, 20500",
      "lat": 38.89869893252, "lon": -77.03518753691,
      "source": "census",      // "census" | "osm" | "coords"
      "kind": "address" }      // "address" | place type | "coordinates"
  ]
}

GET/api/uas/brief

A complete pre-flight airspace briefing for a drone operation at a point: LAANC/UASFM ceiling, controlled airspace, TFRs, national-security restrictions, special-use airspace, stadiums, live low-altitude traffic, and weather — combined into one verdict.

ParameterRequiredDescription
lat, lonyesOperation point. Missing, blank, or out-of-range values are a 400.
radiusnoTraffic scan radius, NM. Clamped to 1–25. Default 5.
altnoPlanned altitude, ft AGL, 1–2000. 0 or absent means "no altitude planned"; any other invalid value is a 400 ("alt must be a number between 1 and 2000 feet AGL."). With an altitude the verdict checks your operation against the binding limit, not just what exists at the point.
windLimit, gustLimitnoAircraft wind/gust limits in kt for the weather assessment.
aircraft, mission, timenoFree-text operation details (aircraft up to 60 characters, mission up to 40), echoed back in the mission object for record-keeping.
atnoPlanned start time, e.g. an ISO-8601 timestamp. Adds a planned block: TFRs in force during the window, forecast weather for it, and sunrise and sunset on that day. Live traffic is never forecast.
durationMinnoLength of the planned window, minutes, 0–1440.
streamno1 answers as text/event-stream: a start event, one source event per upstream as it answers, then the complete briefing as a brief event. The verdict is never streamed early.
curl -s '/api/uas/brief?lat=42.3314&lon=-83.0458&radius=5&alt=200'
{
  "point": { "lat": 42.3314, "lon": -83.0458, "radiusNm": 5 },
  "planned": null,                     // filled in when `at` is given
  "source": "live",                    // traffic: "live" | "unavailable"
  "uasfm": null,                       // UASFM grid cell here, or null
  "laanc": {
    "required": false, "maxAutoApprovalFt": null,
    "controllingAirspace": [], "overlyingAirspace": [ { "name": "DETROIT CLASS B", … } ]
  },
  "airspace": [ … every airspace feature at the point … ],
  "activeTfrs": [], "scheduledTfrs": [ { "notamId": "6/6868", "status": "scheduled",
    "startsInHours": 14.8, "type": "UAS PUBLIC GATHERING", … } ],
  "nearbyTfrs": [ … ], "tfrPending": 0, "tfrDetailsPending": 0, "tfrUnavailable": false,
  "nsufr": [], "sua": [], "frias": [ … ], "stadiums": [ … ],
  "traffic": { "declared": [ … ADS-B category-14 UAVs … ],
               "lowAltitude": [ … manned aircraft below 2,000 ft … ],
               "source": "live", "ageSec": 0 },
  "weather": { … same shape as /api/weather … },
  "maxAltitude": {
    "ft": 400, "limitedBy": "Part 107 ceiling",
    "limits": [ { "source": "Part 107 ceiling", "ft": 400 } ],
    "uncertain": false, "uncertainReason": null
  },
  "verdict": {
    "level": "caution",   // go | caution | authorization-required | no-go | unknown
    "headline": "Clear with advisories",
    "primaryReason": "1 TFR scheduled over this point.",
    "reasons": [ { "level": "caution", "text": "…" } ]
  },
  "mission": { "plannedAltFt": 200, "aircraft": null, "missionType": null,
               "plannedTime": null, "generatedAt": "2026-08-21T01:11:14.000Z", … },
  "advisories": [ { "level": "warning", "text": "…" } ],
  "sourceErrors": {},      // per-source failure messages, when any
  "sourceMs": { … how long each source took … },
  "complete": true         // false when any source failed, live traffic included
}
Missing data never reads as clearance. If any source fails — including live traffic that is unavailable, simulated, or more than 2 minutes old — complete is false, the failed checks are named in sourceErrors, and the verdict becomes unknown ("Insufficient data") unless a confirmed blocker already made it no-go. A source that does not answer in time counts as failed: 15 seconds for weather, airspace, the LAANC grid and TFRs, 8 seconds for the rest. Re-run the briefing before relying on it.

GET/api/uas/overlays

Map overlay geometry for a viewport: TFRs, the LAANC (UASFM) grid, national-security UAS restrictions, special-use airspace, FRIAs, and stadiums.

ParameterRequiredDescription
bboxyeslamin,lomin,lamax,lomax. Missing or malformed is 400 {"error":"bbox is required"}.
layersnoComma-separated: tfr (default), uasfm, nsufr, sua, fria, stadium.
curl -s '/api/uas/overlays?bbox=42.3,-83.2,42.5,-82.9&layers=tfr,uasfm,stadium'
{
  "tfrs": [ … ], "tfrPending": 0, "tfrDetailsPending": 0, "tfrTotal": 129,
  "uasfm": [ … grid cells with ceilings … ],
  "stadiums": [ … ],
  "tfrUnavailable": true,  // present only if the TFR layer failed
  "errors": [ … present only if a requested layer failed … ]
}
The UASFM grid is ~1 sq mile per cell, so it is only returned when the bbox covers less than 1.2 square degrees. A wider view gets "uasfm": [] with "uasfmNote": "Zoom in further to load the LAANC grid."

GET/api/showcase

A small sample of current nationwide TFRs, plus the LAANC grid around Detroit Metro (DTW), rebuilt at most every 30 seconds. Open to signed-out callers. Useful as a cheap "what is restricted right now" pulse.

curl -s '/api/showcase'
{
  "tfrTotal": 129, "activeCount": 79, "scheduledCount": 34, "pendingGeometry": 0,
  "tfrUnavailable": false,
  "tfrs": [
    { "notamId": "6/6931", "type": "HAZARDS", "status": "active",
      "startsInHours": -0.9, "city": "12NM N KETTLE FALLS", "state": "WA",
      "description": "…", "lowerFt": 0, "upperFt": 8000,
      "effective": "2026-08-21T00:15:00", "expires": "2026-08-31T04:00:00",
      "areas": [ { "type": "polygon", "points": [[48.899, -118.033], …] } ] }
    /* … up to 6, outlines simplified for thumbnails */
  ],
  "grid": { "label": "Detroit Metropolitan Wayne County (DTW)",
            "bbox": { "lamin": 42.14, "lomin": -83.45, "lamax": 42.29, "lomax": -83.26 },
            "cells": [ … grid cells with ceilings … ] }
  /* … plus outline and detail counters … */
}

GET/api/sources

Upstream provider health, from a heartbeat sampled every 5 minutes — reading it never touches the providers themselves. Open to signed-out callers.

curl -s '/api/sources'
{
  "checkedAt": 1787274492975, "ageSec": 173,
  "adsb": { "preferred": "adsblol", "pinned": null, "lastUsed": "adsblol",
            "openskyAuthenticated": false, "benched": {}, "notes": [] },
  "sources": [
    { "id": "adsb",    "role": "ADS-B traffic",  "vendor": "adsb.lol",              "ok": true, "status": 200, "ms": 433 },
    { "id": "uasfm",   "role": "LAANC ceilings", "vendor": "FAA UAS Facility Map",  "ok": true, "status": 200, "ms": 226 },
    { "id": "tfr",     "role": "Restrictions",   "vendor": "FAA TFR",               "ok": true, "status": 200, "ms": 393 },
    { "id": "weather", "role": "Weather",        "vendor": "NOAA Aviation Weather", "ok": true, "status": 200, "ms": 243 }
    /* … airspace, tfr-shapes, geocode … */
  ]
}

GET/api/status

Minimal platform status: whether the server runs on live data or in demo mode, and whether traffic is healthy. Open to signed-out callers. Operator detail — credit budget, provider benching, the monitoring loop, cache and memory figures — is added only for the platform admin token.

curl -s '/api/status'
{
  "dataMode": "live",      // "live" | "demo"
  "traffic": { "source": "adsblol", "healthy": true, "trackedAircraft": 106 },
  "uptimeSec": 178
}

GET/healthz

Liveness probe. Open, and answered before rate limiting — safe for load-balancer health checks. /healthz/deep also reports storage health and answers 503 when durable storage is degraded.

curl -s '/healthz'
{ "ok": true, "uptimeSec": 178 }

Monitoring API

Continuous site monitoring: register an operating site once and a server-side loop re-evaluates the airspace over it every 5 minutes, emitting an alert when something material changes — a TFR is scheduled or goes active overhead, the LAANC ceiling moves, the weather verdict crosses a line, or checks degrade.

The token model

  • Monitoring calls need a signed-in session, like the rest of the API. On top of that, every site belongs to an owner token you generate yourself: the token, not your account, scopes every monitoring operation, and the API never lists tokens back.
  • Format: 24–64 characters of lowercase letters, digits, and hyphens, not starting with a hyphen (^[a-z0-9][a-z0-9-]{23,63}$). It must be random, not a name — anyone holding the token controls its sites, and a guessable token like mycompany exposes your alerts to whoever guesses it. Generate one with openssl rand -hex 16. On GET requests send it as an X-NOREVYN-Token header rather than a query parameter — query strings end up in proxy logs.
  • Lose the token, lose access to its sites. There is no recovery, and the server cannot tell you what your token was.
  • Caps: 5 sites per token, 500 sites server-wide, 2000 alerts retained overall and 100 per site, 100 alerts per query.
  • Deletion is operator-only. POST /api/watch/sites/delete requires the platform admin token; email info@norevyn.io to have a site removed. This is deliberate — a self-service delete keyed on a token means one leaked token silently erases someone's monitoring.
  • Monitoring is best-effort during early access. Alerts are kept in a rolling buffer, not a durable queue; treat this as situational awareness, not a system of record.
Durability, honestly: sites and alert history are saved to files on the server's disk and survive process restarts, but on hosts that wipe the disk on deploy they do not survive a redeploy — keep your own copy of each registration and re-register if your sites disappear. The alerts response says so itself. There is no SLA.

POST/api/watch/sites

Register a site for monitoring. Heavy tier. JSON body, max 16 KB (larger is 413; invalid JSON is 400).

Body fieldRequiredDescription
tokenyesYour owner token (format above), else a 400 whose error explains the format.
lat, lonyesSite coordinates, −90…90 / −180…180, else 400 {"error":"lat and lon are required"}.
namenoDisplay name, sanitized, ≤60 chars. Defaults to "Site {lat}, {lon}".
radiusNmnoClamped to 1–10 NM. Default 5.
plannedAltFtnoPlanned altitude, ft AGL; positive values only, capped at 2000, rounded. Anything else is stored as null.
windLimitKtnoPositive, capped at 80 kt — feeds the weather verdict for this site.
gustLimitKtnoPositive, capped at 90 kt.

Other 400 errors: "limit is 5 sites", "monitoring is at capacity", and "this point is already monitored" (same token, same point within ~0.0001°; the existing site is returned alongside the error). On success the first info alert is recorded at once and a baseline snapshot is queued; status stays null until it lands.

curl -s -X POST /api/watch/sites \
  -H 'content-type: application/json' \
  -d '{"token":"3f9c1a7e5b2d8046af13c9e7d2b45810","name":"Bridge site",
       "lat":42.3314,"lon":-83.0458,"radiusNm":5,"plannedAltFt":200}'
{
  "site": {
    "id": "d9275f3c12595c67",
    "name": "Bridge site", "lat": 42.3314, "lon": -83.0458,
    "radiusNm": 5, "plannedAltFt": 200,
    "createdAt": 1787274694119, "checkedAt": 0,
    "status": null            // populated after the baseline snapshot
  }
}

GET/api/watch/sites

List the sites owned by a token, each with its latest airspace snapshot.

ParameterRequiredDescription
tokenyesOwner token, sent as the X-NOREVYN-Token header (or ?token=), else 400 {"error":"invalid token"}.
curl -s -H 'X-NOREVYN-Token: 3f9c1a7e5b2d8046af13c9e7d2b45810' '/api/watch/sites'
{
  "sites": [
    {
      "id": "d9275f3c12595c67", "name": "Bridge site",
      "lat": 42.3314, "lon": -83.0458, "radiusNm": 5, "plannedAltFt": 200,
      "createdAt": 1787274694119, "checkedAt": 1787274694120,
      "status": {
        "at": 1787274694120,
        "tfrActive": [], "tfrScheduled": ["6/6868"],   // NOTAM ids overhead
        "hasGrid": false, "ceiling": null,             // LAANC grid + ceiling ft
        "wxVerdict": "go", "windKt": 8,
        "maxAlt": 400, "limitedBy": "Part 107",
        "unknown": []      // sources that failed this cycle — treat as unknown, not clear
      }
    }
  ]
}

GET/api/watch/alerts

Alerts for all of a token's sites, oldest first. Poll with since to get only what is new.

ParameterRequiredDescription
tokenyesOwner token, as the X-NOREVYN-Token header (or ?token=).
sincenoEpoch milliseconds; only alerts strictly newer are returned. At most the 100 newest per query.
curl -s -H 'X-NOREVYN-Token: 3f9c1a7e5b2d8046af13c9e7d2b45810' '/api/watch/alerts?since=1787274000000'
{
  "alerts": [
    { "id": "23a695bfbddb", "siteId": "d9275f3c12595c67", "siteName": "Bridge site",
      "at": 1787274694119,
      "level": "info",       // "info" | "warning" | "critical"
      "text": "Monitoring started for Bridge site. Checks run every 5 minutes." }
  ],
  "retention": "persisted across restarts; on ephemeral-disk hosts a redeploy clears history"
}
Alerts fire only on material change between consecutive snapshots: a TFR becoming active or newly scheduled overhead (and one when it is no longer active), a LAANC ceiling moving or the site entering or leaving a grid, the weather verdict crossing go/caution/no-go, and one alert per transition into or out of degraded checking. A failed upstream check is excluded from diffing entirely, so an outage can never masquerade as "restriction lifted".

POST/api/watch/sites/delete

Operator-only. Removing a monitored site requires the platform admin token — it is not a self-service API capability. An unknown site is 404 {"error":"no such site"}.

Why this is not public: with sites keyed on a token, a self-service delete means one leaked or guessed token silently erases someone's monitoring with no recovery. Email info@norevyn.io with the site id to have it removed.
curl -s -X POST /api/watch/sites/delete \
  -H 'content-type: application/json' \
  -H 'x-admin-token: <operator token>' \
  -d '{"token":"3f9c1a7e5b2d8046af13c9e7d2b45810","id":"d9275f3c12595c67"}'
// signed in, without the operator token → 403
{ "error": "Deleting monitored sites is restricted to the platform operator. Email info@norevyn.io to remove one." }

// no session and no operator token → 401
{ "error": "Sign in to use this endpoint.", "signIn": "/signin" }

Fair use and disclaimer

  • Situational awareness only — never authorization. Nothing this API returns is a clearance, a LAANC authorization, or legal advice. A "go" verdict means the sources NOREVYN could reach showed no blocker at that moment; it is the start of your pre-flight process, not the end. NOREVYN does not retrieve NOTAMs at all. Obtain LAANC authorization through an FAA-approved provider, check official NOTAMs yourself, and follow 14 CFR Part 107 (or your applicable rules).
  • Data provenance. Traffic comes from adsb.lol and adsb.fi (community ADS-B — coverage gaps and dropouts are normal), and from the OpenSky Network only where the server is configured to use it; airspace, UASFM/LAANC grids, TFRs, and related layers from FAA public services; weather from NOAA aviationweather.gov; geocoding from the US Census and OpenStreetMap Nominatim. All of it can be delayed, incomplete, or wrong, and each provider's own terms apply to your use of their data.
  • Check source. An empty traffic list means an empty sky only when source is "live" and the snapshot is recent. "unavailable" means unknown, and "sim" — demo servers only — is never real traffic.
  • Be gentle. The heavy endpoints proxy free upstream services with real quotas. Respect the rate limits, poll /api/states no faster than the nextPollMs it returns, cache /api/airports, and fetch /api/showcase no more than every 30 seconds. Sustained abuse gets your IP throttled at 429.
  • No SLA, no warranty. This service is provided as-is: monitored sites and alert history may be lost on a redeploy, endpoints and response shapes may change, and availability is not guaranteed. Do not make it a single point of failure for flight-safety decisions.

Data: adsb.lol, adsb.fi, FAA, NOAA, US Census, OpenStreetMap. Response shapes on this page describe the current server and may change.