router-for-me/CLIProxyAPI · error
kimi: failed to parse token response: %w
Error message
kimi: failed to parse token response: %w
What it means
The token endpoint returned a 200 response whose body is not valid JSON. Kimi returns 200 for both success and pending states, so any parse failure means the body is not the expected OAuth JSON — e.g. an HTML page or a truncated payload. The wrapped json error identifies the syntax problem.
Source
Thrown at internal/auth/kimi/kimi.go:310
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("kimi: failed to read token response: %w", err), false
}
// Parse response - Kimi returns 200 for both success and pending states
var oauthResp struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
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, &oauthResp); err != nil {
return nil, fmt.Errorf("kimi: failed to parse token response: %w", err), false
}
if oauthResp.Error != "" {
switch oauthResp.Error {
case "authorization_pending":
return nil, nil, true // Continue polling
case "slow_down":
return nil, nil, true // Continue polling (with increased interval handled by caller)
case "expired_token":
return nil, fmt.Errorf("kimi: device code expired"), false
case "access_denied":
return nil, fmt.Errorf("kimi: access denied by user"), false
default:
return nil, fmt.Errorf("kimi: OAuth error: %s - %s", oauthResp.Error, oauthResp.ErrorDescription), false
}
}
if oauthResp.AccessToken == "" {View on GitHub (pinned to 78f0c4079e)
Solutions
- Log bodyBytes before unmarshal to identify what came back (HTML challenge vs truncated JSON)
- Bypass or correctly configure proxies for auth.kimi.com and retry login
- Update CLIProxyAPI — if the response contract changed, the parser is fixed in newer releases
Defensive patterns
Strategy: validation
Validate before calling
// In wrappers/forks: sanity-check body looks like OAuth JSON before unmarshal
if !bytes.HasPrefix(bytes.TrimSpace(bodyBytes), []byte("{")) {
return nil, nil, fmt.Errorf("kimi: non-JSON token response: %.120s", bodyBytes)
} Try / catch
var synErr *json.SyntaxError
if errors.As(err, &synErr) {
// log body, check for WAF/proxy HTML, retry once
} Prevention
- Log raw token responses on parse failure
- Keep proxies off the auth host
- Update CLIProxyAPI when Kimi changes response schemas
When it happens
Trigger: WAF/Cloudflare HTML page served with 200 on /api/oauth/token, response truncated by proxy so braces are unbalanced, Moonshot schema change adding unexpected content-type or BOM-prefixed JSON.
Common situations: Intercepting proxies on the token host, regional CDN nodes serving challenge pages, upstream API changes after Kimi updates (fix upstream in this repo).
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- kimi: failed to parse device code response: %w
- kimi: failed to parse refresh response: %w
- kimi: failed to create device code request: %w
- kimi: device code request failed: %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/899e666f2c417b84.
Report an issue: GitHub.