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

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