/api/v1/feedback capture structured per-message signal — thumb-up/down, edit, regenerate — for the continuous-learning loop. Rows are keyed to an OTel trace_id so the eval pipeline can join signals back onto the run that produced them. See the Feedback guide for the design contract and UI flow.
All endpoints require authentication. Rows are private to the authoring user — no cross-user reads, even within the same workspace.
Endpoints
Signals
Create, read, and clear per-message feedback rows scoped to the authoring user.Create / upsert feedback
(workspace_id, message_id, user_id, signal) tuple. A re-submit in the same workspace replaces reason, trace_id, and chat_id on the existing row rather than producing a duplicate. A forked workspace can retain its own feedback row for the same source message id.
Request body:
message_id must resolve to a real, visible message — not merely be well-formed. The handler looks it up in conversation_messages (the search mirror, migration v111) and derives workspace_id/chat_id from that row’s own session_id, then confirms the caller is a member of that workspace. A message_id that was never persisted there (nothing has been sent in that chat yet, or it predates the mirror) gets the same 404 as a message in a chat the caller can’t see — the two aren’t distinguishable from outside, closing a cross-tenant message-existence oracle (#1208). chat_id in the body is accepted but no longer authoritative.201 Created
Example:
List feedback
message_id or trace_id must be provided. Returns only rows authored by the caller — no cross-user visibility.
Query parameters:
The request has no body. Supplying both filters is rejected with
400.
Response: 200 OK
created_at DESC. Empty array when no matching rows visible to the caller.
Errors:
Delete feedback
(message_id, signal) tuple in one workspace. Other users’ rows and copies in other workspaces are untouched. Existing clients can omit workspace_id when exactly one visible row matches. If a fork leaves matching rows in two workspaces, the server returns 409; use the workspace_id from GET to choose which one to clear.
Query parameters:
Response:
204 No Content
Idempotent: returns 204 even when no row matched, so a client can fire DELETE on every toggle-off click without first checking existence.
Errors:
Schema
message_feedback table (added in migration v96; unique key scoped to a workspace by #2274):
trace_id to join feedback signals onto the routine run that produced them; see Online eval sampler.