Skip to main content

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​

  • web/api/feedback.js, feedback-attachment.js, feedback-upload-session.js, feedback-agent-export.js
  • web/api/_feedback.js
  • trigger/feedback.ts