REST API reference.
A small, predictable JSON API over HTTPS. Every response wraps results in a data field; every error in an error field. Base URL: https://YOUR-APP/api/v1
Authentication
The REST API and webhooks are on the Team plan (and the 14-day trial). Create a key in Settings → Developers. Keys start with mur_live_, are scoped to one workspace, and are shown only once. Send the key as a Bearer token on every request.
curl https://YOUR-APP/api/v1/posts?board=feature-requests&sort=top \
-H "Authorization: Bearer mur_live_…"The same key works with the MCP server. CORS is enabled, but never ship a key to the browser — call the API from your server.
Errors & limits
Errors return a non-2xx status and a body of the form { "error": { "status": 404, "message": "Post not found" } }. Validation failures return 400 with details about the offending field; a missing or invalid key returns 401.
| Field | Type | Description |
|---|---|---|
| 400 | Bad request | Validation failed — check the message for the field. |
| 401 | Unauthorized | Missing, malformed or revoked API key. |
| 402 | Plan required | This needs the Team plan, or a plan limit was reached. The body includes an upgrade_url. |
| 404 | Not found | The object doesn’t exist in this workspace. |
| 500 | Server error | Something went wrong on our side. Safe to retry idempotent requests. |
Posts
/api/v1/postsList posts, newest or top first. Merged posts are excluded.
| Field | Type | Description |
|---|---|---|
| board | string | Board slug or id. |
| status | string | open, under_review, planned, in_progress, complete, closed. |
| q | string | Text search over titles and descriptions. |
| sort | string | trending (default), top, new, mrr, score, updated. |
| limit | number | 1–100, default 25. |
{
"data": [
{
"id": "p_81",
"title": "Bulk-edit tickets from the inbox",
"status": "planned",
"status_label": "Planned",
"board": { "id": "b_1", "slug": "feature-requests", "name": "Feature requests" },
"category": { "id": "c_4", "name": "Inbox" },
"tags": ["power-users"],
"eta": "2026-11",
"votes": 248,
"comments": 31,
"mrr": 118400,
"score": 92,
"url": "https://YOUR-APP/p/lumen/posts/p_81"
}
],
"count": 1
}/api/v1/postsCreate a post. Pass an author email to attribute it to a customer (created if new).
| Field | Type | Description |
|---|---|---|
| board | string | Required. Board slug or id (or board_id). |
| title | string | Required. 3–140 characters. |
| body | string | Markdown, up to 10,000 characters. |
| author_email | string | Customer to attribute the post to. |
| author_name | string | Display name for a new customer. |
| company | string | Company name to attach. |
curl -X POST https://YOUR-APP/api/v1/posts \
-H "Authorization: Bearer mur_live_…" \
-H "Content-Type: application/json" \
-d '{
"board": "feature-requests",
"title": "Scheduled CSV exports",
"body": "Weekly export of the reports dashboard to S3.",
"author_email": "grace@northwind.io",
"company": "Northwind"
}'/api/v1/posts/{id}Retrieve one post with its voters (name, email, company, segment, priority), comments and AI summary.
/api/v1/posts/{id}Update a post. Changing status notifies voters; message is included in the update email.
| Field | Type | Description |
|---|---|---|
| status | string | New status id. |
| message | string | Optional note sent with the status change. |
| title / body | string | Edit content. |
| owner | string | null | Team member email or id. |
| eta | string | null | Month in YYYY-MM format. |
| category | string | null | Category id or name. |
curl -X PATCH https://YOUR-APP/api/v1/posts/p_81 \
-H "Authorization: Bearer mur_live_…" \
-H "Content-Type: application/json" \
-d '{ "status": "in_progress", "eta": "2026-11", "message": "Kicking off this cycle." }'Votes
/api/v1/posts/{id}/votesVote on behalf of a customer. Idempotent per customer: repeat calls update the priority and return 200 instead of 201.
| Field | Type | Description |
|---|---|---|
| string | Required. The customer’s email. | |
| name | string | Name for a new customer. |
| company | string | Company to attach. |
| priority | string | nice, important (default) or must. |
curl -X POST https://YOUR-APP/api/v1/posts/p_81/votes \
-H "Authorization: Bearer mur_live_…" \
-H "Content-Type: application/json" \
-d '{ "email": "grace@northwind.io", "company": "Northwind", "priority": "must" }'Boards
/api/v1/boardsList boards with slug, name, privacy and post count. Use the slug wherever a board parameter is accepted.
Changelog
/api/v1/changelogPublished changelog entries, newest first, with labels and linked posts.
| Field | Type | Description |
|---|---|---|
| status | string | published (default) or all. |
| label | string | Filter by label, e.g. New or Fixed. |
| limit | number | 1–100, default 20. |
Autopilot capture
/api/v1/autopilot/captureSend raw text — a transcript, an email, a review — and Autopilot extracts requests and suggests a matching post for each. Results appear in the Autopilot inbox for review.
| Field | Type | Description |
|---|---|---|
| text | string | Required. 10–50,000 characters. |
| source | string | intercom, zendesk, gong, slack, hubspot, g2, email, api… (body or query). |
| customer_email | string | Attribute captured feedback to this customer. |
| customer_name | string | Name for a new customer. |
| company_name | string | Company to attach. |
curl -X POST "https://YOUR-APP/api/v1/autopilot/capture?source=intercom" \
-H "Authorization: Bearer mur_live_…" \
-H "Content-Type: application/json" \
-d '{
"text": "We would love to bulk edit tickets from the inbox — 40 agents are waiting on this.",
"customer_email": "grace@northwind.io",
"company_name": "Northwind"
}'Webhooks
Add endpoints in Settings → Developers → Webhooks and choose the events to receive. Murmur sends a JSON POST with an X-Murmur-Event header and an X-Murmur-Signature header: the hex HMAC-SHA256 of the raw body, keyed with your endpoint’s secret.
{
"event": "post.status_changed",
"workspace": "lumen",
"created_at": "2026-10-07T09:14:22.000Z",
"data": {
"post": { "id": "p_81", "title": "Bulk-edit tickets from the inbox", "status": "in_progress" },
"from": "planned",
"to": "in_progress"
}
}Always verify the signature against the raw request body before parsing it, and use a constant-time comparison.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody: string, signature: string, secret: string) {
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
return signature.length === expected.length &&
timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
// In your handler: verify(await req.text(), req.headers.get("x-murmur-signature")!, SECRET)
Comments
/api/v1/posts/{id}/commentsAdd a comment. Without an email it posts as the workspace. Set
internalfor a team-only note.