sipeed/picoclaw · error

Invalid JSON

Error message

Invalid JSON

What it means

Returned by POST /api/models/test-inline when json.Unmarshal of the body into {provider, model, api_base, api_key, auth_method, model_index} fails. Unlike sibling handlers, this error message omits the %v detail, so you must validate the JSON client-side to find the cause. Expected shape: one JSON object; model_index must be a number when present.

Source

Thrown at web/backend/api/models.go:997

//
//	POST /api/models/test-inline
func (h *Handler) handleTestInlineModel(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(io.LimitReader(r.Body, 1<<20))
	if err != nil {
		http.Error(w, "Failed to read request body", http.StatusBadRequest)
		return
	}

	var req struct {
		Provider   string `json:"provider"`
		Model      string `json:"model"`
		APIBase    string `json:"api_base"`
		APIKey     string `json:"api_key"`
		AuthMethod string `json:"auth_method"`
		ModelIndex *int   `json:"model_index"`
	}
	if err := json.Unmarshal(body, &req); err != nil {
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}

	m := &config.ModelConfig{
		Provider:   strings.TrimSpace(req.Provider),
		Model:      strings.TrimSpace(req.Model),
		APIBase:    strings.TrimSpace(req.APIBase),
		AuthMethod: strings.TrimSpace(req.AuthMethod),
	}
	if req.APIKey != "" {
		m.SetAPIKey(req.APIKey)
	}

	// When api_key is empty and model_index is provided, fall back to stored credentials.
	// This lets the edit form test unsaved field changes while using the saved key.
	// Only reuse the stored key when the provider and effective API base match
	// the saved model, to prevent attaching a credential to a different endpoint.
	if req.APIKey == "" && req.ModelIndex != nil {

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Validate with JSON.parse on the client before sending — the server message has no detail to help you
  2. Send exactly: {provider, model, api_base, api_key, auth_method, model_index} with model_index as a number
  3. Ensure Content-Type: application/json and a single JSON.stringify call

Example fix

// before
body: rawJsonString // previously built via template literal

// after
body: JSON.stringify({
  provider: 'openai',
  model: 'gpt-4o',
  api_base: 'https://api.openai.com/v1',
  api_key: key,
  model_index: 0,
})
Defensive patterns

Strategy: validation

Validate before calling

function buildTestInlineBody(p: { provider: string; model: string; api_base?: string; api_key?: string; auth_method?: string; model_index?: number }): string {
  if (typeof p.provider !== 'string' || typeof p.model !== 'string') throw new Error('provider and model are required strings');
  if (p.model_index !== undefined && !Number.isInteger(p.model_index)) throw new Error('model_index must be an integer');
  return JSON.stringify(p);
}

Type guard

function isTestInlinePayload(v: unknown): v is { provider: string; model: string; api_base?: string; api_key?: string; auth_method?: string; model_index?: number } {
  if (typeof v !== 'object' || v === null) return false;
  const o = v as Record<string, unknown>;
  return typeof o.provider === 'string' && typeof o.model === 'string'
    && (o.model_index === undefined || typeof o.model_index === 'number');
}

Try / catch

try {
  const res = await fetch('/api/models/test-inline', {...});
  if (res.status === 400 && (await res.text()) === 'Invalid JSON') {
    /* server gives no detail — run JSON.parse(body) locally to locate the defect */
  }
} catch (e) { /* network */ }

Prevention

When it happens

Trigger: POST /api/models/test-inline with unquoted keys, an array body, {"model_index": "2"} (string vs *int), double-stringified JSON, or trailing garbage after the object.

Common situations: Reusing a fetch wrapper that JSON.stringify's an already-serialized string; sending FormData without conversion; template-built JSON with a trailing comma; field renamed on the client (model_name vs model).

Understand the failure class

Related errors


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