koala73/worldmonitor · error

IMD_AUTH_RESPONSE_INVALID

Error message

IMD_AUTH_RESPONSE_INVALID

What it means

Thrown by mintImdApiToken when the auth endpoint returned HTTP 2xx but readBoundedJsonResponse could not parse a bounded JSON body. The only parse-stage error that passes through unchanged is IMD_RESPONSE_TOO_LARGE:<bytes>; everything else (invalid JSON, empty body, truncated response, non-JSON content type) is normalized to IMD_AUTH_RESPONSE_INVALID. imdAuthFailureReason maps it 1:1 to the caller-facing failure reason.

Solutions

  1. Log the raw response body (within bounds) for one failing request to see what the endpoint actually returned.
  2. If it's HTML/WAF challenge content, the request needs different headers or must come from an allowlisted IP.
  3. If the API contract changed, update the payload parsing to match the new format.
  4. If the body is intermittently truncated, increase timeoutMs or retry; persistent truncation suggests a proxy issue.
  5. Ensure maxBytes is left at the default (IMD_MAX_BYTES) unless a token response legitimately exceeds it — then handle the RESPONSE_TOO_LARGE path instead.

Example fix

// before
payload = await readBoundedJsonResponse(response, maxBytes);
// after
const text = await response.text();
try { payload = JSON.parse(text); }
catch { console.error('IMD auth body head:', text.slice(0, 200)); throw new Error('IMD_AUTH_RESPONSE_INVALID'); }
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

function isOAuthTokenPayload(p) { return p !== null && typeof p === 'object' && typeof p.access_token === 'string' && p.access_token.length > 0 && typeof p.token_type === 'string' && typeof p.expires_in === 'number' && Number.isFinite(p.expires_in) && p.expires_in > 0; }

Try / catch

const { token, error } = await mintImdApiToken({ email, password });
if (error === 'IMD_AUTH_RESPONSE_INVALID') {
  // inspect body once, then retry with backoff in case of truncated/CDN response
  await sleep(RETRY_MS);
  return mintImdApiToken({ email, password });
}

Prevention

When it happens

Trigger: IMD_OAUTH_TOKEN_URL returns 200 with a body that is not valid JSON, an empty body, HTML (e.g. a login page or WAF challenge), or a body larger than maxBytes that fails the bounded read with an error other than the RESPONSE_TOO_LARGE marker.

Common situations: IMD behind a CDN/WAF serving an HTML challenge page with status 200; IMD API contract change (new response format); captive proxy injecting content; truncated gzip/plain body from a flaky connection.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at scripts/lib/imd-cyclone-marine.mjs:839

  try {
    const response = await fetchFn(IMD_OAUTH_TOKEN_URL, {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        'Content-Type': 'application/json',
        'User-Agent': userAgent,
      },
      body: JSON.stringify({ email, password }),
      redirect: 'error',
      signal: AbortSignal.timeout(timeoutMs),
    });
    if (!response.ok) throw new Error(`IMD_AUTH_HTTP_${response.status}`);
    let payload;
    try {
      payload = await readBoundedJsonResponse(response, maxBytes);
    } catch (err) {
      if (/^IMD_RESPONSE_TOO_LARGE:\d+$/.test(String(err?.message || ''))) throw err;
      throw new Error('IMD_AUTH_RESPONSE_INVALID');
    }
    const accessToken = typeof payload?.access_token === 'string' ? payload.access_token : '';
    const tokenType = typeof payload?.token_type === 'string' ? payload.token_type.trim() : '';
    const expiresIn = Number(payload?.expires_in);
    if (
      !/^[\u0021-\u007E]+$/.test(accessToken)
      || tokenType.toLowerCase() !== 'bearer'
      || !Number.isFinite(expiresIn)
      || expiresIn <= 0
    ) {
      throw new Error('IMD_AUTH_RESPONSE_INVALID');
    }
    return { token: accessToken, error: null };
  } catch (err) {
    return { token: null, error: imdAuthFailureReason(err) };
  }
}

View on GitHub (pinned to 7d06c8633d)