Hollahoop
API reference

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.

GET/api/v1/healthNo auth

Liveness probe used by /status and external monitors.

GET/api/v1/readyNo auth

Readiness probe: checks PostgREST and Redis. 503 when degraded.

GET/api/v1/projectsBearer (read)

List projects under this token's organisation. Mirrors list_projects.

POST/api/v1/projectsBearer (write)

Create a project (boards, statuses, tags auto-seeded). Mirrors create_project.

PATCH/api/v1/projects/:projectBearer (write)

Update project fields, visibility (is_public) or slug (the old slug keeps working and redirects). Mirrors update_project.

GET/api/v1/projects/:project/postsBearer (read)

List feedback posts with board/status/sort pagination. Mirrors list_posts.

POST/api/v1/projects/:project/postsBearer (write)

Create a feedback post on a board. Mirrors create_post.

GET/api/v1/projects/:project/posts/:postIdBearer (read)

Fetch a single post with its comments. Mirrors get_post.

PATCH/api/v1/projects/:project/posts/:postIdBearer (write)

Update post status, pin/lock, and/or release estimate. Mirrors update_post_status.

PATCH/api/v1/projects/:project/posts/:postId/contentBearer (write)

Edit a post's title and/or body (passes the publish guard). Mirrors update_post.

GET/api/v1/projects/:project/posts/:postId/commentsBearer (read)

List comments on a post (limit/offset pagination). Mirrors list_comments.

POST/api/v1/projects/:project/posts/:postId/commentsBearer (write)

Add a comment on a post. Mirrors comment_on_post.

POST/api/v1/projects/:project/posts/:postId/votesBearer (write)

Cast a vote on a post (idempotent). Mirrors add_vote.

GET/api/v1/projects/:project/boardsBearer (read)

List feedback boards for a project. Mirrors list_boards.

GET/api/v1/projects/:project/statusesBearer (read)

List the roadmap status workflow for a project. Mirrors list_statuses.

GET/api/v1/projects/:project/publish-guardBearer (read)

Read publish guard rules (effective, org, and project). Mirrors get_publish_guard.

PATCH/api/v1/projects/:project/publish-guardBearer (write)

Set project publish guard rules; null inherits from the org. Mirrors set_publish_guard.

POST/api/v1/projects/:project/publish-guard/checkBearer (read)

Dry run the publish guard on a title and body. Nothing is saved. Mirrors check_publish.

GET/api/v1/projects/:project/tagsBearer (read)

List feedback tags for a project. Mirrors list_tags.

POST/api/v1/projects/:project/tagsBearer (write)

Create a feedback tag. Mirrors create_tag.

DELETE/api/v1/projects/:project/tags/:tagIdBearer (write)

Delete a feedback tag. Mirrors delete_tag.

PUT/api/v1/projects/:project/posts/:postId/tagsBearer (write)

Replace tags on a post. Mirrors set_post_tags.

GET/api/v1/projects/:project/surveysBearer (read)

List survey campaigns (NPS / CSAT) with response counts and summary scores. Mirrors list_surveys.

POST/api/v1/projects/:project/surveysBearer (write)

Create an NPS or CSAT survey campaign. Mirrors create_survey.

GET/api/v1/projects/:project/surveys/:surveyIdBearer (read)

Fetch one survey with summary and recent responses. Mirrors get_survey.

PATCH/api/v1/projects/:project/surveys/:surveyIdBearer (write)

Activate/deactivate or edit a survey. Mirrors update_survey.

DELETE/api/v1/projects/:project/surveys/:surveyIdBearer (write)

Permanently delete a survey campaign and its responses. Mirrors delete_survey.

GET/api/v1/projects/:project/ticketsBearer (read)

List support inbox tickets (optional status, priority, unassigned, limit). Mirrors list_tickets.

POST/api/v1/projects/:project/ticketsBearer (write)

Create a support ticket with an opening message. Mirrors create_ticket.

GET/api/v1/projects/:project/tickets/:ticketIdBearer (read)

Fetch one support ticket with its messages. Mirrors get_ticket.

PATCH/api/v1/projects/:project/tickets/:ticketIdBearer (write)

Update ticket status, priority, or assignee. Mirrors update_ticket.

POST/api/v1/projects/:project/tickets/:ticketId/repliesBearer (write)

Add a message to a support ticket. Mirrors reply_to_ticket.

GET/api/v1/projects/:project/searchBearer (read)

Full-text search across posts, Updates, and docs. Mirrors search (?q=, optional kind & limit).

GET/api/v1/projects/:project/statusBearer (read)

Fetch the public status page snapshot. Mirrors get_status_page.

GET/api/v1/projects/:project/status/subscribersBearer (read)

List status-page email subscribers (limit/offset). Mirrors list_status_subscribers.

DELETE/api/v1/projects/:project/status/subscribers/:subscriberIdBearer (write)

Remove a status-page email subscriber. Mirrors delete_status_subscriber.

PATCH/api/v1/projects/:project/statusBearer (write)

Enable or disable the public status page. Mirrors set_status_page_enabled.

POST/api/v1/projects/:project/status/incidentsBearer (write)

Open a status-page incident. Mirrors create_status_incident.

POST/api/v1/projects/:project/status/incidents/:incidentId/updatesBearer (write)

Post an incident lifecycle update. Mirrors add_status_incident_update.

POST/api/v1/projects/:project/status/componentsBearer (write)

Add a status-page component. Mirrors create_status_component.

PATCH/api/v1/projects/:project/status/components/:componentIdBearer (write)

Set a component current_status. Mirrors update_status_component.

GET/api/v1/projects/:project/changelogBearer (read)

List Updates/changelog entries, including drafts unless filtered. Mirrors list_changelog.

GET/api/v1/projects/:project/changelog/:entryIdBearer (read)

Fetch one changelog entry by UUID or slug. Mirrors get_changelog.

POST/api/v1/projects/:project/changelogBearer (write)

Publish a changelog entry. Optional translations ({ fi: { title, body_md } }). Mirrors publish_changelog.

PATCH/api/v1/projects/:project/changelog/:entryBearer (write)

Edit or publish a changelog entry. translations merge per locale (null removes one). Mirrors update_changelog.

GET/api/v1/projects/:project/docsBearer (read)

List doc pages (public, internal, or all). Mirrors list_docs.

GET/api/v1/projects/:project/docs/:pageIdBearer (read)

Fetch one doc page by UUID or slug (optional locale and version query params). Mirrors get_doc.

PUT/api/v1/projects/:project/docsBearer (write)

Create or update a doc page. Mirrors upsert_doc_page.

GET/api/v1/projects/:project/domainsBearer (read)

List custom domains for a project. Mirrors list_domains.

POST/api/v1/projects/:project/domainsBearer (write)

Add a custom domain (returns DNS verification hints). Mirrors add_domain.

POST/api/v1/projects/:project/domains/:domainId/verifyBearer (write)

Re-check domain DNS verification. Mirrors verify_domain.

POST/api/v1/projects/:project/ciBearer (write)

Record a CI run (GitHub Actions, GitLab, etc.). Mirrors report_ci_status.

GET/api/v1/projects/:project/huddle/sitesBearer (read)

List Huddle sites with open and resolved pin counts. Mirrors list_huddle_sites.

POST/api/v1/projects/:project/huddle/sitesBearer (write)

Register a site for on-page feedback; returns the install snippet. Mirrors create_huddle_site.

PATCH/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.

POST/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.

DELETE/api/v1/projects/:project/huddle/sites/:siteId/guest-links/:linkIdBearer (write)

Revoke a guest review link. Mirrors revoke_huddle_guest_link.

GET/api/v1/projects/:project/huddle/pinsBearer (read)

List pins, filtered by site_id, page_url, and state (open, resolved, all). Mirrors list_huddle_pins.

GET/api/v1/projects/:project/huddle/pins/:pinIdBearer (read)

Fetch a pin with its linked post and comment thread. Mirrors get_huddle_pin.

POST/api/v1/projects/:project/huddle/pins/:pinId/repliesBearer (write)

Post a staff reply on a pin (publish guard applies). Mirrors reply_huddle_pin.

POST/api/v1/projects/:project/huddle/pins/:pinId/resolveBearer (write)

Resolve a pin. Mirrors resolve_huddle_pin.

POST/api/v1/projects/:project/huddle/pins/:pinId/reopenBearer (write)

Reopen a resolved pin. Mirrors reopen_huddle_pin.

Create a project via REST
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
  }'
List roadmap posts via REST
curl "https://hollahoop.app/api/v1/projects/hollahoop/posts?status=planned&sort=top&limit=10" \
  -H "Authorization: Bearer hh_live_..."
List published Updates via REST
curl "https://hollahoop.app/api/v1/projects/hollahoop/changelog?status=published&limit=10" \
  -H "Authorization: Bearer hh_live_..."
Search a project via REST
curl "https://hollahoop.app/api/v1/projects/hollahoop/search?q=webhooks&kind=posts&limit=10" \
  -H "Authorization: Bearer hh_live_..."
Report CI status via REST
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.

huddle.pin.created payload
{
  "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/..." }
    }
  }
}
huddle.comment.created data
{
  "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 envelope
{
  "error": {
    "code": "not_found",
    "message": "No project 'acme-feedback' in org 'acme'"
  }
}
Rate-limited response (HTTP 429)
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."
  }
}
HTTPcodeWhen it happens
400invalid_jsonRequest body was not valid JSON.
401missing_token / invalid_token / revoked_tokenNo usable Bearer token was supplied.
403forbidden_scope / forbiddenThe key lacks the read or write scope this endpoint needs.
404not_foundThe project or resource is not in this organisation.
409slug_taken / conflictA project with that slug, or a Huddle site with that URL, already exists.
422invalid_input / invalid_slugBody or query parameters failed validation.
429rate_limitedPer-key rate limit exceeded. See retry-after.
500internal_errorUnexpected 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.

List projects
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "list_projects",
    "arguments": {}
  }
}
Publish an Update
{
  "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.

See MCP setup details →

Read

list_projects

List every project the API key can see (scoped to a single Hollahoop organisation).

list_boards

List feedback boards on a project (e.g. feature-requests, bugs, general).

list_statuses

List the post-status workflow for a project (Open / Planned / In progress / Shipped / Declined and any custom ones).

list_tags

List feedback tags for a project (slug, label, color).

list_surveys

List survey campaigns on a project (NPS / CSAT), including response_count and summary_score.

get_survey

Get one survey campaign with summary_score, response_count, and recent responses.

list_tickets

List support inbox tickets for a project. Spam is hidden unless status=spam. Most recently active first.

get_ticket

Get one support ticket and its messages (oldest first), scoped to a project.

list_posts

List feedback posts on a project, optionally filtered by board and/or status. Default sort is by vote count.

get_post

Fetch a single post by id, including its comments.

get_publish_guard

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.

check_publish

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

List comments on a feedback post, oldest first. Use limit and offset to paginate; get_post also returns comments inline.

list_changelog

List changelog / Updates entries for a project (drafts and published).

get_changelog

Fetch a single changelog / Updates entry by id or slug.

list_docs

List documentation pages for a project, grouped by section. Use the `visibility` filter to scope to public or internal pages.

get_doc

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.

search

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.

get_status_page

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_status_subscribers

List email subscribers who opted in on the public status page (newest first). Project members only; unsubscribe tokens are never returned.

list_domains

List custom hostnames for a project, including TXT challenge values for pending rows.

list_huddle_sites

List Huddle sites (customer websites that collect on-page feedback pins) for a project, with open and resolved pin counts and last activity.

list_huddle_approvals

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_pins

List Huddle feedback pins on a project, newest first. Filter by site_id, page_url, and state (open, resolved, or all; default open).

get_huddle_pin

Fetch one Huddle pin with its page, anchor, linked feedback post, and the comment thread on that post.

Write

create_project

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_project

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_tag

Create a feedback tag on a project.

delete_tag

Delete a feedback tag from a project. Removes the tag from all posts that used it.

set_post_tags

Replace the tags on a feedback post (full set). Pass an empty tag_ids array to clear.

create_survey

Create an NPS or CSAT survey campaign on a project. Returns the campaign id and public response URL.

update_survey

Update a survey campaign (activate/deactivate and/or edit title/question). At least one field required.

delete_survey

Permanently delete a survey campaign and all of its responses from a project.

create_ticket

Create a support ticket with an opening inbound message. Defaults to status=open, priority=normal.

update_ticket

Update a support ticket status, priority, and/or assignee. At least one field required.

reply_to_ticket

Add a message to a support ticket. Outbound replies move new/pending tickets to open.

create_post

Create a new feedback / roadmap post. Defaults to the feature-requests board and the project default status (Open).

set_publish_guard

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.

update_post

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_post_status

Update a post: roadmap status, pin/lock flags, and/or release estimate. Status moves accept feedback (open → planned), start work, or ship.

comment_on_post

Add a comment (or reply, via parent_id) to a post. The author is the user who created this API key.

create_comment

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.

add_vote

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.

publish_changelog

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.

update_changelog

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.

upsert_doc_page

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.

delete_status_subscriber

Remove a status-page email subscriber (same effect as the public unsubscribe link). Pending incident emails for that subscriber are dropped via cascade.

set_status_page_enabled

Enable or disable the public status page for a project (same as Settings → Status page).

create_status_incident

Open a status-page incident (starts in investigating). Does not require the public status page to be enabled.

add_status_incident_update

Post an update on a status-page incident and advance its lifecycle (investigating → identified → monitoring → resolved).

create_status_component

Add a component to the project status page.

update_status_component

Set the current operational status of a status-page component (for automation / CI hooks).

report_ci_status

Record a CI run (GitHub Actions, GitLab CI, or generic) against a project. Same payload as POST /api/integrations/ci/webhook.

add_domain

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.

verify_domain

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.

create_huddle_site

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.

update_huddle_site

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.

reply_huddle_pin

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.

resolve_huddle_pin

Mark a Huddle pin as resolved. It disappears from the live site and fires huddle.pin.resolved.

reopen_huddle_pin

Reopen a resolved Huddle pin so it shows on the live site again. Fires huddle.pin.reopened.

create_huddle_guest_link

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_huddle_guest_link

Revoke a Huddle guest review link right away. Revoking twice is a no-op.