Docs
How to put retro in your app, send feedback from anywhere, and manage it through the REST API or an AI agent. The full contract β every endpoint, object and error β lives in the repository.
Read the full contract (CONTRACT.md) βWidget
A zero-dependency web component served from this host. Add it with one script tag (it mounts itself) or place the element wherever you want. Works in Qwik, Astro, React, jQuery pages and Phaser games alike.
<script src="https://retro.3m4.net/widget.js" data-project-key="retro_pub_β¦" defer></script><script src="https://retro.3m4.net/widget.js" defer></script>
<retro-widget project-key="retro_pub_β¦" position="bottom-right" lang="auto" theme="auto"></retro-widget>Attributes
project-keyβ the project's public key (required).positionβbottom-right|bottom-leftlangβ auto (localStorage 3m4.lang β <html lang> β browser) or any of the 34 supported locales.themeβ auto (localStorage 3m4.theme β system), light or dark.accentβ hex colour; overrides the accent set in the project settings.hide-launcherβ no floating button; open the panel yourself with retro.open().
JavaScript API
window.retro exposes open({type?, message?}), close(), identify({id?, email?}), setMeta(object), setLang(), setTheme() and on(event, cb) with the events open, close, submitted and error.
// Headless: your own button, your own metadata
retro.identify({ id: user.id, email: user.email });
retro.setMeta({ plan: "pro", build: "1.4.0" });
retro.on("submitted", ({ number }) => toast(`Thanks! Ticket #${number}`));
document.querySelector("#report-bug").onclick = () => retro.open({ type: "bug" });Screenshots: on desktop the widget grabs one frame through the browser's screen-capture dialog; everywhere it also accepts a file or a pasted image. Images are downscaled to 1600 px and compressed before upload. The panel hides itself during capture.
Ingest API
POST /api/ingest/feedback with the project's public key in the X-Project-Key header (or a project_key field). JSON and form-encoded bodies are both accepted, CORS is open, and the response carries the item's number so you can show it to the user.
curl -X POST https://retro.3m4.net/api/ingest/feedback \
-H "X-Project-Key: retro_pub_β¦" -H "Content-Type: application/json" \
-d '{
"type": "bug",
"message": "The export button does nothing on Safari 17",
"email": "ana@example.com",
"page_url": "https://myapp.example.com/editor",
"app_version": "1.4.0",
"metadata": { "browser": "Safari 17" }
}'- type (required): bug, feedback or feature β must be enabled on the project.
- message (required, β€ 5000 chars); title optional, defaults to the first line.
- sentiment (positive / negative / neutral) for type=feedback; email, reporter_id, page_url, app_version, locale and a metadata object (β€ 8 KB) are optional.
- screenshot: a data URL (PNG/JPEG/WebP, β€ 1.5 MB). Dropped, not rejected, past the storage cap.
- website: honeypot β leave it empty. Bots that fill it get a fake 201 and nothing is stored.
<form method="post" action="https://retro.3m4.net/api/ingest/feedback">
<input type="hidden" name="project_key" value="retro_pub_β¦">
<input type="hidden" name="type" value="feedback">
<textarea name="message" required></textarea>
<input type="email" name="email" placeholder="Email (optional)">
<input type="text" name="website" style="display:none" tabindex="-1" autocomplete="off">
<button>Send</button>
</form>Base URL
https://retro.3m4.net/apiAuthentication
Three kinds of credentials, each for one job:
- Dashboard sessions: JWT bearer tokens issued by /auth/login. Hold every scope.
- Management API: API keys with a role (viewer / editor / admin). Header X-API-Key: retro_live_β¦ (or Authorization: Bearer).
- Ingest API and widget: project public keys. Header X-Project-Key: retro_pub_β¦. They identify one project and can only submit feedback.
Projects
A project is one app. It owns a public key, the board settings (slug, enabled, votes, submissions), the enabled feedback types and the widget options.
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /projects?q= | projects:read | |
| POST | /projects | projects:write | {name, key?, description?, url?} β 201 Β· 402 limit_reached |
| GET | /projects/:id | projects:read | |
| PATCH | /projects/:id | projects:write | name, url, board_slug, board_enabled, types_enabled, β¦ |
| DELETE | /projects/:id | projects:delete | cascades |
| POST | /projects/:id/rotate-key | projects:write | old public key stops immediately |
| GET | /projects/:id/summary | feedback:read | counts by status / type / sentiment |
curl -X POST https://retro.3m4.net/api/projects \
-H "X-API-Key: retro_live_β¦" -H "Content-Type: application/json" \
-d '{"name": "My app", "url": "https://myapp.example.com"}'Feedback
Items have a per-project number, a type, an optional sentiment, a status (new β triaged β planned β in_progress β done | declined), a priority, tags, private notes and at most one screenshot. Promoting an item creates a roadmap item and links them.
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /projects/:id/feedback?q=&type=&status=&sentiment=&priority=&tag=&page=&sort= | feedback:read | comma-separated lists for type/status |
| GET | /projects/:id/feedback/:item | feedback:read | :item = UUID or #number |
| PATCH | /projects/:id/feedback/:item | feedback:write | {status?, priority?, tags?, title?, type?, sentiment?, roadmap_item_id?} |
| POST | /projects/:id/feedback/:item/notes | feedback:write | {body} |
| POST | /projects/:id/feedback/:item/promote | feedback:write + board:write | β roadmap item, idempotent |
| GET | /projects/:id/feedback/:item/screenshot | feedback:read | image bytes |
| DELETE | /projects/:id/feedback/:item | feedback:delete |
curl -X PATCH https://retro.3m4.net/api/projects/$PROJECT/feedback/42 \
-H "X-API-Key: retro_live_β¦" -H "Content-Type: application/json" \
-d '{"status": "triaged", "priority": "high", "tags": ["export", "safari"]}'Roadmap & changelog
Roadmap items live in three columns and can be reordered; public ones appear on the board and collect votes. Changelog entries are Markdown with an optional version; drafts stay private until published.
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /projects/:id/roadmap?status= | board:read | |
| POST | /projects/:id/roadmap | board:write | {title, description?, status?, public?} Β· 402 |
| PATCH | /projects/:id/roadmap/:rid | board:write | done β completed_at; linked feedback follows |
| PUT | /projects/:id/roadmap/reorder | board:write | {status, ids} |
| DELETE | /projects/:id/roadmap/:rid | board:delete | unlinks feedback |
| GET | /projects/:id/changelog?include_drafts=true | board:read | |
| POST | /projects/:id/changelog | board:write | {title, body (markdown), version?, publish?} |
| PATCH | /projects/:id/changelog/:cid | board:write | published: true|false |
| DELETE | /projects/:id/changelog/:cid | board:delete |
Public board endpoints
No key needed. They answer only while the project's board is enabled. Votes are de-duplicated per voter_id (a UUID the browser keeps) and per hashed IP per day.
| Method | Path | Scope | Notes |
|---|---|---|---|
| GET | /boards/:slug | β | roadmap (public items) + latest changelog |
| GET | /boards/:slug/changelog?page= | β | |
| POST | /boards/:slug/items/:rid/vote | β | {voter_id} Β· idempotent |
| DELETE | /boards/:slug/items/:rid/vote | β | {voter_id} |
| POST | /boards/:slug/feedback | β | same body as ingest, type defaults to feature |
Errors & limits
Errors are JSON: { "error": { "code", "message" } }. Creating past a plan limit answers 402 limit_reached with error.which = projects | api_keys | roadmap_items. Feedback over the monthly budget is still accepted.
Privacy: retro never stores raw IP addresses β only a salted daily hash used for rate limits and vote de-duplication. User agents are truncated and metadata is capped at 8 KB.
MCP (AI agents)
The MCP server exposes the management API as tools, filtered by the role of the key you connect with. Destructive tools ask for confirmation. A triage_inbox prompt walks a model through new items.
claude mcp add retro --scope user --env RETRO_API_KEY=retro_live_β¦ -- npx -y retro-feedback-mcpclaude mcp add --transport http retro https://retro.3m4.net/mcp --header "Authorization: Bearer retro_live_β¦"Tools: list_projects, get_project, create_project, update_project, rotate_project_key, delete_project, list_feedback, get_feedback, feedback_summary, list_tags, update_feedback, add_note, promote_to_roadmap, delete_feedback, list_roadmap, create_roadmap_item, update_roadmap_item, delete_roadmap_item, list_changelog, create_changelog_entry, update_changelog_entry, delete_changelog_entry, get_usage, get_audit_log.