{"record":{"id":"e3da956d1f91a035","repo":"MetaCubeX/mihomo","slug":"status-reason","errorCode":null,"errorMessage":"[{status}] {reason}","messagePattern":"\\[\\{status\\}\\] \\{reason\\}","errorType":"http","errorClass":"HttpRequestError","httpStatus":null,"severity":"error","filePath":"mihomo/client.py","lineNumber":95,"sourceCode":"            async with session.get(url, params=params) as response:\n                match response.status:\n                    case 200:\n                        return await response.json(encoding=\"utf-8\")\n                    case 400:\n                        try:\n                            data = await response.json(encoding=\"utf-8\")\n                        except:\n                            raise InvalidParams()\n                        else:\n                            if isinstance(data, dict) and (\n                                detail := data.get(\"detail\")\n                            ):\n                                raise InvalidParams(detail)\n                            raise InvalidParams()\n                    case 404:\n                        raise UserNotFound()\n                    case _:\n                        raise HttpRequestError(response.status, str(response.reason))\n\n    async def fetch_user(\n        self,\n        uid: int,\n        *,\n        replace_icon_name_with_url: bool = False,\n    ) -> StarrailInfoParsed:\n        \"\"\"\n        Fetches user data from the API.\n\n        Args:\n            - uid (`int`): The user ID.\n            - replace_icon_name_with_url (`bool`): Whether to replace icon names with asset URLs.\n\n        Returns:\n            StarrailInfoParsed: The parsed user data from mihomo API.\n\n        \"\"\"","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/MetaCubeX/mihomo/blob/008b91bfe8c0e2daca0ab69061efd9ea1ad71bd2/mihomo/client.py#L77-L113","documentation":"HttpRequestError(status, reason) is raised at mihomo/client.py:95 for any status code other than 200/400/404, formatting the message as '[{status}] {reason}' (mihomo/errors.py:26). It is the catch-all transport/server-error channel of MihomoAPI.request: rate limiting, upstream outages, gateway errors, and anything unexpected all land here. The status attribute (exc.status) lets you branch on the exact code.","triggerScenarios":"HTTP 429 from api.mihomo.me when you exceed the rate limit; 5xx (502/503/504) during upstream maintenance or Cloudflare incidents; 403 when the source IP is blocked; any other non-2xx code not specifically mapped in the match statement.","commonSituations":"Bots polling fetch_user in a tight loop without backoff and hitting 429; running scrapers from datacenter IPs that Cloudflare challenges with 403/503; the free mihomo API being down or under load (check its status/Discord); aiohttp behind corporate proxies returning unusual status codes.","solutions":["Check exc.status: 429 → slow down and add exponential backoff; 5xx → the upstream is down, retry later; 403 → network/IP issue, not your code.","Add a retry-with-jitter wrapper around fetch_user for transient 502/503/504, with a cap of 2–3 attempts.","Respect a minimum interval between requests (e.g. ≥1s per UID) to stay under the rate limit.","If sustained, verify https://api.mihomo.me health in a browser/curl and look for announced maintenance."],"exampleFix":"// before\ndata = await client.fetch_user(uid)  # HttpRequestError [429] Too Many Requests kills the job\n\n// after\nimport asyncio\nfor attempt in range(3):\n    try:\n        data = await client.fetch_user(uid)\n        break\n    except HttpRequestError as e:\n        if e.status != 429 or attempt == 2:\n            raise\n        await asyncio.sleep(2 ** attempt)","handlingStrategy":"retry","validationCode":"# Nothing to validate client-side — this is a server/network condition.\n# Guard with a rate limiter before calling:\nimport asyncio\n_lock = asyncio.Semaphore(1)  # serialize + space out requests\nasync def safe_fetch(client, uid):\n    async with _lock:\n        return await client.fetch_user(uid)","typeGuard":"def is_retryable_http_error(exc: HttpRequestError) -> bool:\n    return exc.status in {429, 502, 503, 504}","tryCatchPattern":"from mihomo.errors import HttpRequestError\n\nasync def fetch_with_retry(client, uid, attempts=3):\n    for i in range(attempts):\n        try:\n            return await client.fetch_user(uid)\n        except HttpRequestError as e:\n            if e.status not in {429, 502, 503, 504} or i == attempts - 1:\n                raise\n            await asyncio.sleep((2 ** i) + random.random())","preventionTips":["Rate-limit your calls (≥1s between requests per UID) to avoid 429.","Use exponential backoff with jitter for 429/5xx; never retry in a tight loop.","Read exc.status to branch: 403/5xx often mean IP blocking or outage, not a code bug.","Monitor api.mihomo.me status pages/Discord before assuming your code broke."],"tags":["python","mihomo","aiohttp","http-status","rate-limit","star-rail"],"backgroundTag":"http-error-response-status","analyzedSha":"008b91bfe8c0e2daca0ab69061efd9ea1ad71bd2","analyzedAt":"2026-08-27T19:16:11.578Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}