koala73/worldmonitor · warning · ApiError

Expected a six-character hexadecimal ICAO address

Error message

Expected a six-character hexadecimal ICAO address

What it means

trackAircraft requires that a provided icao24, once trimmed and lowercased, matches /^[0-9a-f]{6}$/ — exactly six hexadecimal characters. Empty is allowed (then callsign must be supplied); any other shape throws ApiError(400, 'Expected a six-character hexadecimal ICAO address').

Solutions

  1. Send exactly six hex characters, e.g. '4ca2b6'
  2. Strip separators and validate with /^[0-9a-fA-F]{6}$/ before calling
  3. If you only have a callsign, omit icao24 entirely and pass callsign

Example fix

// before
await trackAircraft(ctx, { icao24: '4C-A2-B6' });
// after
const icao24 = raw.replace(/[^0-9a-fA-F]/g, '').toLowerCase();
await trackAircraft(ctx, { icao24 });
Defensive patterns

Strategy: validation

Validate before calling

if (icao24 && !/^[0-9a-fA-F]{6}$/.test(icao24.trim())) throw new Error('icao24 must be 6 hex characters');

Type guard

const isIcao24 = (v: unknown): v is string => typeof v === 'string' && /^[0-9a-fA-F]{6}$/.test(v.trim());

Try / catch

try {
  return await trackAircraft(ctx, { icao24 });
} catch (e) {
  if (e instanceof ApiError && e.status === 400) {
    return { error: e.message, hint: 'expected six hex chars, e.g. 4ca2b6' };
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling track-aircraft with icao24 like 'ABC123' (uppercase is fine only because it is lowercased first, but 'G' letters outside a-f are not), 5- or 7-character hex, or identifiers containing separators ('40-6B-7F').

Common situations: Passing registration numbers ('D-ABCD') or flight numbers as icao24; copying ICAO24 with spaces/dashes from ADS-B sites; truncating a longer hex string by hand.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/3c64dc036abacb2d. Report an issue: GitHub.

Appendix: source

Thrown at server/worldmonitor/aviation/v1/track-aircraft.ts:121

        return `aviation:track:bbox:${Math.floor(req.swLat)}:${Math.floor(req.swLon)}:${Math.ceil(req.neLat)}:${Math.ceil(req.neLon)}:v1`;
    }
    return 'aviation:track:all:v2';
}

// Response-level source values (TrackAircraftResponse.source):
//   'opensky'           — data from OpenSky via relay
//   'wingbits'          — data from Wingbits via relay
//   'none'              — all real sources returned empty or failed; positions = []
export async function trackAircraft(
    ctx: ServerContext,
    req: TrackAircraftRequest,
): Promise<TrackAircraftResponse> {
    const rawIcao24 = req.icao24 ?? '';
    const rawCallsign = req.callsign ?? '';
    if (rawIcao24.length > 16 || rawCallsign.length > 16) throw new ApiError(400, 'Aircraft identifier is too long', '');
    const icao24 = rawIcao24.trim().toLowerCase();
    const callsign = rawCallsign.trim().toUpperCase();
    if (rawIcao24 && !/^[0-9a-f]{6}$/.test(icao24)) throw new ApiError(400, 'Expected a six-character hexadecimal ICAO address', '');
    if (rawCallsign && !/^[A-Z0-9]{1,8}$/.test(callsign)) throw new ApiError(400, 'Expected an alphanumeric callsign of at most eight characters', '');
    req = { ...req, icao24, callsign };
    if (icao24 || callsign) await admitIdentifierLookup(ctx.request);

    const redistributableOnly = requiresRedistributableProviders(ctx.request);
    const cacheKey = `${buildCacheKey(req)}${redistributableOnly ? ':redistributable' : ''}`;

    let result: { positions: PositionSample[]; source: string } | null = null;
    try {
        const positiveTtl = req.callsign ? CALLSIGN_CACHE_TTL : CACHE_TTL;
        const negativeTtl = req.callsign ? CALLSIGN_NEGATIVE_TTL : CACHE_TTL;
        result = await cachedFetchJson<{ positions: PositionSample[]; source: string }>(
            cacheKey, positiveTtl, async () => {
                const relayBase = getRelayBaseUrl();
                const isCallsignOnly = !!req.callsign && !req.icao24 && isDegenerateBbox(req);

                // For callsign-only searches, try Wingbits first — commercial flights like UAE20
                // are Wingbits-exclusive and not visible in OpenSky. Trying OpenSky first wastes

View on GitHub (pinned to 7d06c8633d)