{"record":{"id":"f0b2a4563e266fb6","repo":"decolua/9router","slug":"kilo-api-returned-res-status","errorCode":null,"errorMessage":"Kilo API returned ${res.status}","messagePattern":"Kilo API returned (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/app/api/providers/kilo/free-models/route.js","lineNumber":25,"sourceCode":"let cacheTimestamp = 0;\nconst CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour\n\nexport async function GET() {\n  const now = Date.now();\n\n  // Return cached result if still valid\n  if (cachedModels && now - cacheTimestamp < CACHE_TTL_MS) {\n    return NextResponse.json({ models: cachedModels, cached: true });\n  }\n\n  try {\n    const res = await fetch(KILO_MODELS_URL, {\n      headers: { \"Accept\": \"application/json\" },\n      signal: AbortSignal.timeout(10000),\n    });\n\n    if (!res.ok) {\n      throw new Error(`Kilo API returned ${res.status}`);\n    }\n\n    const json = await res.json();\n    const allModels = json.data || [];\n\n    const freeModels = allModels\n      .filter((m) => m.isFree === true)\n      .map((m) => ({\n        id: m.id,\n        name: m.name,\n        isFree: true,\n        context_length: m.context_length || 0,\n      }));\n\n    cachedModels = freeModels;\n    cacheTimestamp = now;\n\n    return NextResponse.json({ models: freeModels, cached: false });","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/app/api/providers/kilo/free-models/route.js#L7-L43","documentation":"The kilo free-models route fetches KILO_MODELS_URL with a 10s timeout and expects a 2xx JSON response. If res.ok is false (any 4xx/5xx from the Kilo upstream), the route surfaces the upstream status in this error instead of trying to parse a non-JSON error body.","triggerScenarios":"GET /api/providers/kilo/free-models while the Kilo models API returns 401/403 (auth required), 404 (endpoint moved), 429 (rate limited), or 5xx outage; also when a proxy/firewall intercepts with an error status.","commonSituations":"Kilo API is down or geo-blocked, the hard-coded KILO_MODELS_URL changed upstream, corporate proxy returns 403, or rate limiting from repeated polling.","solutions":["Retry later if the Kilo API is having an outage (5xx).","Check the returned status in the message: 401/403 means auth/blocking — verify network access to the Kilo endpoint from this machine.","Verify KILO_MODELS_URL is still valid upstream and update the constant if Kilo moved the endpoint.","Reduce polling frequency if hitting 429 rate limits."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const models = await fetchFreeModels();\n} catch (e) {\n  if (e.message.startsWith(\"Kilo API returned\")) {\n    const status = parseInt(e.message.match(/\\d+$/)?.[0], 10);\n    if (status >= 500 || status === 429) await delay(backoff).then(retry);\n    else console.error(\"Kilo upstream rejected the request:\", status);\n  } else throw e;\n}","preventionTips":["Retry with backoff on 429/5xx only","Cache the free-models list instead of polling repeatedly","Check Kilo API status/docs when the status is 404 (endpoint moved)","Verify outbound network/proxy access to the Kilo endpoint"],"tags":["network","upstream","http-error","kilo"],"backgroundTag":"upstream-http-error","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}