router-for-me/CLIProxyAPI · error
kimi: device code request failed with status %d: %s
Error message
kimi: device code request failed with status %d: %s
What it means
Kimi's device authorization endpoint returned a non-200 HTTP status. The full response body is embedded in the error message, so the server's explanation (rate limit, invalid client, server error) is visible. The request itself is well-formed; failure comes from the server side or the request's client_id.
Source
Thrown at internal/auth/kimi/kimi.go:211
}
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("kimi: device code request failed: %w", err)
}
defer func() {
if errClose := resp.Body.Close(); errClose != nil {
log.Errorf("kimi device code: close body error: %v", errClose)
}
}()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("kimi: failed to read device code response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("kimi: device code request failed with status %d: %s", resp.StatusCode, string(bodyBytes))
}
var deviceCode DeviceCodeResponse
if err = json.Unmarshal(bodyBytes, &deviceCode); err != nil {
return nil, fmt.Errorf("kimi: failed to parse device code response: %w", err)
}
return &deviceCode, nil
}
// PollForToken polls the token endpoint until the user authorizes or the device code expires.
func (c *DeviceFlowClient) PollForToken(ctx context.Context, deviceCode *DeviceCodeResponse) (*KimiTokenData, error) {
if deviceCode == nil {
return nil, fmt.Errorf("kimi: device code is nil")
}
interval := time.Duration(deviceCode.Interval) * time.Second
if interval < defaultPollInterval {View on GitHub (pinned to 78f0c4079e)
Solutions
- Read the embedded body in the error message — it names the actual cause (e.g. 'rate_limited', 'invalid_client')
- If 429: wait the interval indicated (or 60s+) before requesting a new device code instead of retrying in a loop
- Update CLIProxyAPI to the latest release — a changed/rotated kimiClientID is fixed upstream in internal/auth/kimi/kimi.go
- If blocked (403) from a datacenter/VPN IP, run the login from a residential network or allowed egress IP
Defensive patterns
Strategy: try-catch
Type guard
func isDeviceCodeStatusErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "device code request failed with status")
} Try / catch
if err != nil {
if strings.Contains(err.Error(), "status 429") {
// back off before next login attempt
time.Sleep(time.Minute)
}
return err // body in message carries the server's reason
} Prevention
- Never loop StartDeviceFlow without backoff — 429 compounds
- Parse the status out of the message to branch retry vs abort
- Update CLIProxyAPI when Moonshot rotates the OAuth client ID
When it happens
Trigger: POST to /api/oauth/device_authorization with the hardcoded client_id 17e5f671-... returns 4xx/5xx: 429 rate limiting from too many login attempts, 4xx if Moonshot invalidates or restricts the client ID, 5xx during auth.kimi.com outages, or Cloudflare-style 403 blocks for datacenter IPs.
Common situations: Version drift where Kimi changes or deprecates its OAuth client ID (fix requires updating this repo), repeated login retries triggering rate limits, running the proxy from a cloud IP that Kimi's WAF blocks, auth.kimi.com regional outage.
Related errors
- kimi: failed to read device code response: %w
- kimi: failed to create token request: %w
- kimi: token request failed: %w
- kimi: failed to read token response: %w
- kimi: refresh failed with status %d: %s
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/2a3789730b644bfb.
Report an issue: GitHub.