MetaCubeX/mihomo · error · HttpRequestError
[{status}] {reason}
Error message
[{status}] {reason} What it means
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.
Source
Thrown at mihomo/client.py:95
async with session.get(url, params=params) as response:
match response.status:
case 200:
return await response.json(encoding="utf-8")
case 400:
try:
data = await response.json(encoding="utf-8")
except:
raise InvalidParams()
else:
if isinstance(data, dict) and (
detail := data.get("detail")
):
raise InvalidParams(detail)
raise InvalidParams()
case 404:
raise UserNotFound()
case _:
raise HttpRequestError(response.status, str(response.reason))
async def fetch_user(
self,
uid: int,
*,
replace_icon_name_with_url: bool = False,
) -> StarrailInfoParsed:
"""
Fetches user data from the API.
Args:
- uid (`int`): The user ID.
- replace_icon_name_with_url (`bool`): Whether to replace icon names with asset URLs.
Returns:
StarrailInfoParsed: The parsed user data from mihomo API.
"""View on GitHub (pinned to 008b91bfe8)
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.
Example fix
// before
data = await client.fetch_user(uid) # HttpRequestError [429] Too Many Requests kills the job
// after
import asyncio
for attempt in range(3):
try:
data = await client.fetch_user(uid)
break
except HttpRequestError as e:
if e.status != 429 or attempt == 2:
raise
await asyncio.sleep(2 ** attempt) Defensive patterns
Strategy: retry
Validate before calling
# Nothing to validate client-side — this is a server/network condition.
# Guard with a rate limiter before calling:
import asyncio
_lock = asyncio.Semaphore(1) # serialize + space out requests
async def safe_fetch(client, uid):
async with _lock:
return await client.fetch_user(uid) Type guard
def is_retryable_http_error(exc: HttpRequestError) -> bool:
return exc.status in {429, 502, 503, 504} Try / catch
from mihomo.errors import HttpRequestError
async def fetch_with_retry(client, uid, attempts=3):
for i in range(attempts):
try:
return await client.fetch_user(uid)
except HttpRequestError as e:
if e.status not in {429, 502, 503, 504} or i == attempts - 1:
raise
await asyncio.sleep((2 ** i) + random.random()) Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of MetaCubeX/mihomo@008b91bfe8 (2026-08-27).
Data as JSON: /api/errors/e3da956d1f91a035.
Report an issue: GitHub.