{"record":{"id":"cb365a6bec73467e","repo":"usestrix/strix","slug":"rate-limited","errorCode":"rate_limited","errorMessage":"rate_limited","messagePattern":"rate_limited","errorType":"error_code","errorClass":"RelayError","httpStatus":429,"severity":"warning","filePath":"strix/interface/viewer/auth.py","lineNumber":171,"sourceCode":"        logger.warning(\"relay request to %s failed: %s\", path, exc)\n        raise RelayError(\"unavailable\") from exc\n\n\ndef _parse_body(raw: bytes) -> dict[str, Any]:\n    try:\n        data = json.loads(raw or b\"{}\")\n    except json.JSONDecodeError:\n        return {}\n    return data if isinstance(data, dict) else {}\n\n\ndef otp_start(email: str) -> None:\n    \"\"\"Ask the relay to email a verification code. Raises RelayError on failure.\"\"\"\n    status, data = _post_json(\"/api/oss/otp/start\", {\"email\": email}, timeout=_OTP_TIMEOUT)\n    if status == 200:\n        return\n    if status == 429:\n        raise RelayError(\"rate_limited\")\n    if status == 400:\n        # The relay uses 400 both for a malformed address and, separately, to\n        # reject a free/personal email domain (it wants a work email).\n        if data.get(\"error\") == \"work_email_required\":\n            raise RelayError(\"work_email_required\")\n        raise RelayError(\"invalid_email\")\n    raise RelayError(\"unavailable\")\n\n\ndef otp_verify(email: str, code: str) -> dict[str, Any]:\n    \"\"\"Verify a code. Returns ``{token, email, expires_at}`` or raises RelayError.\"\"\"\n    status, data = _post_json(\n        \"/api/oss/otp/verify\",\n        {\"email\": email, \"code\": code},\n        timeout=_OTP_TIMEOUT,\n    )\n    if status == 200 and isinstance(data.get(\"token\"), str):\n        # A token with no usable expiry cannot unlock history locally (the gate","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/interface/viewer/auth.py#L153-L189","documentation":"RelayError('rate_limited') raised by otp_start() when the relay responds 429 to POST /api/oss/otp/start. The relay throttles how often verification codes can be requested per email/IP, so repeated code requests within a short window are rejected.","triggerScenarios":"Calling otp_start(email) more than the relay's allowed frequency — e.g. clicking 'resend code' several times, or a retry loop that re-requests a code on every failure. The 429 maps directly to RelayError('rate_limited').","commonSituations":"Impatient users re-clicking the email-verification button; scripts that call otp_start on each attempt instead of waiting; multiple people behind one NAT/IP triggering shared rate limits; automated tests hammering the endpoint.","solutions":["Wait for the relay's cooldown (typically 30-60s) before requesting a new code","Check the inbox (and spam folder) for an already-sent code before resending","In scripts, cache the 'code requested' state and only call otp_verify until the code expires","Cap retries with exponential backoff instead of immediate re-request"],"exampleFix":"# before\nfor attempt in range(5):\n    otp_start(email)  # may 429 immediately\n# after\notp_start(email)\nfor attempt in range(5):\n    try:\n        otp_verify(email, input('code: ')); break\n    except RelayError as e:\n        if e.code != 'invalid_code': raise\n        time.sleep(2 ** attempt)","handlingStrategy":"retry","validationCode":"# Track last request time; only call otp_start after the cooldown.\nimport time\n_last_otp_request = 0.0\nOTP_COOLDOWN = 60\n\ndef safe_otp_start(email):\n    global _last_otp_request\n    wait = OTP_COOLDOWN - (time.monotonic() - _last_otp_request)\n    if wait > 0:\n        raise RuntimeError(f\"wait {wait:.0f}s before requesting a new code\")\n    _last_otp_request = time.monotonic()\n    return otp_start(email)","typeGuard":null,"tryCatchPattern":"try:\n    otp_start(email)\nexcept RelayError as e:\n    if e.code == \"rate_limited\":\n        show(\"Code requested too often. Wait a minute, check your inbox, then retry.\")\n    else:\n        raise","preventionTips":["Request a code once, then poll the inbox — never re-request on verify failure","Cache 'code requested' state in scripts instead of blind retries","Disable resend buttons for the cooldown window in UIs"],"tags":["strix","rate-limit","relay","viewer","auth"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}