koala73/worldmonitor · error · ApiError
Unsupported airport delay parameter
Error message
Unsupported airport delay parameter: ${key} What it means
listAirportDelays validates every query parameter in the request URL against ALLOWED_QUERY_PARAMS and throws ApiError 400 for any unrecognized key. The library accepts a strict allowlist (effectively 'page_size' and 'rpc' with exact required values) to catch client-side typos and stale parameters early. It also rejects duplicate parameters, non-'0' page_size values, and rpc values other than 'list-airport-delays'.
Solutions
- Remove the unsupported query parameter and send only keys in ALLOWED_QUERY_PARAMS ('page_size' and 'rpc').
- Fix parameter naming: use snake_case 'page_size', not 'pageSize' or 'limit'.
- Stop appending cache-buster/timestamp query params to this RPC URL.
- Call listAirportDelays through the request-object API (req.pageSize) instead of hand-building URLs, so the framework serializes only valid params.
Example fix
// before
const url = `${base}/list-airport-delays?pageSize=0&_=${Date.now()}`;
// after
const url = `${base}/list-airport-delays?page_size=0&rpc=list-airport-delays`;
// or better: use the request object
listAirportDelays(ctx, { pageSize: 0 }); Defensive patterns
Strategy: validation
Validate before calling
const ALLOWED = new Set(['page_size', 'rpc']);
function buildDelaysUrl(base, params = {}) {
const qs = new URLSearchParams();
for (const [k, v] of Object.entries(params)) {
if (!ALLOWED.has(k)) throw new Error(`Unsupported param: ${k}`);
qs.set(k, v);
}
return `${base}?${qs}`;
} Try / catch
try {
return await listAirportDelays(ctx, req);
} catch (e) {
if (e instanceof ApiError && e.status === 400 && e.message.startsWith('Unsupported')) {
// strip unknown params and retry once with the canonical URL
return listAirportDelays(ctx, { pageSize: 0 });
}
throw e;
} Prevention
- Always build this URL from the typed request object, never by hand.
- Use snake_case param names ('page_size') exactly as the server defines them.
- Disable cache-buster query params on RPC endpoints.
- Keep the client's allowlist in sync with server ALLOWED_QUERY_PARAMS.
When it happens
Trigger: Sending any query string key not in ALLOWED_QUERY_PARAMS, e.g. ?pageSize=0 (camelCase instead of snake_case), ?limit=10, ?airport=JFK, or appended cache-buster params like ?_=1690000000.
Common situations: Client builds the URL from a shared serializer that adds pagination params this endpoint does not support; a frontend adds cache-busting query params; migration from another delays endpoint with different parameter names; hand-typed URLs during debugging.
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
- Expected at most 20 IATA airport codes
- Aircraft identifier is too long
- disruptionPct must be an integer from 0 to 100 for a…
- Duplicate airport delay parameter
- Dynamic ApiError(400, message) via local invalid() helper…
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22).
Data as JSON: /api/errors/cd4589985693cd00.
Report an issue: GitHub.
Appendix: source
Thrown at server/worldmonitor/aviation/v1/list-airport-delays.ts:43
import { ApiError } from '../../../../src/generated/server/worldmonitor/aviation/v1/service_server';
// @ts-expect-error — JS module, no declaration file
import { captureSilentError } from '../../../../api/_sentry-edge.js';
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 }> => {View on GitHub (pinned to e586b8b4b8)