router-for-me/CLIProxyAPI · error
kimi: refresh failed with status %d: %s
Error message
kimi: refresh failed with status %d: %s
What it means
The refresh endpoint returned a non-200 status other than 401/403, and the full body is embedded in the error. Typical statuses are 429 (rate limiting from too-frequent refreshes) or 5xx (auth.kimi.com errors). The credential may still be valid — this is usually a transient server-side condition.
Source
Thrown at internal/auth/kimi/kimi.go:405
return nil, fmt.Errorf("kimi: refresh request failed: %w", err)
}
defer func() {
if errClose := resp.Body.Close(); errClose != nil {
log.Errorf("kimi refresh token: close body error: %v", errClose)
}
}()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("kimi: failed to read refresh response: %w", err)
}
if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
return nil, fmt.Errorf("kimi: refresh token rejected (status %d)", resp.StatusCode)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("kimi: refresh failed with status %d: %s", resp.StatusCode, string(bodyBytes))
}
var tokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn float64 `json:"expires_in"`
Scope string `json:"scope"`
}
if err = json.Unmarshal(bodyBytes, &tokenResp); err != nil {
return nil, fmt.Errorf("kimi: failed to parse refresh response: %w", err)
}
if tokenResp.AccessToken == "" {
return nil, fmt.Errorf("kimi: empty access token in refresh response")
}
View on GitHub (pinned to 78f0c4079e)
Solutions
- Read the embedded status and body: 429 means back off (respect Retry-After, or wait 30-60s) then retry refresh — the token is still good
- 5xx: retry with backoff; check Moonshot status pages for auth.kimi.com incidents
- If a proxy returns 4xx/5xx for the POST, bypass it for auth.kimi.com
Defensive patterns
Strategy: retry
Type guard
func isKimiRefreshStatusErr(err error) (status int, ok bool) {
m := regexp.MustCompile(`refresh failed with status (\d+)`).FindStringSubmatch(err.Error())
if len(m) == 2 { s, _ := strconv.Atoi(m[1]); return s, true }
return 0, false
} Try / catch
if status, ok := isKimiRefreshStatusErr(err); ok {
if status == 429 || status >= 500 {
time.Sleep(30 * time.Second) // credential still valid; retry later
return c.RefreshToken(ctx, refreshToken)
}
return err
} Prevention
- Jitter refresh schedules so fleets don't align on 429
- Respect Retry-After on 429
- Only 401/403 are terminal; everything else is retryable
When it happens
Trigger: Many tokens expiring simultaneously causing refresh storms that trip 429; auth.kimi.com 500/502/503 during incidents; oversized/blocked requests through a proxy returning 400/502.
Common situations: Fleets of proxy instances refreshing at aligned times, Moonshot auth outages, proxy-induced 502s for POST bodies.
Related errors
- kimi: device code request failed with status %d: %s
- kimi: failed to create refresh request: %w
- kimi: refresh request failed: %w
- kimi: failed to read refresh response: %w
- kimi: failed to read device code response: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/dd53ff2fe4a737f0.
Report an issue: GitHub.