Feedback API
Five routes cover the in-app feedback widget end to end, all identity and org scope from a verified Supabase JWT.
/api/feedback — canonical intake, timeline, and triage
Handles creation (normalizeCreateInput), the reporter's own timeline (canReporterReply), and
lead/admin triage (normalizeStatus, normalizePriority). Feedback kinds, priorities, and
statuses are fixed enums (FEEDBACK_KINDS, FEEDBACK_PRIORITIES, FEEDBACK_STATUSES) shared with
the widget's own copy, so the API and the UI can never drift on what a valid value even is.
mutationGuard enforces who can change what — a reporter can reply to their own report; only
lead/admin can change status or priority.
/api/feedback-attachment — screenshots
Completes or verifies a direct screenshot upload, or mints a short-lived lead/admin download
URL. Rejecting an attachment (rejectAttachment) deletes the storage object immediately and sets
retention_expires_at to now — a rejected screenshot doesn't wait for the nightly retention sweep
to actually leave storage. Download responses set x-content-type-options: nosniff and a
restrictive content-security-policy — a screenshot is untrusted user-supplied image content, and
these headers stop a browser from executing it as anything other than an image.
/api/feedback-upload-session — pre-upload allocation
Allocates the attachment row and a signed upload token the browser uploads directly to, the same
two-step pattern /api/doc-upload-session uses for documents.
/api/feedback-agent-export — the agent-facing view
A separate, narrower export shape for feeding feedback into agent workflows (e.g. surfacing a recurring complaint as context), distinct from the reporter/lead-facing shapes the other routes return.
Feature flags govern rollout, independently
Four separate flags gate different slices of this surface, so any one can be turned off without
disabling the others: SABLE_FEEDBACK_INTAKE_ENABLED (creating reports at all),
SABLE_FEEDBACK_WIDGET_ENABLED (showing the widget on authenticated pages),
SABLE_FEEDBACK_SCREENSHOTS_ENABLED (screenshot capture/upload specifically), and
SABLE_FEEDBACK_ANSWER_REPORTING_ENABLED (reporting a specific chat answer, vs. general
feedback). All default off in production for staged rollout.
Rate limiting and retention
SABLE_FEEDBACK_RATE_LIMIT caps submissions per authenticated person per hour (default 20),
enforced in the database, not just in the route handler. Attachments past their
retention_expires_at are deleted daily by sable.feedback.retention — see
Documents and Cleanup Tasks.
Where the code lives
sable-agents-demo/web/api/feedback.js,feedback-attachment.js,feedback-upload-session.js,feedback-agent-export.jssable-agents-demo/web/api/_feedback.jssable-agents-demo/trigger/feedback.ts