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/loginwith{"email","password"}: the response sets an HTTP-only session cookie and also returns the sessiontoken. From a page served by NOREVYN, send the cookie (credentials: 'include'); from your own server, sendAuthorization: Bearer <token>. A session expires 30 days after sign-in. - Cross-origin — JSON responses outside
/api/org/carryAccess-Control-Allow-Origin: *, so the open endpoints can be read from any page. Signed-in calls cannot: browsers do not send theSameSite=Laxsession cookie on cross-site requests, and a request with anAuthorizationheader needs a CORS preflight (OPTIONS), which the server answers with405. Call the API from a page served by NOREVYN, or from a server. - Methods — the endpoints on this page are
GET/HEAD, exceptPOST /api/org/loginand the two monitoring writes.POSTis accepted only on the account, pilot network, remote operations, monitoring, event and payment-webhook routes. Anything else returns405. - 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 with414. - Errors — non-2xx responses are JSON with an
errorstring, e.g.{"error":"bbox is required"}. A few add context, such assignInon a401.
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.
| Tier | Burst capacity | Refill | Applies to |
|---|---|---|---|
| heavy | 40 requests | 0.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. |
| auth | 20 requests | 1 every 6 sec | POST /api/org/login, POST /api/org/register and POST /api/network/register. |
| api | 240 requests | 8 / sec | Every 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.
latmust be within −90…90 andlonwithin −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<lamaxandlomin<lomaxmust hold or the request is a400. - 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 namedlastContact(seconds) or is an ISO-8601 string. - Traffic is live or unavailable, never invented —
sourceis"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);ageSecgives a snapshot's age when it is known. When no real picture can be had,sourceis"unavailable",statesis empty anderrorsays 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.
| Parameter | Required | Description |
|---|---|---|
| bbox | no | lamin,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.
| Parameter | Required | Description |
|---|---|---|
| icao24 | yes (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 */
]
}
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 } /* … */
]
GET/api/airport/search
Type-ahead airport search across live FAA airport records plus the bundled list. Cheap tier — it is designed to be called per keystroke.
| Parameter | Required | Description |
|---|---|---|
| q | yes | IATA/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.
| Parameter | Required | Description |
|---|---|---|
| code | yes (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": { … }
}
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.
| Parameter | Required | Description |
|---|---|---|
| lat, lon | yes* | Point of interest, −90…90 / −180…180. Required unless station is given; otherwise 400 {"error":"lat and lon (or station) are required"}. |
| station | yes* | ICAO station id (e.g. KDET). Returns that station's observation, forecast and assessment; 404 if it has no current observation. |
| radius | no | Search radius for nearby stations, NM. Default 30. |
| windLimit, gustLimit | no | Your 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.
| Parameter | Required | Description |
|---|---|---|
| q | yes | Free-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.
| Parameter | Required | Description |
|---|---|---|
| lat, lon | yes | Operation point. Missing, blank, or out-of-range values are a 400. |
| radius | no | Traffic scan radius, NM. Clamped to 1–25. Default 5. |
| alt | no | Planned 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, gustLimit | no | Aircraft wind/gust limits in kt for the weather assessment. |
| aircraft, mission, time | no | Free-text operation details (aircraft up to 60 characters, mission up to 40), echoed back in the mission object for record-keeping. |
| at | no | Planned 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. |
| durationMin | no | Length of the planned window, minutes, 0–1440. |
| stream | no | 1 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
}
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.
| Parameter | Required | Description |
|---|---|---|
| bbox | yes | lamin,lomin,lamax,lomax. Missing or malformed is 400 {"error":"bbox is required"}. |
| layers | no | Comma-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 … ]
}
"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 likemycompanyexposes your alerts to whoever guesses it. Generate one withopenssl rand -hex 16. On GET requests send it as anX-NOREVYN-Tokenheader 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/deleterequires 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.
POST/api/watch/sites
Register a site for monitoring. Heavy tier. JSON body, max 16 KB (larger is 413; invalid JSON is 400).
| Body field | Required | Description |
|---|---|---|
| token | yes | Your owner token (format above), else a 400 whose error explains the format. |
| lat, lon | yes | Site coordinates, −90…90 / −180…180, else 400 {"error":"lat and lon are required"}. |
| name | no | Display name, sanitized, ≤60 chars. Defaults to "Site {lat}, {lon}". |
| radiusNm | no | Clamped to 1–10 NM. Default 5. |
| plannedAltFt | no | Planned altitude, ft AGL; positive values only, capped at 2000, rounded. Anything else is stored as null. |
| windLimitKt | no | Positive, capped at 80 kt — feeds the weather verdict for this site. |
| gustLimitKt | no | Positive, 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.
| Parameter | Required | Description |
|---|---|---|
| token | yes | Owner 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.
| Parameter | Required | Description |
|---|---|---|
| token | yes | Owner token, as the X-NOREVYN-Token header (or ?token=). |
| since | no | Epoch 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"
}
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"}.
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 whensourceis"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/statesno faster than thenextPollMsit returns, cache/api/airports, and fetch/api/showcaseno more than every 30 seconds. Sustained abuse gets your IP throttled at429. - 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.