{"openapi":"3.1.0","info":{"title":"Hollahoop REST API","version":"1","description":"Read and write your feedback, roadmap, and changelog programmatically. These REST endpoints mirror the Hollahoop MCP tools one-to-one and share the same Bearer token auth, so you can use whichever surface fits your stack. Responses are JSON; errors use a consistent { error: { code, message } } envelope.","contact":{"name":"Hollahoop","url":"https://hollahoop.app/api-docs"}},"servers":[{"url":"https://hollahoop.app/api/v1","description":"Production"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"System","description":"Unauthenticated liveness and readiness probes."},{"name":"Projects","description":"List and create projects in the token organisation."},{"name":"Posts","description":"Read feedback and roadmap posts."},{"name":"Statuses","description":"Read the roadmap status workflow."},{"name":"Changelog","description":"Read changelog / Updates entries."}],"paths":{"/health":{"get":{"tags":["System"],"summary":"Liveness probe","description":"Always returns 200 while the app is serving traffic. No auth required.","operationId":"getHealth","security":[],"responses":{"200":{"description":"Service is live.","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"version":{"type":"string","example":"1"},"ts":{"type":"string","format":"date-time"}},"required":["ok","version","ts"]}}}}}}},"/ready":{"get":{"tags":["System"],"summary":"Readiness probe","description":"Checks downstream dependencies (PostgREST, optional pooler, Redis). Returns 200 when ready and 503 when degraded. No auth required unless READINESS_PROBE_TOKEN is configured for the deployment.","operationId":"getReady","security":[],"responses":{"200":{"description":"All dependency checks passed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Readiness"}}}},"503":{"description":"One or more dependency checks failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Readiness"}}}}}}},"/projects":{"get":{"tags":["Projects"],"summary":"List projects","description":"List every project the API key can see (scoped to a single Hollahoop organisation).","operationId":"listProjects","security":[{"bearerAuth":["read"]}],"responses":{"200":{"description":"Projects visible to this token.","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}},"post":{"tags":["Projects"],"summary":"Create a project","description":"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.","operationId":"createProject","security":[{"bearerAuth":["write"]}],"requestBody":{"required":true,"description":"Project attributes. Boards, statuses, tags, and a docs section are auto-seeded.","content":{"application/json":{"schema":{"type":"object","properties":{"slug":{"type":"string","minLength":3,"maxLength":40,"pattern":"^[a-z0-9][a-z0-9-]{1,38}[a-z0-9]$","description":"URL slug - lives at /<slug> on the public site."},"name":{"type":"string","minLength":2,"maxLength":80,"description":"Display name shown in the UI."},"tagline":{"description":"Short marketing line for the public board.","type":"string","maxLength":120},"description":{"description":"Optional longer description.","type":"string","maxLength":2000},"accent_color":{"description":"Hex colour for chips / accents, e.g. #6366f1.","type":"string","pattern":"^#[0-9a-fA-F]{6}$"},"is_public":{"description":"Make the public board visible - defaults to true.","type":"boolean"}},"required":["slug","name"]}}}},"responses":{"201":{"description":"The created project plus its public board URL.","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/projects/{projectSlug}/posts":{"get":{"tags":["Posts"],"summary":"List posts","description":"List feedback posts on a project, optionally filtered by board and/or status. Default sort is by vote count.","operationId":"listPosts","security":[{"bearerAuth":["read"]}],"parameters":[{"name":"projectSlug","in":"path","required":true,"description":"Slug of a project inside the token organisation.","schema":{"type":"string","minLength":1}},{"name":"board","in":"query","required":false,"description":"Optional board slug (e.g. feature-requests)","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Optional status key (e.g. open, planned)","schema":{"type":"string"}},{"name":"post_type","in":"query","required":false,"description":"Optional post type filter","schema":{"type":"string","enum":["feature","bug","tweak"]}},{"name":"sort","in":"query","required":false,"schema":{"default":"top","type":"string","enum":["top","new","oldest"]}},{"name":"limit","in":"query","required":false,"schema":{"default":25,"type":"integer","minimum":1,"maximum":100}},{"name":"offset","in":"query","required":false,"schema":{"default":0,"type":"integer","minimum":0,"maximum":9007199254740991}}],"responses":{"200":{"description":"Matching posts with the applied filter echoed back.","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/projects/{projectSlug}/posts/{postId}":{"get":{"tags":["Posts"],"summary":"Get a post","description":"Fetch a single post by id, including its comments.","operationId":"getPost","security":[{"bearerAuth":["read"]}],"parameters":[{"name":"projectSlug","in":"path","required":true,"description":"Slug of a project inside the token organisation.","schema":{"type":"string","minLength":1}},{"name":"postId","in":"path","required":true,"description":"UUID of the post.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The post plus its comments.","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/projects/{projectSlug}/statuses":{"get":{"tags":["Statuses"],"summary":"List statuses","description":"List the post-status workflow for a project (Open / Planned / In progress / Shipped / Declined and any custom ones).","operationId":"listStatuses","security":[{"bearerAuth":["read"]}],"parameters":[{"name":"projectSlug","in":"path","required":true,"description":"Slug of a project inside the token organisation.","schema":{"type":"string","minLength":1}}],"responses":{"200":{"description":"The roadmap status workflow for the project.","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/projects/{projectSlug}/changelog":{"get":{"tags":["Changelog"],"summary":"List changelog entries","description":"List changelog / Updates entries for a project (drafts and published).","operationId":"listChangelog","security":[{"bearerAuth":["read"]}],"parameters":[{"name":"projectSlug","in":"path","required":true,"description":"Slug of a project inside the token organisation.","schema":{"type":"string","minLength":1}},{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["draft","published"]}},{"name":"limit","in":"query","required":false,"schema":{"default":25,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Changelog / Updates entries for the project.","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/ValidationError"},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Organisation-scoped API key. Create one from your project settings and send it as `Authorization: Bearer hh_live_...`. The read scope covers GET endpoints; the write scope is required for POST."}},"headers":{"RateLimitLimit":{"description":"Request ceiling for this scope in the current window.","schema":{"type":"integer"}},"RateLimitRemaining":{"description":"Requests remaining in the current window.","schema":{"type":"integer"}},"RateLimitReset":{"description":"Unix epoch (seconds) when the current window resets.","schema":{"type":"integer"}},"RetryAfter":{"description":"Seconds to wait before retrying after a 429.","schema":{"type":"integer"}}},"schemas":{"Error":{"type":"object","description":"Consistent error envelope returned for every non-2xx response.","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable, machine-readable error code.","example":"not_found"},"message":{"type":"string","description":"Human-readable explanation. Safe to log, not for end users."}},"required":["code","message"]}},"required":["error"]},"Readiness":{"type":"object","properties":{"ready":{"type":"boolean"},"status":{"type":"string","example":"ok"},"checks":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"ok":{"type":"boolean"},"detail":{"type":"string"}}}},"ts":{"type":"string","format":"date-time"}},"required":["ready","status","ts"]}},"responses":{"Unauthorized":{"description":"Missing, malformed, or revoked API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The API key lacks the scope required for this endpoint.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"The requested project or resource does not exist in this organisation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"A resource with the same unique key already exists (e.g. project slug).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ValidationError":{"description":"The request body or query parameters failed validation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Per-key rate limit exceeded (120 read/min, 30 write/min).","headers":{"x-ratelimit-limit":{"$ref":"#/components/headers/RateLimitLimit"},"x-ratelimit-remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"x-ratelimit-reset":{"$ref":"#/components/headers/RateLimitReset"},"retry-after":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}