Wei-Shaw/sub2api · error

invalid_request_error

invalid_request_error

Error message

request body must be valid JSON

What it means

Thrown while decoding the LiveCall request body as JSON when json.Decoder.Decode fails (code=invalid_request_error). Covers syntax errors (trailing commas, truncation), wrong content type with raw body, and completely empty bodies on non-multipart requests. The second decode (single-object check) has its own error.

Source

Thrown at backend/internal/handler/openai_live.go:126

	c.Header("Location", liveSidebandLocation(c.FullPath(), created.CallID))
	c.Data(http.StatusOK, "application/sdp", created.SDP)
}

func parseLiveCallRequest(c *gin.Context) (*service.LiveCallRequest, error) {
	contentType := strings.ToLower(c.GetHeader("Content-Type"))
	if strings.HasPrefix(contentType, "multipart/form-data") {
		sdp := c.PostForm("sdp")
		session := json.RawMessage(c.PostForm("session"))
		request := &service.LiveCallRequest{SDP: sdp, Session: session}
		if err := service.ValidateLiveCallRequest(request); err != nil {
			return nil, err
		}
		return request, nil
	}
	var request service.LiveCallRequest
	decoder := json.NewDecoder(c.Request.Body)
	if err := decoder.Decode(&request); err != nil {
		return nil, errors.New("request body must be valid JSON")
	}
	if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
		return nil, errors.New("request body must contain one JSON object")
	}
	if err := service.ValidateLiveCallRequest(&request); err != nil {
		return nil, err
	}
	return &request, nil
}

func liveSidebandLocation(fullPath, callID string) string {
	prefix := "/v1/live/"
	if strings.HasPrefix(fullPath, "/backend-api/codex/") {
		prefix = "/backend-api/codex/"
	}
	return prefix + url.PathEscape(callID)
}

View on GitHub (pinned to 073e92d171)

Solutions

  1. Validate the body is syntactically valid JSON before sending (jq, JSON.stringify)
  2. Ensure Content-Type is application/json and the full body is transmitted (check for truncation on large SDP payloads)
  3. Build the payload with a JSON serializer rather than string concatenation

Example fix

// before
fetch(url, { body: `{sdp: ${sdp}}` }) // invalid JSON
// after
fetch(url, { headers: {'Content-Type':'application/json'}, body: JSON.stringify({ sdp }) })
Defensive patterns

Strategy: validation

Validate before calling

// TS: serialize + parse round-trip before sending
const bodyText = JSON.stringify(payload);
JSON.parse(bodyText); // throws locally if the serializer produced bad JSON
await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: bodyText });

Type guard

function isLiveCallRequest(v: unknown): v is { sdp?: string; session?: unknown } {
  return typeof v === 'object' && v !== null && !Array.isArray(v);
}

Prevention

When it happens

Trigger: POST to /v1/live/... with a malformed JSON body: truncated payload, single quotes, comments, empty body, or body sent as form fields without multipart content type.

Common situations: Clients string-interpolating JSON; proxies truncating large SDP offers; sending x-www-form-urlencoded fields where raw JSON is expected; debug tools posting invalid JSON.

Related errors


AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15). Data as JSON: /api/errors/533edd29e8a3968b. Report an issue: GitHub.