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.

One tag, auto-mount
<script src="https://retro.3m4.net/widget.js" data-project-key="retro_pub_…" defer></script>
Explicit element
<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-left
  • lang β€” 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.
Plain HTML form (no JavaScript)
<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/api

Authentication

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.

MethodPathScopeNotes
GET/projects?q=projects:read
POST/projectsprojects:write{name, key?, description?, url?} β†’ 201 Β· 402 limit_reached
GET/projects/:idprojects:read
PATCH/projects/:idprojects:writename, url, board_slug, board_enabled, types_enabled, …
DELETE/projects/:idprojects:deletecascades
POST/projects/:id/rotate-keyprojects:writeold public key stops immediately
GET/projects/:id/summaryfeedback:readcounts 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.

MethodPathScopeNotes
GET/projects/:id/feedback?q=&type=&status=&sentiment=&priority=&tag=&page=&sort=feedback:readcomma-separated lists for type/status
GET/projects/:id/feedback/:itemfeedback:read:item = UUID or #number
PATCH/projects/:id/feedback/:itemfeedback:write{status?, priority?, tags?, title?, type?, sentiment?, roadmap_item_id?}
POST/projects/:id/feedback/:item/notesfeedback:write{body}
POST/projects/:id/feedback/:item/promotefeedback:write + board:write→ roadmap item, idempotent
GET/projects/:id/feedback/:item/screenshotfeedback:readimage bytes
DELETE/projects/:id/feedback/:itemfeedback: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.

MethodPathScopeNotes
GET/projects/:id/roadmap?status=board:read
POST/projects/:id/roadmapboard:write{title, description?, status?, public?} Β· 402
PATCH/projects/:id/roadmap/:ridboard:writedone β‡’ completed_at; linked feedback follows
PUT/projects/:id/roadmap/reorderboard:write{status, ids}
DELETE/projects/:id/roadmap/:ridboard:deleteunlinks feedback
GET/projects/:id/changelog?include_drafts=trueboard:read
POST/projects/:id/changelogboard:write{title, body (markdown), version?, publish?}
PATCH/projects/:id/changelog/:cidboard:writepublished: true|false
DELETE/projects/:id/changelog/:cidboard: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.

MethodPathScopeNotes
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.

Local (stdio, npm)
claude mcp add retro --scope user --env RETRO_API_KEY=retro_live_… -- npx -y retro-feedback-mcp
Remote (Streamable HTTP)
claude 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.