Skip to main content
Endpoints under /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

Idempotent at the (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.
Response: 201 Created
Errors: Example:

List feedback

Exactly one of 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
Ordered by created_at DESC. Empty array when no matching rows visible to the caller. Errors:

Delete feedback

Removes the caller’s row for the given (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):
The eval pipeline reads from this table by trace_id to join feedback signals onto the routine run that produced them; see Online eval sampler.