Docs
Everything public about a project lives under one slug. Replace your-project with yours.
Every widget on this page is live. This page loads the same embed.js you would, pointed at our demo project. Click things.
Your public hub
Every project gets a hub page with a tab per enabled module. Turn a module off in Settings and its tab disappears.
- https://feedfa.st/p/your-project
- Changelog: the hub's default tab
- https://feedfa.st/p/your-project/feedback
- Feedback board
- https://feedfa.st/p/your-project/status
- Status page
- https://feedfa.st/p/your-project/changelog/entry-slug
- A single changelog entry
Install the script
One script tag, then place widgets wherever you want them. Every widget renders inside a shadow root, so your CSS and ours can never collide.
<script src="https://feedfa.st/embed.js" data-project="your-project" defer></script>- data-project
- Required. Your project slug.
- data-theme
- auto (default) · light · dark
- data-accent
- Override the accent colour, e.g. #4F46E5
- data-badge
- false to turn the floating badge off
- data-position
- bottom-right (default) · bottom-left
- data-user-id / -email / -name
- Identify the signed-in user: see below
Feedback button
Put data-feedfast="feedback" on any element: a button, a link, a menu item. It opens a modal that posts straight to your board.
If the element already has content, we leave your markup and styling completely alone and only attach the click handler.
<button class="my-button" data-feedfast="feedback">
Send feedback
</button>The modal asks for a category, title, optional details and an optional email: the same fields as the public board, with the same rate limiting and spam trap. The submitted idea starts with the author's own upvote.
Identify your users
When the button lives inside your dashboard, you already know who's clicking it. Pass them along and the modal drops its name and email fields entirely.
No setup. Whatever your page sends is accepted and stored, marked unverified.
<button
data-feedfast="feedback"
data-user-id="${user.id}"
data-user-email="${user.email}"
data-user-name="${user.name}"
>Send feedback</button>This is only asserted, not verified
- data-user-id
- Your own user id. Opaque to us, never shown publicly.
- data-user-email
- Prefills the author, and makes their votes portable.
- data-user-name
- Shown as “Posting as …” in the modal.
- data-user-hash
- HMAC-SHA256 of the user id. Required once a secret is set.
Set the same attributes on the loader script to cover every trigger on the page, or call Feedfast.identify(...) once your SPA has the session. Identity on a trigger beats identity on the loader, so one page can have both an authenticated and an anonymous entry point.
Feedfast.identify({ id: user.id, email: user.email, name: user.name });
Feedfast.identify(null); // back to anonymous on sign-outUptime bar
The same 90-day strip as your status page, at any size.
<div data-feedfast="uptime"></div>- data-monitor
- Show a single monitor, by name
- data-days
- 1–90. How much history to draw
- data-compact
- true | false: shorter bars
- data-overall
- true | false: the status line above the monitors
- data-slim
- true | false: drops the status line and the “View status page” link, and makes the whole badge the link instead. Implies data-compact unless you set it. Built for a site footer
Which day is a bar?
Changelog
Two shapes. Cards for a landing page or a dedicated “what's new” section; inline for a compact list in a sidebar or footer.
<div data-feedfast="changelog" data-variant="card" data-limit="3"></div>- data-variant
- card (default) | inline
- data-limit
- 1–10. How many entries
- data-excerpt
- true | false: card variant only
- data-tags
- true | false: show tag pills
Floating badge
On by default: a “What's new” badge pinned to a corner, with an unread dot that clears once the visitor opens it.
<!-- on by default -->
<script src="https://feedfa.st/embed.js" data-project="your-project" defer></script>
<!-- off, if you'd rather place widgets yourself -->
<script src="https://feedfa.st/embed.js" data-project="your-project" data-badge="false" defer></script>These docs load the script with data-badge="false", so the badge isn't pinned over the page. This button opens the same panel it would.
The unread state lives in the visitor's own browser. If maintenance is scheduled, a notice appears above the entries.
data-badge is read once, when the script boots. In a single-page app that is the page the visitor first landed on, so a route change into a screen with no room for the badge: a dashboard, a checkout, anything with its own floating button in that corner: needs to say so:
Feedfast.setBadge(false); // leaving a page that wants it
Feedfast.setBadge(true); // and backSafe to call before the script has finished loading only if you guard it, until then there is no window.Feedfast to call, and the attribute on the tag already describes the first page.
JavaScript API
The loader exposes window.Feedfast once it runs.
- Feedfast.openFeedback(opts?)
- Open the modal. { title, subtitle, user } frame it; { prefill } fills it in — see Feedback button → From code
- Feedfast.openChangelog()
- Open the “What's new” panel
- Feedfast.setBadge(visible)
- Show or hide the floating badge after load
- Feedfast.identify(user)
- Set the signed-in user; null clears it
- Feedfast.close()
- Close whatever is open
- Feedfast.refresh()
- Re-fetch and re-render every widget
- Feedfast.mount(root?)
- Scan for new widgets (usually automatic)
- Feedfast.data()
- The current payload, or null before first load
Private endpoints
A health check worth having is often one you would rather not leave open. A monitor can hold a credential and send it on every check.
Set it on the monitor, under Uptime → the monitor → Authentication. Three methods, which between them cover most APIs:
- Bearer token
- Sent as Authorization: Bearer <token>.
- Custom header
- Any header name you like, e.g. X-API-Key.
- Query parameter
- Appended to the URL, e.g. ?token=…
What your endpoint receives, so you know what to compare against. Prefer the bearer form: a token in a URL ends up in your own access logs, and in any proxy in front of them.
GET /api/health HTTP/1.1
Host: your-app.com
Authorization: Bearer <your token>
User-Agent: FeedFast-Monitor/1.0 (+https://feedfa.st)Unexpected redirect rather than forwarding it. Point the monitor at the final URL.Your token is encrypted before it is stored and is never shown again — the form only knows whether one is saved. Leave the field blank when editing a monitor to keep the existing token, or type a new one to replace it. If a check ever reports that the credential cannot be read, re-enter it: that means the deployment's signing secret changed.
RSS feeds
Both feeds are RSS 2.0 with content:encoded, cached for five minutes.
- https://feedfa.st/p/your-project/rss.xml
- Published changelog entries
- https://feedfa.st/p/your-project/status.rss
- Incidents and maintenance windows
JSON payload
The widgets read one small public document. It's CORS-open and cached for 60 seconds, so you can build your own UI on top of it.
- GET https://feedfa.st/api/widget/your-project
- No auth required
{
"project": { "name": "Acme Analytics", "slug": "acme", "accentColor": "#4F46E5" },
"hubUrl": "https://feedfa.st/p/acme",
"modules": { "changelog": true, "feedback": true, "status": true },
"entries": [{ "id": "…", "title": "…", "url": "…", "excerpt": "…",
"tags": ["New"], "publishedAt": "2026-08-14T05:12:59.305Z" }],
"categories": [{ "id": "…", "slug": "bug", "name": "Bug",
"color": "#EF4444", "isDefault": false }],
"uptime": {
"overall": { "state": "operational", "label": "All systems operational" },
"monitors": [{ "name": "API", "state": "up", "uptimePct": 99.82,
"bars": "uuuuuuuuduuu…" }],
"days": 90, "startDay": "2026-05-19", "timezone": "Europe/Bucharest"
},
"maintenance": { "title": "Database upgrade", "status": "SCHEDULED",
"startsAt": "2026-08-19T08:41:12.638Z",
"endsAt": "2026-08-19T09:41:12.638Z" }
}Each character in bars is one day, oldest first: u up, d down, g degraded, m maintenance, n no data. Every timestamp is ISO 8601 in UTC.
Board filters
Feedback board filters live in the URL, so any view you can see is a link you can share.
- ?category=bug
- One category, by slug
- ?category=bug,feature-request
- Several categories
- ?status=open,planned
- Filter by status
- ?sort=top | new | trending
- Top votes (default), newest, or votes in the last 7 days
- ?q=slack
- Free-text search across title and body
Status subscribers
Anyone can subscribe from your status page, with no account.
Subscriptions are double opt-in (the address only receives mail after the confirmation link is clicked) and every email carries a one-click unsubscribe link.
Subscribers are emailed when an incident opens or resolves, and at whichever maintenance milestones you enabled (scheduled, started, completed).
API keys
The REST API and the MCP server both authenticate with a bearer key from Settings → Account.
Keys look like ff_…. Only a SHA-256 hash is stored, so the plaintext is shown exactly once, at creation.
curl https://feedfa.st/api/v1/projects \
-H "Authorization: Bearer ff_your_key_here"- Scope
- READ_ONLY or READ_WRITE, writes 403 on a read-only key
- Rate limit
- 120 requests per minute per key
- Reach
- Every project on the account that owns the key
REST API
Everything the dashboard can do to a project, over HTTP.
- GET /api/v1/projects
- Every project this key can reach
- GET /api/v1/projects/:slug/changelog
- ?status=&limit=
- POST /api/v1/projects/:slug/changelog
- Create, and publish by default
- PATCH /api/v1/projects/:slug/changelog
- Edit, publish or unpublish
- GET /api/v1/projects/:slug/feedback
- ?category=&status=&sort=&q=&limit=
- GET /api/v1/projects/:slug/feedback/:postId
- One post with its comment thread
- PATCH /api/v1/projects/:slug/feedback
- Status, category, pin, owner reply
- POST /api/v1/projects/:slug/feedback/:postId/resolve
- Close it, reply, and publish the changelog entry in one transaction
- GET /api/v1/projects/:slug/uptime
- ?days=, state, uptime windows, incidents
- GET /api/v1/projects/:slug/maintenance
- ?upcoming=true
- POST /api/v1/projects/:slug/maintenance
- Schedule a window
- PATCH /api/v1/projects/:slug/maintenance
- start | complete | cancel
Two ways to say when
timezone ("2026-09-01T02:00") or a full ISO instant ("2026-08-31T23:00:00Z"). The first is what a person would say; the second is what a machine already has.curl -X POST https://feedfa.st/api/v1/projects/acme/maintenance \
-H "Authorization: Bearer ff_…" \
-H "Content-Type: application/json" \
-d '{
"title": "Database upgrade",
"startsAt": "2026-09-01T02:00",
"endsAt": "2026-09-01T03:30",
"timezone": "Europe/Bucharest",
"monitors": ["API"]
}'MCP server
Connect Claude or any MCP client and let it publish release notes, schedule maintenance, and read your board and uptime. Point it at the hosted endpoint, or run the package locally.
Hosted: a URL and your key
Nothing to install and nothing to update: the server runs on ours. Works in Claude Code, Claude Desktop, Cursor, Windsurf and VS Code.
Claude Code
One command, then restart the session.
claude mcp add --transport http feedfast https://feedfa.st/api/mcp --header "Authorization: Bearer ff_your_key_here"Claude Desktop, Cursor, Windsurf, VS Code
The same object everywhere: only the config file's location differs.
{
"mcpServers": {
"feedfast": {
"type": "http",
"url": "https://feedfa.st/api/mcp",
"headers": {
"Authorization": "Bearer ff_your_key_here"
}
}
}
}Local: run it through npx
For clients without HTTP transport, and for when the key should stay on your machine. Fetched from npm on each run.
Claude Code
Add --scope project to commit it to the repo for your team.
claude mcp add feedfast --env FEEDFAST_API_KEY=ff_your_key_here -- npx -y feedfast-mcpClaude Desktop, Cursor, Windsurf, Zed, VS Code, Gemini CLI
Same file as above.
{
"mcpServers": {
"feedfast": {
"command": "npx",
"args": [
"-y",
"feedfast-mcp"
],
"env": {
"FEEDFAST_API_KEY": "ff_your_key_here"
}
}
}
}Codex
TOML, in ~/.codex/config.toml.
[mcp_servers.feedfast]
command = "npx"
args = ["-y", "feedfast-mcp"]
env = { FEEDFAST_API_KEY = "ff_your_key_here" }The key comes from Settings → Account → API keys, and both transports take the same one. A read-only key lets an agent answer questions but never change anything. Give it a read-write key only when you want it to publish. The local form runs feedfast-mcp, fetched from npm on each run.
- list_projects
- Slugs, enabled modules and content counts
- get_uptime
- Monitor state, uptime windows, open and recent incidents
- list_feedback
- Filter by status, category, search; sort by top/new/trending
- get_feedback
- One post with its full comment thread
- update_feedback
- Status, category, pin, and public owner replies
- resolve_feedback
- Close the loop: mark it done, reply publicly, publish the changelog entry, link them
- list_changelog
- Published and draft entries
- publish_changelog
- Write a release note, public immediately
- update_changelog
- Edit, publish a draft, or unpublish
- list_maintenance
- Scheduled, in progress and completed windows
- schedule_maintenance
- Announce planned downtime
- transition_maintenance
- Start now, complete early, or cancel
It can only do what the key can
The hosted endpoint
POST https://feedfa.st/api/mcp speaks Streamable HTTP JSON-RPC, with the key as Authorization: Bearer instead of an environment variable. It is stateless: no session id, so every request carries its own key and nothing is pinned to a process. Tool calls are rate-limited per key, at the same 120 requests a minute as the REST API.Want to see it all working? Browse the live demo hub.