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

  1. Log bodyBytes before unmarshal to identify what came back (HTML challenge vs truncated JSON)
  2. Bypass or correctly configure proxies for auth.kimi.com and retry login
  3. 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

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

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/899e666f2c417b84. Report an issue: GitHub.