Wei-Shaw/sub2api · error
keyUsage.queryFailed
Error message
keyUsage.queryFailed
What it means
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.
Source
Thrown at frontend/src/views/KeyUsageView.vue:868
try {
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'
} catch {
return 'UTC'
}
}
// ==================== API Query ====================
async function fetchUsage(key: string) {
const dateParams = getDateParams()
const url = buildGatewayUrl('/v1/usage') + (dateParams ? '?' + dateParams : '')
const res = await fetch(url, {
headers: { 'Authorization': 'Bearer ' + key },
})
if (!res.ok) {
const body = await res.json().catch(() => null)
const msg = body?.error?.message || body?.message || `${t('keyUsage.queryFailed')} (${res.status})`
throw new Error(msg)
}
return await res.json()
}
async function queryKey() {
if (isQuerying.value) return
const key = apiKey.value.trim()
if (!key) {
appStore.showInfo(t('keyUsage.enterApiKey'))
return
}
isQuerying.value = true
showResults.value = true
showLoading.value = true
resultData.value = null
try {View on GitHub (pinned to 073e92d171)
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.
Example fix
// before
const body = await res.json().catch(() => null)
const msg = body?.error?.message || body?.message || `${t('keyUsage.queryFailed')} (${res.status})`
throw new Error(msg)
// after
let body: any = null
const raw = await res.text().catch(() => '')
try { body = JSON.parse(raw) } catch {}
const msg = body?.error?.message || body?.message
|| `${t('keyUsage.queryFailed')} (${res.status}${raw ? `: ${raw.slice(0, 120)}` : ''})`
throw new Error(msg) Defensive patterns
Strategy: try-catch
Validate before calling
const trimmed = apiKey.value.trim();
if (!trimmed || !/^[A-Za-z0-9_-]{20,}$/.test(trimmed)) { showEnterApiKey(); return; } // before fetch Try / catch
try { await fetchUsage(key); }
catch (e) {
if (String(e.message).includes('queryFailed')) {
showError('Gateway unreachable — check API base URL and key'); return;
}
showError(e.message);
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- HTTP error! status: ${response.status}
- HTTP error! status: ${response.status}
- Passkeys are not supported by this browser
- Passkey sign-in was cancelled
- Passkey creation was cancelled
AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15).
Data as JSON: /api/errors/5083f843519ec1df.
Report an issue: GitHub.