koala73/worldmonitor · error · ApiError
Invalid airport delay route
Error message
Invalid airport delay route
What it means
This is a generic request-validation sentinel error raised at the top of listAirportDelays when the request's query string contains a parameter not in ALLOWED_QUERY_PARAMS (page_size, cursor, region, min_severity, jmespath, _debug, rpc). The handler iterates URL search params and, on the first unrecognized key, throws 'Invalid airport delay route' to reject the call rather than silently ignoring unknown inputs. It fires when a client calls the airport-delays endpoint with a typo'd, deprecated, or unsupported query parameter (e.g. ?airport=KJFK or ?limit=10); the faulting input is the offending query parameter name in the request URL.
Solutions
- Send rpc=list-airport-delays exactly, matching case and spelling
- Update hardcoded route names after any endpoint rename
- Derive the rpc param from the same constant used for the path instead of a separate literal
Example fix
// before fetch(url + '?rpc=list-airport-delay'); // after fetch(url + '?rpc=list-airport-delays');
Defensive patterns
Strategy: validation
Validate before calling
if (params.has('rpc') && params.get('rpc') !== 'list-airport-delays') throw new Error('bad rpc param'); Type guard
const isAirportDelayRoute = (p) => p.get('rpc') === 'list-airport-delays'; Try / catch
try { await listAirportDelays(ctx, req); } catch (e) { if (e instanceof ApiError && e.status === 400 && e.message === 'Invalid airport delay route') { fixRpcParam(); } else throw e; } Prevention
- Share a single constant for the rpc value between path and query builder
- Update all literals when endpoints are renamed
- Add a test comparing rpc param against the route constant
- Never hardcode rpc strings inline in call sites
When it happens
Trigger: GET .../list-airport-delays?rpc=some-other-rpc or a misspelled/mismatched rpc value like ?rpc=list-airport-delay.
Common situations: Copy-pasting a query string from another endpoint; stale hardcoded RPC names after a rename; proxy or gateway rewriting the rpc param.
Understand the failure class
Background: "Invalid query parameter" / "Failed to parse value of ...": fixing bad query string parameters across APIs — this error's family across 36 libraries.
Related errors
- Airport delay filters are not supported
- Airport delay page_size must be 0
- deadlineMs must be a positive number
- Duplicate airport delay parameter
- Expected finite viewport coordinates
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22).
Data as JSON: /api/errors/98fc6de43aa77000.
Report an issue: GitHub.
Appendix: source
Thrown at server/worldmonitor/aviation/v1/list-airport-delays.ts:47
const FAA_CACHE_KEY = 'aviation:delays:faa:v1';
const INTL_CACHE_KEY = 'aviation:delays:intl:v3';
const FAA_AIRPORT_SET = new Set(FAA_AIRPORTS);
const INTL_AIRPORT_SET = new Set(AVIATIONSTACK_AIRPORTS);
const ALLOWED_QUERY_PARAMS = new Set(['page_size', 'cursor', 'region', 'min_severity', 'jmespath', '_debug', 'rpc']);
export async function listAirportDelays(
ctx: ServerContext,
req: ListAirportDelaysRequest,
): Promise<ListAirportDelaysResponse> {
const seenParams = new Set<string>();
for (const [key, value] of new URL(ctx.request.url).searchParams) {
if (!ALLOWED_QUERY_PARAMS.has(key)) throw new ApiError(400, `Unsupported airport delay parameter: ${key}`, '');
if (seenParams.has(key)) throw new ApiError(400, `Duplicate airport delay parameter: ${key}`, '');
seenParams.add(key);
if (key === 'page_size' && value !== '0') throw new ApiError(400, 'Airport delay page_size must be 0', '');
if (key === 'rpc' && value !== 'list-airport-delays') throw new ApiError(400, 'Invalid airport delay route', '');
}
if ((req.pageSize ?? 0) !== 0 || req.cursor
|| (req.region && req.region !== 'AIRPORT_REGION_UNSPECIFIED')
|| (req.minSeverity && req.minSeverity !== 'FLIGHT_DELAY_SEVERITY_UNSPECIFIED')) {
throw new ApiError(400, 'Airport delay filters are not supported', '');
}
// 1. FAA (US) — seed-only read
// faaSourceCovered = the seed cache hit AND returned a valid alerts array.
// A miss/parse-error means we have no telemetry for any FAA airport this
// tick — we MUST NOT publish synthetic "normal" rows for them. See #3707.
// PERF: the three inputs below are independent (different Redis keys / an
// independent fetcher) and merge only afterwards — start them concurrently
// instead of paying three serial round-trips per request.
const faaRead = (async (): Promise<{ faaAlerts: AirportDelayAlert[]; faaSourceCovered: boolean; available: boolean }> => {
let faaAlerts: AirportDelayAlert[] = [];
let faaSourceCovered = false;
try {
const seed = await readCachedJson(FAA_CACHE_KEY, true);View on GitHub (pinned to e586b8b4b8)