sipeed/picoclaw · error

API error (status %d): %s

Error message

API error (status %d): %s

What it means

Returned by MimoTTSProvider.Synthesize when the MiMo chat/completions endpoint answers a non-200 status; the status code and raw body are embedded in the message. This is the provider rejecting the request: 401 for a bad Api-Key header (the code uses 'Api-Key', not Bearer), 400 for an unknown model or voice, 429 for quota/rate limits, 5xx for provider outages.

Source

Thrown at pkg/audio/tts/mimo_tts.go:134

		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Api-Key", t.apiKey)

	resp, err := t.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("failed to read response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
	}

	var payload struct {
		Choices []struct {
			Message struct {
				Audio struct {
					Data string `json:"data"`
				} `json:"audio"`
			} `json:"message"`
		} `json:"choices"`
	}

	err = json.Unmarshal(body, &payload)
	if err != nil {
		return nil, fmt.Errorf("failed to decode response: %w", err)
	}

	if len(payload.Choices) == 0 || payload.Choices[0].Message.Audio.Data == "" {

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Read the embedded status and body: 401 → fix the api_key, 400 → fix model/voice, 429 → back off, 5xx → wait and retry
  2. Confirm the model config entry has api_key set and the model is mimo-v2-tts (or another TTS-capable name)
  3. Retry 429/5xx with exponential backoff and jitter
  4. If the body names an invalid parameter, simplify the request parameters

Example fix

// before
p := tts.NewMimoTTSProvider(``, ``, ``, ``) // empty key -> 401 later
// after
p := tts.NewMimoTTSProvider(os.Getenv(`MIMO_API_KEY`), ``, `mimo-v2-tts`, ``)
Defensive patterns

Strategy: try-catch

Validate before calling

if strings.TrimSpace(apiKey) == `` {
    return errors.New(`mimo tts requires an api_key on the model config entry`)
}
if model != `` && !strings.Contains(strings.ToLower(model), `tts`) {
    return errors.New(`mimo tts expects a TTS model such as mimo-v2-tts`)
}

Type guard

func apiStatus(err error) (int, bool) {
    s := err.Error()
    i := strings.Index(s, `API error (status `)
    if i < 0 {
        return 0, false
    }
    rest := s[i+len(`API error (status `):]
    j := strings.IndexByte(rest, ')')
    if j < 0 {
        return 0, false
    }
    n, cerr := strconv.Atoi(rest[:j])
    return n, cerr == nil
}

Try / catch

if code, ok := apiStatus(err); ok {
    switch {
    case code == 401:
        // fix the api_key; the header used is Api-Key, not Bearer
    case code == 429:
        // rate limited: exponential backoff, then retry
    case code >= 500:
        // provider incident: retry later
    default:
        // inspect the embedded body for invalid model/voice parameters
    }
}

Prevention

When it happens

Trigger: Empty or wrong apiKey passed to NewMimoTTSProvider; model name set to a non-TTS model; voice or format values the endpoint rejects (defaults: model mimo-v2-tts, voice default_zh, format mp3); exhausted quota returning 429.

Common situations: api_key missing from the model config so APIKey() returns ''; using text model names like 'mimo-v2' instead of 'mimo-v2-tts'; free-tier rate limits during bulk synthesis.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/fa708cb839d76ad9. Report an issue: GitHub.