plandex-ai/plandex · error

Invalid request body:

Error message

Invalid request body: 

What it means

UpsertCustomModelsHandler decodes the request body into shared.ModelsInput with json.NewDecoder(r.Body).Decode. Any malformed body — invalid JSON, wrong types (e.g. string where an object/array is expected), trailing garbage, or an empty body — produces 400 'Invalid request body: <err>'. Unlike the invites marshalling errors, this one is caused by the client's payload.

Source

Thrown at app/server/handlers/models.go:34

const CustomModelsMinClientVersion = "2.2.0"

func UpsertCustomModelsHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Received request for CreateCustomModelHandler")

	auth := Authenticate(w, r, true)
	if auth == nil {
		return
	}

	if !requireMinClientVersion(w, r, CustomModelsMinClientVersion) {
		return
	}

	var modelsInput shared.ModelsInput
	if err := json.NewDecoder(r.Body).Decode(&modelsInput); err != nil {
		log.Printf("Error decoding request body: %v\n", err)
		http.Error(w, "Invalid request body: "+err.Error(), http.StatusBadRequest)
		return
	}

	if len(modelsInput.CustomProviders) > 0 {
		if os.Getenv("IS_CLOUD") != "" {
			http.Error(w, "Custom model providers are not supported on Plandex Cloud", http.StatusBadRequest)
			return
		}
	}

	if len(modelsInput.CustomModels) > 0 {
		if os.Getenv("IS_CLOUD") != "" {
			apiOrg, err := getApiOrg(auth.OrgId)
			if err != nil {
				log.Printf("Error fetching org: %v\n", err)
				http.Error(w, "Failed to create custom model: "+err.Error(), http.StatusInternalServerError)
				return
			}

View on GitHub (pinned to e2d772072e)

Solutions

  1. Read the json error in the message/log (e.g. 'unexpected end of JSON input', 'cannot unmarshal string into Go value of type ...') to pinpoint the malformation
  2. Send a valid JSON body matching shared.ModelsInput (customModels and customProviders arrays of objects)
  3. Ensure the client version is >= 2.2.0 (CustomModelsMinClientVersion) and Content-Type is application/json

Example fix

// before
curl -X POST $BASE/custom-models -d 'customModels=my-model'
// after
curl -X POST $BASE/custom-models -H 'Content-Type: application/json' \
  -d '{"customModels":[{"provider":"my-provider","model":"my-model"}]}'
Defensive patterns

Strategy: validation

Validate before calling

const body = { customModels: [{ provider: 'p', model: 'm' }], customProviders: [] }
const payload = JSON.stringify(body)
JSON.parse(payload) // verify it's valid JSON before sending
if (!Array.isArray(body.customModels)) throw new TypeError('customModels must be an array')
// also ensure client version >= 2.2.0

Type guard

function isModelsInput(v) {
	return v != null && typeof v === 'object' &&
		Array.isArray(v.customModels) && Array.isArray(v.customProviders)
}

Try / catch

try {
	await client.upsertCustomModels(body)
} catch (e) {
	if (e.status === 400 && String(e.message).includes('Invalid request body')) {
		console.error('Payload is not valid JSON for ModelsInput:', e.message)
	}
	throw e
}

Prevention

When it happens

Trigger: POST to the custom models upsert endpoint with: non-JSON body, truncated JSON, wrong field types (e.g. customModels as a string instead of array), Content-Type/body mismatch, or an empty body.

Common situations: Older CLI (<2.2.0) sending a legacy payload shape; hand-built curl missing quotes or with a stale JSON template; proxy stripping the body; client sending form-encoded data instead of JSON.

Related errors


AI-assisted analysis of plandex-ai/plandex@e2d772072e (2026-09-05). Data as JSON: /api/errors/e5c4043168a6463f. Report an issue: GitHub.