siyuan-note/siyuan · error

invalid request

Error message

invalid request: %s

What it means

AIProviderRequest's request binding decodes the incoming JSON body into a Request value; when json.Decode fails, the raw decoder error is rewritten (renaming internal struct names back to legacy field names) and wrapped as 'invalid request: %s'. It means the request body sent to the AI API does not match the expected schema.

Solutions

  1. Inspect the wrapped message after 'invalid request: ' — it names the exact JSON decode problem
  2. Validate the payload against the Request schema before sending
  3. Send Content-Type: application/json with a syntactically valid JSON body
  4. Update the plugin/client to the current AI request format

Example fix

// before
fetch(url, {body: "prompt=hi"}) // form-encoded
// after
fetch(url, {
  headers: {"Content-Type": "application/json"},
  body: JSON.stringify({prompt: "hi"})
})
Defensive patterns

Strategy: try-catch

Validate before calling

try { JSON.parse(payload); } catch (e) { throw new Error("body is not valid JSON"); }

Try / catch

try {
  await fetchPost("/api/ai/...", payload);
} catch (e) {
  if (String(e).includes("invalid request:")) {
    console.error("Schema mismatch:", e.message); // details follow the prefix
  }
}

Prevention

When it happens

Trigger: POSTing a body to the AI input endpoint that is not valid JSON or does not unmarshal into the Request struct: wrong field types, unknown malformed payload, trailing garbage, or truncated JSON.

Common situations: Client sends form-encoded data instead of JSON; a field typed as string receives a number; plugin uses an outdated request shape; body got truncated by a proxy.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/1878cad43b0f8fa0. Report an issue: GitHub.

Appendix: source

Thrown at kernel/apicontract/ai_input.go:20

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"reflect"
	"strings"
)

func aiStructDecoder[Request, Data any](endpoint *Endpoint[Request, Data], legacyName string) {
	endpoint.decodeRequest = func(reader io.Reader) (value Request, err error) {
		err = json.NewDecoder(reader).Decode(&value)
		if err != nil {
			message := strings.ReplaceAll(err.Error(), reflect.TypeFor[Request]().Name()+".", legacyName+".")
			for _, name := range []string{"Reference", "EditorContext", "FrontendCapability", "ToolEffects"} {
				message = strings.ReplaceAll(message, "AI"+name+".", name+".")
			}
			err = fmt.Errorf("invalid request: %s", message)
		}
		return
	}
}

func (r AIProviderRequest) ProviderError() error { return r.providerError }

func aiLegacyString(fields fileTreeFields, key string, required, trim bool) (value string, err error) {
	value, err = legacyField[string](fields, key, "String", required)
	if err == nil && trim {
		value = strings.TrimSpace(value)
		if value == "" {
			err = fmt.Errorf("Field [%s] must not be empty", key)
		}
	}
	return
}

View on GitHub (pinned to 9f775e8a12)