{"record":{"id":"5083f843519ec1df","repo":"Wei-Shaw/sub2api","slug":"keyusage-queryfailed","errorCode":null,"errorMessage":"keyUsage.queryFailed","messagePattern":"keyUsage\\.queryFailed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/views/KeyUsageView.vue","lineNumber":868,"sourceCode":"  try {\n    return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'\n  } catch {\n    return 'UTC'\n  }\n}\n\n// ==================== API Query ====================\n\nasync function fetchUsage(key: string) {\n  const dateParams = getDateParams()\n  const url = buildGatewayUrl('/v1/usage') + (dateParams ? '?' + dateParams : '')\n  const res = await fetch(url, {\n    headers: { 'Authorization': 'Bearer ' + key },\n  })\n  if (!res.ok) {\n    const body = await res.json().catch(() => null)\n    const msg = body?.error?.message || body?.message || `${t('keyUsage.queryFailed')} (${res.status})`\n    throw new Error(msg)\n  }\n  return await res.json()\n}\n\nasync function queryKey() {\n  if (isQuerying.value) return\n  const key = apiKey.value.trim()\n  if (!key) {\n    appStore.showInfo(t('keyUsage.enterApiKey'))\n    return\n  }\n\n  isQuerying.value = true\n  showResults.value = true\n  showLoading.value = true\n  resultData.value = null\n\n  try {","sourceCodeStart":850,"sourceCodeEnd":886,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/frontend/src/views/KeyUsageView.vue#L850-L886","documentation":"In frontend/src/views/KeyUsageView.vue:868, fetchUsage() calls GET {gateway}/v1/usage with a raw Bearer key. On a non-OK response it tries to parse JSON and prefers body.error.message or body.message; only when the body is missing/unparsable does it fall back to the localized 'keyUsage.queryFailed' plus the status. So this exact message means the gateway returned an error with no readable JSON body.","triggerScenarios":"GET /v1/usage returns non-2xx with a non-JSON body: gateway returns HTML error page (502/504 from an nginx in front), plain-text 429 from a rate limiter, connection reset body, or 404 with empty body because the path is wrong (wrong gateway base URL in buildGatewayUrl).","commonSituations":"Wrong API base URL configured (points at the frontend server which serves the SPA HTML for unknown routes); gateway temporarily down; CDN interposing an HTML challenge page; CORS preflight failure surfacing as an opaque error.","solutions":["Verify buildGatewayUrl('/v1/usage') resolves to the actual gateway origin, not the SPA host.","curl the URL with the same Bearer key to see the raw non-JSON body and fix the upstream (proxy 502 HTML, etc.).","Include a snippet of the raw text (first 120 chars) in the thrown message when JSON parsing fails, to speed diagnosis.","Handle 401 distinctly (bad/expired key) with the enterApiKey prompt."],"exampleFix":"// before\nconst body = await res.json().catch(() => null)\nconst msg = body?.error?.message || body?.message || `${t('keyUsage.queryFailed')} (${res.status})`\nthrow new Error(msg)\n\n// after\nlet body: any = null\nconst raw = await res.text().catch(() => '')\ntry { body = JSON.parse(raw) } catch {}\nconst msg = body?.error?.message || body?.message\n  || `${t('keyUsage.queryFailed')} (${res.status}${raw ? `: ${raw.slice(0, 120)}` : ''})`\nthrow new Error(msg)","handlingStrategy":"try-catch","validationCode":"const trimmed = apiKey.value.trim();\nif (!trimmed || !/^[A-Za-z0-9_-]{20,}$/.test(trimmed)) { showEnterApiKey(); return; } // before fetch","typeGuard":null,"tryCatchPattern":"try { await fetchUsage(key); }\ncatch (e) {\n  if (String(e.message).includes('queryFailed')) {\n    showError('Gateway unreachable — check API base URL and key'); return;\n  }\n  showError(e.message);\n}","preventionTips":["Verify the gateway base URL before shipping (config check, not a guess)","Include a raw-body snippet when JSON parsing fails for faster diagnosis","Handle 401 separately with a re-enter-key prompt"],"tags":["http","gateway","usage-api","error-handling","frontend"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}