{"record":{"id":"7ba02491697e31c2","repo":"atuinsh/atuin","slug":"usage-request-failed-status","errorCode":null,"errorMessage":"usage request failed ({status})","messagePattern":"usage request failed \\((.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/atuin-ai/src/usage.rs","lineNumber":106,"sourceCode":"}\n\n/// Fetch current usage from the hub. Mirrors the `credits` object on the\n/// chat `done` event, for refreshing without starting a chat.\npub async fn fetch_usage(endpoint: &reqwest::Url, token: &str) -> Result<UsageSnapshot> {\n    let url = endpoint.append_path(\"api/cli/usage\")?;\n\n    let response = reqwest::Client::new()\n        .get(url)\n        .header(USER_AGENT, crate::stream::APP_USER_AGENT)\n        .bearer_auth(token)\n        .timeout(Duration::from_secs(10))\n        .send()\n        .await\n        .context(\"failed to fetch usage\")?;\n\n    let status = response.status();\n    if !status.is_success() {\n        eyre::bail!(\"usage request failed ({status})\");\n    }\n\n    response.json::<UsageSnapshot>().await.context(\"failed to parse usage response\")\n}\n\n#[cfg(test)]\nmod tests {\n    use rstest::rstest;\n\n    use super::*;\n\n    #[test]\n    fn deserializes_server_payload() {\n        // Shape documented in the hub's CliUsageController / credits_payload.\n        let json = r#\"{\n            \"period\": \"calendar_monthly\",\n            \"resets_at\": \"2026-08-01T00:00:00Z\",\n            \"requests\": {\"used\": 3, \"limit\": -1},","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/atuinsh/atuin/blob/900007c48045867e7f44b0f9bc55ad9dbe99ee0d/crates/atuin-ai/src/usage.rs#L88-L124","documentation":"Fires in fetch_usage when the hub's GET api/cli/usage endpoint responds with a non-success HTTP status. This is not a network failure — the request reached the server, which rejected it (e.g. 401/403 for an expired or invalid bearer token, 429 rate limiting, or 5xx hub-side trouble). The status code is interpolated into the message so callers can distinguish auth problems (fix by re-authenticating) from transient server errors (fix by retrying later). It surfaces when a user refreshes their credit/usage snapshot outside of a chat session, mirroring the credits object normally delivered on the chat done event.","triggerScenarios":"Calling `fetch_usage` when the backend returns 4xx/5xx — invalid/expired auth token, rate limiting, or service error.","commonSituations":"Checking AI usage with a stale session (401), exceeding plan limits (429), or temporary backend outage (5xx).","solutions":["Re-authenticate if the status is 401/403","Wait and retry if the status is 429 (rate limit) or 5xx","Check connectivity and any proxy settings","Update Atuin if the endpoint has changed"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"match fetch_usage(&client, &token).await {\n    Ok(snapshot) => snapshot,\n    Err(e) if e.to_string().contains(\"429\") => { tokio::time::sleep(backoff).await; retry().await? }\n    Err(e) if e.to_string().contains(\"401\") => { reauth().await?; retry().await? }\n    Err(e) => return Err(e),\n}","preventionTips":["Re-authenticate on 401 before retrying","Back off on 429 instead of hammering the endpoint","Cache usage snapshots to reduce request volume"],"tags":["http","api","network","ai"],"backgroundTag":"http-error-response","analyzedSha":"900007c48045867e7f44b0f9bc55ad9dbe99ee0d","analyzedAt":"2026-09-19T12:52:51.491Z","contentChangedAt":"2026-09-19T12:52:51.491Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}