{"record":{"id":"7ba02491697e31c2","repo":"atuinsh/atuin","slug":"usage-request-failed-status","errorCode":null,"errorMessage":"usage request failed ({status})","messagePattern":"usage request failed \\((.+?)\\)","errorType":"http","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/c0c717ab04c881764bcad4b3d169a507e2432643/crates/atuin-ai/src/usage.rs#L88-L124","documentation":"`fetch_usage` requests usage/quotas from the AI gateway and bails with the HTTP status when the response is not successful. Like the model-list fetch, it surfaces the raw status so the caller can tell auth failures from server errors.","triggerScenarios":"The usage endpoint returns non-success status: invalid/expired token (401), forbidden account (403), unknown account, gateway 5xx, or rate limit (429)","commonSituations":"Checking usage with a stale session after the token expired, during gateway incidents, or when the account has been suspended","solutions":["Check the status in the message; re-authenticate on 401/403","Refresh the hub session / re-login","Verify the AI gateway configuration and network connectivity","Retry later on 5xx/429 responses"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// validate session exists before fetching usage\nlet token = session.token().ok_or_else(|| eyre!(\"no active session\"))?;","typeGuard":null,"tryCatchPattern":"match fetch_usage().await {\n    Ok(u) => u,\n    Err(e) if e.to_string().contains(\"401\") => {\n        refresh_session().await?;\n        fetch_usage().await?\n    }\n    Err(e) if e.to_string().contains(\"429\") || e.to_string().contains(\"5\") => {\n        tokio::time::sleep(BACKOFF).await;\n        fetch_usage().await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Re-login when sessions expire","Apply exponential backoff for 429/5xx","Surface the status code from the error message to the user"],"tags":["http","api","network"],"backgroundTag":"http-error-response","analyzedSha":"c0c717ab04c881764bcad4b3d169a507e2432643","analyzedAt":"2026-09-12T07:40:01.341Z","contentChangedAt":"2026-09-12T07:40:01.341Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}