Build against your feedback loop.
The Hollahoop API is exposed through a hosted MCP JSON-RPC endpoint. Use one org-scoped token to let Cursor, Claude, Codex, internal scripts, or backend jobs read feedback, move roadmap work, publish Updates, and maintain docs.
Token auth
Send Authorization: Bearer hh_live_… with every request. Tokens are created from your project workspace and scoped to that organisation.
MCP-native
Use the same endpoint from agents and code. Tool schemas are discoverable through tools/list, and execution happens through tools/call.
Read / write scopes
Each tool declares the permission it needs. Revoked tokens stop working immediately, and REST calls include rate-limit headers.
Endpoint
POST JSON-RPC 2.0 requests to the MCP endpoint. Browser GET requests redirect to the public MCP overview, while API clients should send JSON.
- Base URL
- https://hollahoop.app/mcp
- Auth header
- Authorization: Bearer hh_live_…
- Content type
- application/json
curl https://hollahoop.app/mcp \
-H "Authorization: Bearer hh_live_..." \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}'REST mirror
Prefer plain HTTP? The same Bearer token works against the REST endpoints below. Bodies and responses match the MCP tool schemas one-to-one, so you can pick whichever surface fits your stack.
Rate limits are enforced per API key and per client IP (120 read / 30 write requests per minute; Pro orgs with higher_api_limit get 5×). Responses include x-ratelimit-limit, x-ratelimit-remaining, and x-ratelimit-reset; 429 responses also include retry-after. Repeated 429s trigger a short storm cooldown. Operator notes: docs/MCP_SECURITY.md in the repo.
Generating a client? The full machine-readable contract lives at /api/v1/openapi.json (OpenAPI 3.1). Point Swagger UI, Redoc, or your codegen of choice at it.
Prefer a tiny official client? Install @hollahoop/sdk from the repo at packages/js (see README for listPosts, createPost, commentOnPost, listChangelog, publishChangelog, and listStatuses). No runtime dependencies beyond fetch.
/api/v1/healthNo authLiveness probe used by /status and external monitors.
/api/v1/readyNo authReadiness probe: checks PostgREST and Redis. 503 when degraded.
/api/v1/projectsBearer (read)List projects under this token's organisation. Mirrors list_projects.
/api/v1/projectsBearer (write)Create a project (boards, statuses, tags auto-seeded). Mirrors create_project.
/api/v1/projects/:projectBearer (write)Update project fields, visibility (is_public) or slug (the old slug keeps working and redirects). Mirrors update_project.
/api/v1/projects/:project/postsBearer (read)List feedback posts with board/status/sort pagination. Mirrors list_posts.
/api/v1/projects/:project/postsBearer (write)Create a feedback post on a board. Mirrors create_post.
/api/v1/projects/:project/posts/:postIdBearer (read)Fetch a single post with its comments. Mirrors get_post.
/api/v1/projects/:project/posts/:postIdBearer (write)Update post status, pin/lock, and/or release estimate. Mirrors update_post_status.
/api/v1/projects/:project/posts/:postId/contentBearer (write)Edit a post's title and/or body (passes the publish guard). Mirrors update_post.
/api/v1/projects/:project/posts/:postId/commentsBearer (read)List comments on a post (limit/offset pagination). Mirrors list_comments.
/api/v1/projects/:project/posts/:postId/commentsBearer (write)Add a comment on a post. Mirrors comment_on_post.
/api/v1/projects/:project/posts/:postId/votesBearer (write)Cast a vote on a post (idempotent). Mirrors add_vote.
/api/v1/projects/:project/boardsBearer (read)List feedback boards for a project. Mirrors list_boards.
/api/v1/projects/:project/statusesBearer (read)List the roadmap status workflow for a project. Mirrors list_statuses.
/api/v1/projects/:project/publish-guardBearer (read)Read publish guard rules (effective, org, and project). Mirrors get_publish_guard.
/api/v1/projects/:project/publish-guardBearer (write)Set project publish guard rules; null inherits from the org. Mirrors set_publish_guard.
/api/v1/projects/:project/publish-guard/checkBearer (read)Dry run the publish guard on a title and body. Nothing is saved. Mirrors check_publish.
/api/v1/projects/:project/tagsBearer (read)List feedback tags for a project. Mirrors list_tags.
/api/v1/projects/:project/tagsBearer (write)Create a feedback tag. Mirrors create_tag.
/api/v1/projects/:project/tags/:tagIdBearer (write)Delete a feedback tag. Mirrors delete_tag.
/api/v1/projects/:project/posts/:postId/tagsBearer (write)Replace tags on a post. Mirrors set_post_tags.
/api/v1/projects/:project/surveysBearer (read)List survey campaigns (NPS / CSAT) with response counts and summary scores. Mirrors list_surveys.
/api/v1/projects/:project/surveysBearer (write)Create an NPS or CSAT survey campaign. Mirrors create_survey.
/api/v1/projects/:project/surveys/:surveyIdBearer (read)Fetch one survey with summary and recent responses. Mirrors get_survey.
/api/v1/projects/:project/surveys/:surveyIdBearer (write)Activate/deactivate or edit a survey. Mirrors update_survey.
/api/v1/projects/:project/surveys/:surveyIdBearer (write)Permanently delete a survey campaign and its responses. Mirrors delete_survey.
/api/v1/projects/:project/ticketsBearer (read)List support inbox tickets (optional status, priority, unassigned, limit). Mirrors list_tickets.
/api/v1/projects/:project/ticketsBearer (write)Create a support ticket with an opening message. Mirrors create_ticket.
/api/v1/projects/:project/tickets/:ticketIdBearer (read)Fetch one support ticket with its messages. Mirrors get_ticket.
/api/v1/projects/:project/tickets/:ticketIdBearer (write)Update ticket status, priority, or assignee. Mirrors update_ticket.
/api/v1/projects/:project/tickets/:ticketId/repliesBearer (write)Add a message to a support ticket. Mirrors reply_to_ticket.
/api/v1/projects/:project/searchBearer (read)Full-text search across posts, Updates, and docs. Mirrors search (?q=, optional kind & limit).
/api/v1/projects/:project/statusBearer (read)Fetch the public status page snapshot. Mirrors get_status_page.
/api/v1/projects/:project/status/subscribersBearer (read)List status-page email subscribers (limit/offset). Mirrors list_status_subscribers.
/api/v1/projects/:project/status/subscribers/:subscriberIdBearer (write)Remove a status-page email subscriber. Mirrors delete_status_subscriber.
/api/v1/projects/:project/statusBearer (write)Enable or disable the public status page. Mirrors set_status_page_enabled.
/api/v1/projects/:project/status/incidentsBearer (write)Open a status-page incident. Mirrors create_status_incident.
/api/v1/projects/:project/status/incidents/:incidentId/updatesBearer (write)Post an incident lifecycle update. Mirrors add_status_incident_update.
/api/v1/projects/:project/status/componentsBearer (write)Add a status-page component. Mirrors create_status_component.
/api/v1/projects/:project/status/components/:componentIdBearer (write)Set a component current_status. Mirrors update_status_component.
/api/v1/projects/:project/changelogBearer (read)List Updates/changelog entries, including drafts unless filtered. Mirrors list_changelog.
/api/v1/projects/:project/changelog/:entryIdBearer (read)Fetch one changelog entry by UUID or slug. Mirrors get_changelog.
/api/v1/projects/:project/changelogBearer (write)Publish a changelog entry. Optional translations ({ fi: { title, body_md } }). Mirrors publish_changelog.
/api/v1/projects/:project/changelog/:entryBearer (write)Edit or publish a changelog entry. translations merge per locale (null removes one). Mirrors update_changelog.
/api/v1/projects/:project/docsBearer (read)List doc pages (public, internal, or all). Mirrors list_docs.
/api/v1/projects/:project/docs/:pageIdBearer (read)Fetch one doc page by UUID or slug (optional locale and version query params). Mirrors get_doc.
/api/v1/projects/:project/docsBearer (write)Create or update a doc page. Mirrors upsert_doc_page.
/api/v1/projects/:project/domainsBearer (read)List custom domains for a project. Mirrors list_domains.
/api/v1/projects/:project/domainsBearer (write)Add a custom domain (returns DNS verification hints). Mirrors add_domain.
/api/v1/projects/:project/domains/:domainId/verifyBearer (write)Re-check domain DNS verification. Mirrors verify_domain.
/api/v1/projects/:project/ciBearer (write)Record a CI run (GitHub Actions, GitLab, etc.). Mirrors report_ci_status.
/api/v1/projects/:project/huddle/sitesBearer (read)List Huddle sites with open and resolved pin counts. Mirrors list_huddle_sites.
/api/v1/projects/:project/huddle/sitesBearer (write)Register a site for on-page feedback; returns the install snippet. Mirrors create_huddle_site.
/api/v1/projects/:project/huddle/sites/:siteIdBearer (write)Set a site live or paused, or change its white label branding and logo. Mirrors update_huddle_site.
/api/v1/projects/:project/huddle/sites/:siteId/guest-linksBearer (write)Create a guest review link for clients without an account. Mirrors create_huddle_guest_link.
/api/v1/projects/:project/huddle/sites/:siteId/guest-links/:linkIdBearer (write)Revoke a guest review link. Mirrors revoke_huddle_guest_link.
/api/v1/projects/:project/huddle/pinsBearer (read)List pins, filtered by site_id, page_url, and state (open, resolved, all). Mirrors list_huddle_pins.
/api/v1/projects/:project/huddle/pins/:pinIdBearer (read)Fetch a pin with its linked post and comment thread. Mirrors get_huddle_pin.
/api/v1/projects/:project/huddle/pins/:pinId/repliesBearer (write)Post a staff reply on a pin (publish guard applies). Mirrors reply_huddle_pin.
/api/v1/projects/:project/huddle/pins/:pinId/resolveBearer (write)Resolve a pin. Mirrors resolve_huddle_pin.
/api/v1/projects/:project/huddle/pins/:pinId/reopenBearer (write)Reopen a resolved pin. Mirrors reopen_huddle_pin.
curl -X POST https://hollahoop.app/api/v1/projects \
-H "Authorization: Bearer hh_live_..." \
-H "Content-Type: application/json" \
-d '{
"slug": "acme-feedback",
"name": "Acme Feedback",
"tagline": "What customers want next",
"is_public": true
}'curl "https://hollahoop.app/api/v1/projects/hollahoop/posts?status=planned&sort=top&limit=10" \
-H "Authorization: Bearer hh_live_..."curl "https://hollahoop.app/api/v1/projects/hollahoop/changelog?status=published&limit=10" \
-H "Authorization: Bearer hh_live_..."curl "https://hollahoop.app/api/v1/projects/hollahoop/search?q=webhooks&kind=posts&limit=10" \
-H "Authorization: Bearer hh_live_..."curl -X POST https://hollahoop.app/api/v1/projects/hollahoop/ci \
-H "Authorization: Bearer hh_live_..." \
-H "Content-Type: application/json" \
-d '{
"provider": "github",
"repo": "acme/hollahoop",
"sha": "abc1234567890",
"status": "success",
"url": "https://github.com/acme/hollahoop/actions/runs/42"
}'Webhooks, Zapier, and Make
Add an endpoint under Project settings, Webhooks and pick the events you want. Every delivery is a signed JSON POST (X-Hollahoop-Signature: t=...,v1=..., an HMAC SHA-256 of timestamp.body with the endpoint secret), retried with backoff. Huddle sends huddle.pin.created, huddle.pin.resolved, huddle.pin.reopened, huddle.comment.created, and huddle.page.approved.
For Zapier, start a Zap with Webhooks by Zapier, choose Catch Hook, and paste the hook URL as the endpoint. For Make, add a Custom webhook module and paste its address. Then drop one test pin (or replay a delivery from the endpoint log) so either tool can learn the payload fields.
{
"id": "6f1c...",
"api_version": "2026-06-05",
"event": "huddle.pin.created",
"created_at": "2026-10-24T09:12:00.000Z",
"project": { "id": "...", "slug": "acme", "name": "Acme" },
"data": {
"pin": {
"id": "...",
"state": "open",
"page_url": "https://acme.com/pricing",
"excerpt": "The CTA overlaps the footer on mobile",
"admin_url": "https://hollahoop.app/app/acme/acme/huddle?pin=...",
"site": { "id": "...", "url": "https://acme.com", "platform": "wordpress" },
"artifact": null,
"post": { "id": "...", "title": "The CTA overlaps...", "url": "https://hollahoop.app/acme/feedback/bugs/p/..." }
}
}
}{
"pin": { "id": "...", "state": "open", "page_url": "https://acme.com/pricing", "...": "..." },
"comment": {
"id": "...",
"excerpt": "Fixed in the latest deploy, can you check?",
"author_name": null,
"created_at": "2026-10-24T10:02:00.000Z"
}
}Errors
Every non-2xx REST response uses the same JSON envelope, so you can handle failures with one code path. The code is stable and machine-readable; the message is for your logs, not end users.
{
"error": {
"code": "not_found",
"message": "No project 'acme-feedback' in org 'acme'"
}
}HTTP/1.1 429 Too Many Requests
x-ratelimit-limit: 120
x-ratelimit-remaining: 0
x-ratelimit-reset: 1718323200
retry-after: 42
{
"error": {
"code": "rate_limited",
"message": "Too many requests for this API key. Please retry after the reset window."
}
}| HTTP | code | When it happens |
|---|---|---|
| 400 | invalid_json | Request body was not valid JSON. |
| 401 | missing_token / invalid_token / revoked_token | No usable Bearer token was supplied. |
| 403 | forbidden_scope / forbidden | The key lacks the read or write scope this endpoint needs. |
| 404 | not_found | The project or resource is not in this organisation. |
| 409 | slug_taken / conflict | A project with that slug, or a Huddle site with that URL, already exists. |
| 422 | invalid_input / invalid_slug | Body or query parameters failed validation. |
| 429 | rate_limited | Per-key rate limit exceeded. See retry-after. |
| 500 | internal_error | Unexpected server error. Safe to retry with backoff. |
Tool calls
Call a tool by name with a JSON object that matches its schema. Agents do this automatically; scripts can call the same endpoint directly.
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "list_projects",
"arguments": {}
}
}{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "publish_changelog",
"arguments": {
"project": "hollahoop",
"title": "New API docs",
"body_md": "We added a public API reference.",
"tag": "announcement"
}
}
}Available tools
62 tools are currently exposed. Read tools can inspect data; write tools can create or mutate feedback, roadmap, Updates, and docs.
Read
List every project the API key can see (scoped to a single Hollahoop organisation).
List feedback boards on a project (e.g. feature-requests, bugs, general).
List the post-status workflow for a project (Open / Planned / In progress / Shipped / Declined and any custom ones).
List feedback tags for a project (slug, label, color).
List survey campaigns on a project (NPS / CSAT), including response_count and summary_score.
Get one survey campaign with summary_score, response_count, and recent responses.
List support inbox tickets for a project. Spam is hidden unless status=spam. Most recently active first.
Get one support ticket and its messages (oldest first), scoped to a project.
List feedback posts on a project, optionally filtered by board and/or status. Default sort is by vote count.
Fetch a single post by id, including its comments.
Read the publish guard rules for a project: effective action per category (security, internal, embarrassing, minor, off_topic), plus the org and project overrides. Null project values inherit from the org.
Dry run the publish guard on a title and body without saving anything. Returns the decision (publish, rewrite, hold, skip), the category, the reason, and the text that would be published.
List comments on a feedback post, oldest first. Use limit and offset to paginate; get_post also returns comments inline.
List changelog / Updates entries for a project (drafts and published).
Fetch a single changelog / Updates entry by id or slug.
List documentation pages for a project, grouped by section. Use the `visibility` filter to scope to public or internal pages.
Fetch a single documentation page by id or slug, including Markdown body. When using a slug, scope with locale and version to match list_docs / upsert_doc_page.
Free-text search across posts, changelog entries, and docs for a project. Uses PostgreSQL full-text search (ts_rank) when available, with ILIKE fallback. Semantic pgvector search is on the roadmap.
Read the public status page payload for a project (components + recent incidents). Requires the project status page to be enabled. Useful for embedding status in dashboards.
List email subscribers who opted in on the public status page (newest first). Project members only; unsubscribe tokens are never returned.
List custom hostnames for a project, including TXT challenge values for pending rows.
List Huddle sites (customer websites that collect on-page feedback pins) for a project, with open and resolved pin counts and last activity.
List page and site approvals left from the Huddle overlay ("Approve this page" / "Approve the whole site"), newest first, with who approved and when. state=all includes withdrawn approvals and the reason.
List Huddle feedback pins on a project, newest first. Filter by site_id, page_url, and state (open, resolved, or all; default open).
Fetch one Huddle pin with its page, anchor, linked feedback post, and the comment thread on that post.
Write
Create a new project under this API key’s organisation. Returns the project plus the URL of its public board. Default boards, statuses, changelog tags, and docs section are auto-seeded.
Update an existing project: slug (rename; the old slug keeps working and redirects), name, tagline, description, accent_color, and/or is_public (visibility of the public board). Accepts the project slug or id. Returns the updated project plus its public board URL.
Create a feedback tag on a project.
Delete a feedback tag from a project. Removes the tag from all posts that used it.
Replace the tags on a feedback post (full set). Pass an empty tag_ids array to clear.
Create an NPS or CSAT survey campaign on a project. Returns the campaign id and public response URL.
Update a survey campaign (activate/deactivate and/or edit title/question). At least one field required.
Permanently delete a survey campaign and all of its responses from a project.
Create a support ticket with an opening inbound message. Defaults to status=open, priority=normal.
Update a support ticket status, priority, and/or assignee. At least one field required.
Add a message to a support ticket. Outbound replies move new/pending tickets to open.
Create a new feedback / roadmap post. Defaults to the feature-requests board and the project default status (Open).
Set project-level publish guard rules. on_security / on_internal / on_embarrassing / on_minor / on_off_topic take publish, rewrite, hold, skip, or null to inherit from the org. Owners and admins can always change them; members only while the org lets projects override.
Edit the title and/or body of an existing feedback or roadmap post. The new wording passes the publish guard like create_post. Use update_post_status for status, pin, lock or release estimate.
Update a post: roadmap status, pin/lock flags, and/or release estimate. Status moves accept feedback (open → planned), start work, or ship.
Add a comment (or reply, via parent_id) to a post. The author is the user who created this API key.
Alias of comment_on_post. Add a comment (or reply, via parent_id) to a post. The author is the user who created this API key.
Cast a vote on a post as the API key owner. Idempotent: if the user already voted, returns ok without double-counting. Authors cannot vote on their own posts.
Create a changelog / Updates entry. Set publish=false to leave it as a draft. Auto-derives a slug from the title if none is given. The optional `tag` becomes the pill on the public Updates feed. The optional `translations` ({ "fi": { title, body_md } }) show readers their language; the entry keeps one slug, date and notification.
Edit an existing changelog / Updates entry (title, body_md, tag, translations) and/or publish it with publish=true. `translations` merges per locale: a { title, body_md } value replaces that locale, null removes it, other locales stay. publish=false moves a published entry back to draft. The slug never changes. Publishing runs the same public-safety check as publish_changelog, including all translation text.
Create or update a documentation page (uniquely identified by project + slug + locale). Body is Markdown. Pass visibility=internal to keep the page hidden from the public site.
Remove a status-page email subscriber (same effect as the public unsubscribe link). Pending incident emails for that subscriber are dropped via cascade.
Enable or disable the public status page for a project (same as Settings → Status page).
Open a status-page incident (starts in investigating). Does not require the public status page to be enabled.
Post an update on a status-page incident and advance its lifecycle (investigating → identified → monitoring → resolved).
Add a component to the project status page.
Set the current operational status of a status-page component (for automation / CI hooks).
Record a CI run (GitHub Actions, GitLab CI, or generic) against a project. Same payload as POST /api/integrations/ci/webhook.
Add a custom hostname to a project. Returns the TXT name and value to publish, then call verify_domain. Needs the Starter plan or higher; Free orgs get a plan_required error.
Check the TXT challenge for a pending custom hostname and mark it verified when the record matches. Verifying a pending hostname needs the Starter plan or higher (plan_required otherwise); verified hostnames keep working on any plan.
Register a customer website for Huddle on-page feedback. Detects the platform when not given and returns the install snippet (or WordPress plugin steps). New sites start as draft and review-only; set status live to start collecting pins.
Change a Huddle site: status (draft, live, paused), white label branding (accent, toolbar_bg, toolbar_text, radius, label, display_name, position, logo_url, hide_branding, board; null clears a field), logo_upload (PNG, JPEG or WebP; no SVG), platform, install method, review_only (overlay only for guest links, staff and signed users), unapprove_on_new_pin, extra allowed origins (staging hosts), or the query params that make a different page. Hiding the Hollahoop credit needs the Starter plan or higher. Returns the site, the branding guests will really see (unreadable text colours are replaced) and a fresh install snippet.
Post a staff reply on the thread behind a Huddle pin (a comment on its linked feedback post). The publish guard runs first; held or skipped replies are refused. The author is the user who created this API key.
Mark a Huddle pin as resolved. It disappears from the live site and fires huddle.pin.resolved.
Reopen a resolved Huddle pin so it shows on the live site again. Fires huddle.pin.reopened.
Create a time-limited guest review link for a Huddle site so a client can leave pins without an account. The review_url is shown once; only a hash is stored.
Revoke a Huddle guest review link right away. Revoking twice is a no-op.