{"openapi":"3.0.3","info":{"title":"ProductLift API","description":"You can use our ProductLift's API to smoothly integrate ProductLift with other services. AI agents can consume the machine-readable OpenAPI spec at https://developer.productlift.dev/docs/api.yaml.","version":"1.0.0","contact":{"name":"ProductLift support","url":"https://www.productlift.dev/contact/"},"termsOfService":"https://www.productlift.dev/legal/terms/"},"servers":[{"url":"https://{portal}","description":"Your ProductLift portal (own subdomain or custom domain)","variables":{"portal":{"default":"your-portal.productlift.dev","description":"Hostname of the portal the API key belongs to"}}}],"tags":[{"name":"Anonymous Votes","description":"\nManage anonymous votes on posts and comments."},{"name":"Categories","description":"\nCategories (e.g. Bug, Integration) can be used to organize and group posts. Categories are visible for your users.\nPosts can have multiple categories."},{"name":"Comments","description":"\nComments can be added to posts. Each post can have multiple comments."},{"name":"Endpoints","description":""},{"name":"Groups","description":"\nGroup management lets you organize users, control access, and customize experiences. Use it for focus groups, beta tests, customer segments, or team organization. After creating groups, you can set tab visibility for specific groups and choose who can access them."},{"name":"Knowledgebase Link Health","description":"\nScan every http(s) link in every KB article on a tab, flag 4xx/5xx and\ndead hosts. URL results are cached in Redis for 6h and shared across\nposts + portals. Per-post rollup lives in the kb_link_health table."},{"name":"Knowledgebase Sync","description":"\nHandles syncing local markdown files with the knowledgebase."},{"name":"Moderation","description":"\nHandles operations related to post and comment moderation including listing pending and rejected items, approving, and rejecting them."},{"name":"Portal","description":"\nYou should see a portal as a single project for your product or service. It can consist of roadmaps, changelogs, voting boards, etc."},{"name":"Posts","description":"\nManage posts."},{"name":"Product Vision","description":"\nHandles operations related to product vision including viewing, creating, updating, deleting, and generating vision boards."},{"name":"Redirects","description":""},{"name":"Sections","description":"\nManages operations related to sections within tabs, including creation, updating, deletion, and reordering."},{"name":"Statuses","description":"\nStatuses can be used to track the status of posts. Each post gets one status."},{"name":"Tabs","description":"\nAPI endpoints for managing tabs."},{"name":"Tags","description":"\nTags can be used to label and categorize posts. Tags are not visible for your users.\nPosts can have multiple tags."},{"name":"Users","description":"\nUsers can create posts, votes, and comments. You can create and manage users from the API."},{"name":"Votes","description":"\nManage votes on your posts."},{"name":"Widgets","description":"\nAPI endpoints for managing widgets in your portal."}],"components":{"securitySchemes":{"apiKeyBearer":{"type":"http","scheme":"bearer","description":"Portal API key created under Customize > API & Webhooks. Send as \"Authorization: Bearer <key>\". Keys can be read-only and can carry an expiry. Requires the Pro plan or higher. See https://www.productlift.dev/auth.md","bearerFormat":"API key"},"oauth2":{"type":"oauth2","description":"OAuth 2.1 (PKCE S256, dynamic client registration) used by the per-portal ProductLift MCP server at https://{portal}/mcp. Authorization server metadata: https://app.productlift.dev/.well-known/oauth-authorization-server; protected-resource metadata is served per portal at https://{portal}/.well-known/oauth-protected-resource","flows":{"authorizationCode":{"authorizationUrl":"https://app.productlift.dev/oauth/authorize","tokenUrl":"https://app.productlift.dev/oauth/token","refreshUrl":"https://app.productlift.dev/oauth/token","scopes":{"mcp":"Access the portal through the MCP server: read and write posts, comments, votes, users and statuses on behalf of the authorizing admin"}}}}}},"security":[{"apiKeyBearer":[]}],"paths":{"/api/v1/posts/{key}/anonymous_votes/{anonymous_id}":{"post":{"summary":"Vote anonymously","operationId":"voteAnonymously","description":"Add an anonymous vote to a post or comment.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"created_at":"2026-08-19T12:15:06.000000Z","voter":null}},"properties":{"data":{"type":"object","properties":{"created_at":{"type":"string","example":"2026-08-19T12:15:06.000000Z"},"voter":{"type":"string","example":null,"nullable":true}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model ..."},"properties":{"message":{"type":"string","example":"No query results for model ..."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"oneOf":[{"description":"","type":"object","example":{"message":"Item is not voteable."},"properties":{"message":{"type":"string","example":"Item is not voteable."}}},{"description":"","type":"object","example":{"message":"Anonymous user has already voted for this item."},"properties":{"message":{"type":"string","example":"Anonymous user has already voted for this item."}}}]}}}}},"tags":["Anonymous Votes"]},"delete":{"summary":"Revoke anonymous vote","operationId":"revokeAnonymousVote","description":"Revoke an anonymous vote for a post or comment.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":[],"properties":[]}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model ..."},"properties":{"message":{"type":"string","example":"No query results for model ..."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"oneOf":[{"description":"","type":"object","example":{"message":"Item is not voteable."},"properties":{"message":{"type":"string","example":"Item is not voteable."}}},{"description":"","type":"object","example":{"message":"Anonymous user has not voted yet."},"properties":{"message":{"type":"string","example":"Anonymous user has not voted yet."}}}]}}}}},"tags":["Anonymous Votes"]},"parameters":[{"in":"path","name":"key","description":"The ID of the post or comment.","example":"0UFQQh","required":true,"schema":{"type":"string"}},{"in":"path","name":"anonymous_id","description":"The ID of the anonymous.","example":"architecto","required":true,"schema":{"type":"string"}},{"in":"path","name":"anonymousId","description":"The MD5 hash representing the anonymous user.","example":"d41d8cd98f00b204e9800998ecf8427e","required":true,"schema":{"type":"string"}}]},"/api/v1/categories":{"get":{"summary":"List categories","operationId":"listCategories","description":"Lists all categories of your portal. Categories are the user-visible taxonomy\nfor organizing posts (compare with Tags, which are admin-only internal labels).\nA post can belong to multiple categories.\n\nNOTE: results are scoped to the current portal automatically via the model's\nglobal scope. No filtering by tab is needed; categories are portal-wide.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":null,"name":"nostrum","color":"#CBAACB","sort_order":null},{"id":null,"name":"odit","color":"#FFCCB6","sort_order":null}]},"properties":{"data":{"type":"array","example":[{"id":null,"name":"nostrum","color":"#CBAACB","sort_order":null},{"id":null,"name":"odit","color":"#FFCCB6","sort_order":null}],"items":{"type":"object","properties":{"id":{"type":"string","example":null,"nullable":true},"name":{"type":"string","example":"nostrum"},"color":{"type":"string","example":"#CBAACB"},"sort_order":{"type":"string","example":null,"nullable":true}}}}}}}}}},"tags":["Categories"],"x-mcp":true,"x-mcp-name":"categories.list"},"post":{"summary":"Create category","operationId":"createCategory","description":"Creates a new category in your portal. Categories are user-visible on the board\n(surfaced as filter chips, post labels, etc.), so name them for end-user reading.\n\nNOTE: category names must be unique per portal (case-sensitive at DB level).\nNOTE: to assign a category to a post, pass `category_id` on POST /posts or PATCH /posts/{key}.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":null,"name":"nostrum","color":"#CBAACB","sort_order":null}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":null,"nullable":true},"name":{"type":"string","example":"nostrum"},"color":{"type":"string","example":"#CBAACB"},"sort_order":{"type":"string","example":null,"nullable":true}}}}}}}},"422":{"description":"","content":{"text/plain":{"schema":{"type":"string","example":"{\n \"message\": \"The given data was invalid.\",\n \"errors\": {\n \"name\": [\n     \"The name has already been taken.\"\n ]\n}"}}}}},"tags":["Categories"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The category name (2-60 chars, unique within the portal).","example":"Integration"}},"required":["name"]}}}},"x-mcp":true,"x-mcp-name":"categories.create"}},"/api/v1/categories/{id}":{"delete":{"summary":"Delete category","operationId":"deleteCategory","description":"Deletes a specified category.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Category deleted successfully"},"properties":{"message":{"type":"string","example":"Category deleted successfully"}}}}}},"403":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"You do not have permission to delete this category"},"properties":{"message":{"type":"string","example":"You do not have permission to delete this category"}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Category not found"},"properties":{"message":{"type":"string","example":"Category not found"}}}}}}},"tags":["Categories"]},"patch":{"summary":"Update category","operationId":"updateCategory","description":"Updates the details of a category. This endpoint is PATCH (not PUT) — send only\nthe fields you want to change, but note that both `name` AND `color` are required\non every call by the current validation.\n\nCRITICAL: HTTP verb is PATCH /api/v1/categories/{id}, not PUT. A PUT will 405.\nNOTE: renaming a category is visible to end users immediately; no cache flush needed.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":null,"name":"mollitia","color":"#A2E1DB","sort_order":null}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":null,"nullable":true},"name":{"type":"string","example":"mollitia"},"color":{"type":"string","example":"#A2E1DB"},"sort_order":{"type":"string","example":null,"nullable":true}}}}}}}},"403":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"You do not have permission to update this category."},"properties":{"message":{"type":"string","example":"You do not have permission to update this category."}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Category not found."},"properties":{"message":{"type":"string","example":"Category not found."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The given data was invalid.","errors":{"name":["The name has already been taken.","The name must be at least 2 characters.","The name may not be greater than 60 characters."],"color":["The color format is invalid. It should be a valid hex color code (e.g., #FF5733)."]}},"properties":{"message":{"type":"string","example":"The given data was invalid."},"errors":{"type":"object","properties":{"name":{"type":"array","example":["The name has already been taken.","The name must be at least 2 characters.","The name may not be greater than 60 characters."],"items":{"type":"string"}},"color":{"type":"array","example":["The color format is invalid. It should be a valid hex color code (e.g., #FF5733)."],"items":{"type":"string"}}}}}}}}}},"tags":["Categories"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The category name (2-60 chars, unique within the portal).","example":"Integration"},"color":{"type":"string","description":"Hex color code, format `#RRGGBB` (6 hex digits, leading `#`).","example":"#FF5733"}},"required":["name","color"]}}}},"x-mcp":true,"x-mcp-name":"categories.update"},"parameters":[{"in":"path","name":"id","description":"The ID of the category.","example":16,"required":true,"schema":{"type":"integer"}}]},"/api/v1/posts/{key}/comments":{"get":{"summary":"List comments","operationId":"listComments","description":"Retrieves all comments for a specific post, including replies (nested via parent_id).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":null,"comment":"This is a must-have!","formatted_comment":"<p>This is a must-have!</p>","author":{"id":"604e583a-b3a9-4760-8cb2-aa7f9fce474c","database_id":4687,"name":"Lucienne","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4687","avatar_small_url":"https://www.gravatar.com/avatar/e1de144931d3625b1b2d54ccd99c7ff7?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Lucienne/60/ECEAE4/56544e"},"pinned_to_top":false,"tagged_for_changelog":false,"is_ai":false,"moderation_status":null,"parent_id":null,"votes_count":0,"user_has_voted":false,"can_edit":false,"can_delete":false,"created_at":null,"updated_at":null,"url":"{YOUR-PORTAL-URL}/p/example-slug-123456#comment_123","source_locale":null,"is_translated":false,"original_html":null},{"id":null,"comment":"This is a must-have!","formatted_comment":"<p>This is a must-have!</p>","author":{"id":"23387028-227f-4cd2-905d-f3147b07e6da","database_id":4689,"name":"Modesta","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4689","avatar_small_url":"https://www.gravatar.com/avatar/9925ebc652c5e6056010c9b8f19d3d75?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Modesta/60/CBAACB/351435"},"pinned_to_top":false,"tagged_for_changelog":false,"is_ai":false,"moderation_status":null,"parent_id":null,"votes_count":0,"user_has_voted":false,"can_edit":false,"can_delete":false,"created_at":null,"updated_at":null,"url":"{YOUR-PORTAL-URL}/p/example-slug-123456#comment_123","source_locale":null,"is_translated":false,"original_html":null}]},"properties":{"data":{"type":"array","example":[{"id":null,"comment":"This is a must-have!","formatted_comment":"<p>This is a must-have!</p>","author":{"id":"604e583a-b3a9-4760-8cb2-aa7f9fce474c","database_id":4687,"name":"Lucienne","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4687","avatar_small_url":"https://www.gravatar.com/avatar/e1de144931d3625b1b2d54ccd99c7ff7?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Lucienne/60/ECEAE4/56544e"},"pinned_to_top":false,"tagged_for_changelog":false,"is_ai":false,"moderation_status":null,"parent_id":null,"votes_count":0,"user_has_voted":false,"can_edit":false,"can_delete":false,"created_at":null,"updated_at":null,"url":"{YOUR-PORTAL-URL}/p/example-slug-123456#comment_123","source_locale":null,"is_translated":false,"original_html":null},{"id":null,"comment":"This is a must-have!","formatted_comment":"<p>This is a must-have!</p>","author":{"id":"23387028-227f-4cd2-905d-f3147b07e6da","database_id":4689,"name":"Modesta","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4689","avatar_small_url":"https://www.gravatar.com/avatar/9925ebc652c5e6056010c9b8f19d3d75?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Modesta/60/CBAACB/351435"},"pinned_to_top":false,"tagged_for_changelog":false,"is_ai":false,"moderation_status":null,"parent_id":null,"votes_count":0,"user_has_voted":false,"can_edit":false,"can_delete":false,"created_at":null,"updated_at":null,"url":"{YOUR-PORTAL-URL}/p/example-slug-123456#comment_123","source_locale":null,"is_translated":false,"original_html":null}],"items":{"type":"object","properties":{"id":{"type":"string","example":null,"nullable":true},"comment":{"type":"string","example":"This is a must-have!"},"formatted_comment":{"type":"string","example":"<p>This is a must-have!</p>"},"author":{"type":"object","properties":{"id":{"type":"string","example":"604e583a-b3a9-4760-8cb2-aa7f9fce474c"},"database_id":{"type":"integer","example":4687},"name":{"type":"string","example":"Lucienne"},"role":{"type":"string","example":"member"},"profile_url":{"type":"string","example":"{YOUR-PORTAL-URL}/user/profile/4687"},"avatar_small_url":{"type":"string","example":"https://www.gravatar.com/avatar/e1de144931d3625b1b2d54ccd99c7ff7?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Lucienne/60/ECEAE4/56544e"}}},"pinned_to_top":{"type":"boolean","example":false},"tagged_for_changelog":{"type":"boolean","example":false},"is_ai":{"type":"boolean","example":false},"moderation_status":{"type":"string","example":null,"nullable":true},"parent_id":{"type":"string","example":null,"nullable":true},"votes_count":{"type":"integer","example":0},"user_has_voted":{"type":"boolean","example":false},"can_edit":{"type":"boolean","example":false},"can_delete":{"type":"boolean","example":false},"created_at":{"type":"string","example":null,"nullable":true},"updated_at":{"type":"string","example":null,"nullable":true},"url":{"type":"string","example":"{YOUR-PORTAL-URL}/p/example-slug-123456#comment_123"},"source_locale":{"type":"string","example":null,"nullable":true},"is_translated":{"type":"boolean","example":false},"original_html":{"type":"string","example":null,"nullable":true}}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Post] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Post] ..."}}}}}}},"tags":["Comments"],"x-mcp":true,"x-mcp-name":"comments.list"},"post":{"summary":"Create comment","operationId":"createComment","description":"Adds a new comment to a specific post, attributed to the user identified by `user_id`.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":null,"comment":"Nice! This sounds good :)","formatted_comment":"<p>Nice! This sounds good 😀</p>","author":{"id":"0f517ca9-b08a-420b-a2ec-ac51300359a9","database_id":4691,"name":"Alayna","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4691","avatar_small_url":"https://www.gravatar.com/avatar/da35c733747605fb1465f598f747e416?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Alayna/60/FCB9AA/662314"},"pinned_to_top":false,"tagged_for_changelog":false,"is_ai":false,"moderation_status":null,"parent_id":null,"votes_count":0,"user_has_voted":false,"can_edit":false,"can_delete":false,"created_at":null,"updated_at":null,"url":"{YOUR-PORTAL-URL}/p/example-slug-123456#comment_123","source_locale":null,"is_translated":false,"original_html":null}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":null,"nullable":true},"comment":{"type":"string","example":"Nice! This sounds good :)"},"formatted_comment":{"type":"string","example":"<p>Nice! This sounds good 😀</p>"},"author":{"type":"object","properties":{"id":{"type":"string","example":"0f517ca9-b08a-420b-a2ec-ac51300359a9"},"database_id":{"type":"integer","example":4691},"name":{"type":"string","example":"Alayna"},"role":{"type":"string","example":"member"},"profile_url":{"type":"string","example":"{YOUR-PORTAL-URL}/user/profile/4691"},"avatar_small_url":{"type":"string","example":"https://www.gravatar.com/avatar/da35c733747605fb1465f598f747e416?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Alayna/60/FCB9AA/662314"}}},"pinned_to_top":{"type":"boolean","example":false},"tagged_for_changelog":{"type":"boolean","example":false},"is_ai":{"type":"boolean","example":false},"moderation_status":{"type":"string","example":null,"nullable":true},"parent_id":{"type":"string","example":null,"nullable":true},"votes_count":{"type":"integer","example":0},"user_has_voted":{"type":"boolean","example":false},"can_edit":{"type":"boolean","example":false},"can_delete":{"type":"boolean","example":false},"created_at":{"type":"string","example":null,"nullable":true},"updated_at":{"type":"string","example":null,"nullable":true},"url":{"type":"string","example":"{YOUR-PORTAL-URL}/p/example-slug-123456#comment_123"},"source_locale":{"type":"string","example":null,"nullable":true},"is_translated":{"type":"boolean","example":false},"original_html":{"type":"string","example":null,"nullable":true}}}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The given data was invalid.","errors":{"comment":["The comment field is required."]}},"properties":{"message":{"type":"string","example":"The given data was invalid."},"errors":{"type":"object","properties":{"comment":{"type":"array","example":["The comment field is required."],"items":{"type":"string"}}}}}}}}}},"tags":["Comments"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","description":"The UUID (User.key) of the user the comment is attributed to.","example":"8cbfbfd6-ecc3-4ec3-8b31-ea76b11c1f88"},"comment":{"type":"string","description":"The content of the comment (3-6000 chars). HTML for wysiwyg portals, markdown for markdown portals.","example":"This is a great idea, we should prioritize it."},"parent_id":{"type":"integer","description":"The ID of the parent comment if this is a reply.","example":1,"nullable":true},"tagged_for_changelog":{"type":"boolean","description":"Tag this comment as the post's public changelog description. Only one comment per post can hold this tag; setting it here removes it from any previous comment.","example":false,"nullable":true},"pinned_to_top":{"type":"boolean","description":"Pin this comment to the top of the comments list.","example":false,"nullable":true},"created_at":{"type":"string","description":"Custom creation timestamp (useful for backfill/imports).","example":"2025-01-15 14:30:00","nullable":true},"silent":{"type":"boolean","description":"If true, skips ALL notifications and events (no emails, no CommentCreated event). Default false.","example":false,"nullable":true}},"required":["user_id","comment"]}}}},"x-mcp":true,"x-mcp-name":"comments.create"},"parameters":[{"in":"path","name":"key","description":"The portal_post_key of the post (short string like \"ROXDWP\", NOT the numeric id).","example":"ROXDWP","required":true,"schema":{"type":"string"}}]},"/api/v1/comments/{id}":{"put":{"summary":"Update comment","operationId":"updateComment","description":"Updates the details of a comment.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":null,"comment":"Yes 😍","formatted_comment":"<p>Yes 😍</p>","author":{"id":"f221d6d6-0e48-4c50-8d2e-e5d697ac285b","database_id":4693,"name":"Ferne","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4693","avatar_small_url":"https://www.gravatar.com/avatar/78bab266e3eebbfbe4087fe9d8230a0a?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Ferne/60/FFCCB6/693620"},"pinned_to_top":false,"tagged_for_changelog":false,"is_ai":false,"moderation_status":null,"parent_id":null,"votes_count":0,"user_has_voted":false,"can_edit":false,"can_delete":false,"created_at":null,"updated_at":null,"url":"{YOUR-PORTAL-URL}/p/example-slug-123456#comment_123","source_locale":null,"is_translated":false,"original_html":null}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":null,"nullable":true},"comment":{"type":"string","example":"Yes 😍"},"formatted_comment":{"type":"string","example":"<p>Yes 😍</p>"},"author":{"type":"object","properties":{"id":{"type":"string","example":"f221d6d6-0e48-4c50-8d2e-e5d697ac285b"},"database_id":{"type":"integer","example":4693},"name":{"type":"string","example":"Ferne"},"role":{"type":"string","example":"member"},"profile_url":{"type":"string","example":"{YOUR-PORTAL-URL}/user/profile/4693"},"avatar_small_url":{"type":"string","example":"https://www.gravatar.com/avatar/78bab266e3eebbfbe4087fe9d8230a0a?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Ferne/60/FFCCB6/693620"}}},"pinned_to_top":{"type":"boolean","example":false},"tagged_for_changelog":{"type":"boolean","example":false},"is_ai":{"type":"boolean","example":false},"moderation_status":{"type":"string","example":null,"nullable":true},"parent_id":{"type":"string","example":null,"nullable":true},"votes_count":{"type":"integer","example":0},"user_has_voted":{"type":"boolean","example":false},"can_edit":{"type":"boolean","example":false},"can_delete":{"type":"boolean","example":false},"created_at":{"type":"string","example":null,"nullable":true},"updated_at":{"type":"string","example":null,"nullable":true},"url":{"type":"string","example":"{YOUR-PORTAL-URL}/p/example-slug-123456#comment_123"},"source_locale":{"type":"string","example":null,"nullable":true},"is_translated":{"type":"boolean","example":false},"original_html":{"type":"string","example":null,"nullable":true}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Comment] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Comment] ..."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The given data was invalid.","errors":{"comment":["The comment field is required."]}},"properties":{"message":{"type":"string","example":"The given data was invalid."},"errors":{"type":"object","properties":{"comment":{"type":"array","example":["The comment field is required."],"items":{"type":"string"}}}}}}}}}},"tags":["Comments"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"comment":{"type":"string","description":"The content of the comment (3-6000 chars). HTML or markdown depending on portal editor_type.","example":"Updated comment with more detail."},"parent_id":{"type":"integer","description":"The ID of the parent comment if this is a reply.","example":1,"nullable":true},"tagged_for_changelog":{"type":"boolean","description":"Tag this comment as the post's public changelog description. Only one comment per post can hold this tag.","example":false,"nullable":true},"pinned_to_top":{"type":"boolean","description":"Pin this comment to the top of the comments list.","example":false,"nullable":true},"created_at":{"type":"string","description":"Custom creation timestamp.","example":"2025-01-15 14:30:00","nullable":true}},"required":["comment"]}}}},"x-mcp":true,"x-mcp-name":"comments.update"},"delete":{"summary":"Delete comment","operationId":"deleteComment","description":"Deletes a specified comment and all of its direct child replies (cascading one level).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":[],"properties":[]}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Comment] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Comment] ..."}}}}}}},"tags":["Comments"],"x-mcp":true,"x-mcp-name":"comments.delete"},"parameters":[{"in":"path","name":"id","description":"The numeric ID of the comment.","example":16,"required":true,"schema":{"type":"integer"}}]},"/api/v1/posts/{key}/references":{"post":{"summary":"Create a reference for a post.","operationId":"createAReferenceForAPost","description":"","parameters":[],"responses":[],"tags":["Endpoints"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reference":{"type":"string","description":"Must be at least 1 character. Must not be greater than 6000 characters.","example":"b"}},"required":["reference"]}}}}},"parameters":[{"in":"path","name":"key","description":"","example":33,"required":true,"schema":{"type":"integer"}}]},"/api/v1/references/{id}":{"delete":{"summary":"Delete a reference.","operationId":"deleteAReference","description":"","parameters":[],"responses":[],"tags":["Endpoints"]},"parameters":[{"in":"path","name":"id","description":"The ID of the reference.","example":"564","required":true,"schema":{"type":"string"}}]},"/api/v1/groups":{"get":{"summary":"List groups","operationId":"listGroups","description":"Lists all groups on the current portal.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":"43a2f85f-02ec-4d54-90a1-514fe4b4d43e","portal_id":1025,"name":"Aut","description":"Quidem nostrum qui commodi incidunt iure odit.","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"},{"id":"8accedcb-6916-4de2-9a3c-7b2451665865","portal_id":1026,"name":"Autem","description":"Consequatur aut dolores enim non facere tempora.","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"}]},"properties":{"data":{"type":"array","example":[{"id":"43a2f85f-02ec-4d54-90a1-514fe4b4d43e","portal_id":1025,"name":"Aut","description":"Quidem nostrum qui commodi incidunt iure odit.","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"},{"id":"8accedcb-6916-4de2-9a3c-7b2451665865","portal_id":1026,"name":"Autem","description":"Consequatur aut dolores enim non facere tempora.","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"}],"items":{"type":"object","properties":{"id":{"type":"string","example":"43a2f85f-02ec-4d54-90a1-514fe4b4d43e"},"portal_id":{"type":"integer","example":1025},"name":{"type":"string","example":"Aut"},"description":{"type":"string","example":"Quidem nostrum qui commodi incidunt iure odit."},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"updated_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"}}}}}}}}}},"tags":["Groups"],"x-mcp":true,"x-mcp-name":"groups.list"},"post":{"summary":"Create group","operationId":"createGroup","description":"Creates a new group.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"974fd55f-5453-4f31-b39a-fb57866f9f3d","portal_id":1027,"name":"Fugiat","description":"Nihil accusantium harum mollitia modi deserunt.","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"974fd55f-5453-4f31-b39a-fb57866f9f3d"},"portal_id":{"type":"integer","example":1027},"name":{"type":"string","example":"Fugiat"},"description":{"type":"string","example":"Nihil accusantium harum mollitia modi deserunt."},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"updated_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"}}}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The given data was invalid.","errors":{"name":["The name has already been taken."]}},"properties":{"message":{"type":"string","example":"The given data was invalid."},"errors":{"type":"object","properties":{"name":{"type":"array","example":["The name has already been taken."],"items":{"type":"string"}}}}}}}}}},"tags":["Groups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The group name (2-60 chars, must be unique).","example":"Beta Testers"},"description":{"type":"string","description":"Optional group description.","example":"Users participating in beta testing.","nullable":true}},"required":["name"]}}}}}},"/api/v1/groups/{id}":{"get":{"summary":"Show group","operationId":"showGroup","description":"Displays details of a specific group.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"e340a8b7-1dda-4fca-9904-487e2ffa2ea9","portal_id":1028,"name":"Aut","description":"Quidem nostrum qui commodi incidunt iure odit.","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"e340a8b7-1dda-4fca-9904-487e2ffa2ea9"},"portal_id":{"type":"integer","example":1028},"name":{"type":"string","example":"Aut"},"description":{"type":"string","example":"Quidem nostrum qui commodi incidunt iure odit."},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"updated_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Group] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Group] ..."}}}}}}},"tags":["Groups"]},"put":{"summary":"Update group","operationId":"updateGroup","description":"Updates the details of a group.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"015f961c-ee50-45fb-b423-12a3850fdc0b","portal_id":1029,"name":"Fugiat","description":"Nihil accusantium harum mollitia modi deserunt.","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"015f961c-ee50-45fb-b423-12a3850fdc0b"},"portal_id":{"type":"integer","example":1029},"name":{"type":"string","example":"Fugiat"},"description":{"type":"string","example":"Nihil accusantium harum mollitia modi deserunt."},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"updated_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Group] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Group] ..."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The given data was invalid.","errors":{"name":["The name has already been taken."]}},"properties":{"message":{"type":"string","example":"The given data was invalid."},"errors":{"type":"object","properties":{"name":{"type":"array","example":["The name has already been taken."],"items":{"type":"string"}}}}}}}}}},"tags":["Groups"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The group name (2-60 chars, must be unique).","example":"Beta Testers"},"description":{"type":"string","description":"Optional group description.","example":"Updated description for beta testers group.","nullable":true}}}}}}},"delete":{"summary":"Delete group","operationId":"deleteGroup","description":"Deletes a specified group. Group memberships are removed but member user accounts are\npreserved. Boards that reference this group via `visibility='groups'` will no longer\nexpose posts to (now-former) members of this group.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Group deleted successfully"},"properties":{"message":{"type":"string","example":"Group deleted successfully"}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Group] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Group] ..."}}}}}}},"tags":["Groups"],"x-mcp":true,"x-mcp-name":"groups.delete"},"parameters":[{"in":"path","name":"id","description":"The ID of the group.","example":16,"required":true,"schema":{"type":"integer"}}]},"/api/v1/groups/{group}/users":{"post":{"summary":"Add user to group","operationId":"addUserToGroup","description":"Adds a user to the specified group. Idempotent-safe: returns 422 if the user is already a member (no duplicate row created).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"User added to group successfully"},"properties":{"message":{"type":"string","example":"User added to group successfully"}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Group or User not found"},"properties":{"message":{"type":"string","example":"Group or User not found"}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"User is already in the group"},"properties":{"message":{"type":"string","example":"User is already in the group"}}}}}}},"tags":["Groups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","description":"The user's UUID `key` (preferred) or integer `id`.","example":"8cbfbfd6-ecc3-4ec3-8b31-ea76b11c1f88"}},"required":["user_id"]}}}},"x-mcp":true,"x-mcp-name":"groups.addUser"},"delete":{"summary":"Remove user from group","operationId":"removeUserFromGroup","description":"Removes a user from the specified group.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"User removed from group successfully"},"properties":{"message":{"type":"string","example":"User removed from group successfully"}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Group or User not found"},"properties":{"message":{"type":"string","example":"Group or User not found"}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"User is not in the group"},"properties":{"message":{"type":"string","example":"User is not in the group"}}}}}}},"tags":["Groups"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"integer","description":"The ID of the user to remove.","example":16}},"required":["user_id"]}}}}},"get":{"summary":"Get users in group","operationId":"getUsersInGroup","description":"Retrieves all users belonging to a specific group.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":"31c6d6f1-d673-47ac-9238-8f4160fbb84f","database_id":4684,"name":"Haven","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4684","avatar_small_url":"https://www.gravatar.com/avatar/4a1b0fdc7ca782414559ecf6d663d361?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Haven/60/FFDBCC/694536","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:05.000000Z"},{"id":"5befb442-2160-4f6a-970c-ed348c588251","database_id":4685,"name":"Bailee","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4685","avatar_small_url":"https://www.gravatar.com/avatar/2bb67cfc65b36078988fbb0881d57464?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Bailee/60/FFAEA5/69180f","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:05.000000Z"}]},"properties":{"data":{"type":"array","example":[{"id":"31c6d6f1-d673-47ac-9238-8f4160fbb84f","database_id":4684,"name":"Haven","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4684","avatar_small_url":"https://www.gravatar.com/avatar/4a1b0fdc7ca782414559ecf6d663d361?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Haven/60/FFDBCC/694536","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:05.000000Z"},{"id":"5befb442-2160-4f6a-970c-ed348c588251","database_id":4685,"name":"Bailee","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4685","avatar_small_url":"https://www.gravatar.com/avatar/2bb67cfc65b36078988fbb0881d57464?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Bailee/60/FFAEA5/69180f","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:05.000000Z"}],"items":{"type":"object","properties":{"id":{"type":"string","example":"31c6d6f1-d673-47ac-9238-8f4160fbb84f"},"database_id":{"type":"integer","example":4684},"name":{"type":"string","example":"Haven"},"role":{"type":"string","example":"member"},"profile_url":{"type":"string","example":"{YOUR-PORTAL-URL}/user/profile/4684"},"avatar_small_url":{"type":"string","example":"https://www.gravatar.com/avatar/4a1b0fdc7ca782414559ecf6d663d361?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Haven/60/FFDBCC/694536"},"counter_votes":{"type":"integer","example":0},"counter_comments":{"type":"integer","example":0},"counter_posts":{"type":"integer","example":0},"counter_comment_votes":{"type":"integer","example":0},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"}}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Group] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Group] ..."}}}}}}},"tags":["Groups"]},"parameters":[{"in":"path","name":"group","description":"The group.","example":"310007ab-afaa-428b-8ff0-5ff7be2b1d88","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","description":"The integer ID of the group.","example":16,"required":true,"schema":{"type":"integer"}}]},"/api/v1/knowledgebase/link-health/scan":{"post":{"summary":"Kick off a scan for the given tab. Returns scan_id for polling.","operationId":"kickOffAScanForTheGivenTabReturnsScanIdForPolling","description":"","parameters":[],"responses":[],"tags":["Knowledgebase Link Health"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tab_id":{"type":"integer","description":"Must match an existing stored value.","example":16}},"required":["tab_id"]}}}}}},"/api/v1/knowledgebase/link-health/progress/{scanId}":{"get":{"summary":"Poll scan progress.","operationId":"pollScanProgress","description":"","parameters":[],"responses":{"401":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Unauthenticated."},"properties":{"message":{"type":"string","example":"Unauthenticated."}}}}}}},"tags":["Knowledgebase Link Health"]},"parameters":[{"in":"path","name":"scanId","description":"","example":"architecto","required":true,"schema":{"type":"string"}}]},"/api/v1/knowledgebase/link-health/results/{tabId}":{"get":{"summary":"Return the latest per-post health for a tab. Ordered failed-first\nso the review UI shows the actionable rows at the top.","operationId":"returnTheLatestPerPostHealthForATabOrderedFailedFirstSoTheReviewUIShowsTheActionableRowsAtTheTop","description":"","parameters":[],"responses":{"401":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Unauthenticated."},"properties":{"message":{"type":"string","example":"Unauthenticated."}}}}}}},"tags":["Knowledgebase Link Health"]},"parameters":[{"in":"path","name":"tabId","description":"","example":"architecto","required":true,"schema":{"type":"string"}}]},"/api/v1/knowledgebase/sync/export":{"get":{"summary":"Export knowledge base as ZIP","operationId":"exportKnowledgeBaseAsZIP","description":"Downloads all sections and posts as markdown files in a ZIP archive.","parameters":[],"responses":{"401":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Unauthenticated."},"properties":{"message":{"type":"string","example":"Unauthenticated."}}}}}}},"tags":["Knowledgebase Sync"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tab_id":{"type":"integer","description":"Must match an existing stored value.","example":16}},"required":["tab_id"]}}}}}},"/api/v1/knowledgebase/sync/preview":{"post":{"summary":"Preview sync changes","operationId":"previewSyncChanges","description":"Uploads a ZIP file and returns a preview of what would be created, updated, or deleted.","parameters":[],"responses":[],"tags":["Knowledgebase Sync"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"Must be a file. Must not be greater than 102400 kilobytes."},"tab_id":{"type":"integer","description":"100MB max. Must match an existing stored value.","example":16}},"required":["file","tab_id"]}}}}}},"/api/v1/knowledgebase/sync/execute":{"post":{"summary":"Execute sync","operationId":"executeSync","description":"Performs the actual sync based on uploaded ZIP file.","parameters":[],"responses":[],"tags":["Knowledgebase Sync"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"Must be a file. Must not be greater than 102400 kilobytes."},"tab_id":{"type":"integer","description":"100MB max. Must match an existing stored value.","example":16},"delete_confirmed":{"type":"array","description":"","example":[16],"items":{"type":"integer"}},"delete_sections_confirmed":{"type":"array","description":"","example":[16],"items":{"type":"integer"}}},"required":["file","tab_id"]}}}}}},"/api/v1/moderation/pending":{"get":{"summary":"Get pending items","operationId":"getPendingItems","description":"Retrieves posts and comments awaiting moderation (`moderation_status = 'pending'`),\nnewest first, cursor-paginated. Returns both collections in a single response so\nmoderators can triage the full queue without two round trips.","parameters":[{"in":"query","name":"last_post_id","description":"Cursor. Return posts with id less than this. Omit for the first page.","example":1234,"required":false,"schema":{"type":"integer","description":"Cursor. Return posts with id less than this. Omit for the first page.","example":1234}},{"in":"query","name":"last_comment_id","description":"Cursor. Return comments with id less than this. Omit for the first page.","example":5678,"required":false,"schema":{"type":"integer","description":"Cursor. Return comments with id less than this. Omit for the first page.","example":5678}}],"responses":{"401":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Unauthenticated."},"properties":{"message":{"type":"string","example":"Unauthenticated."}}}}}}},"tags":["Moderation"],"x-mcp":true,"x-mcp-name":"moderation.pending"}},"/api/v1/moderation/reject/{type}/{identifier}":{"post":{"summary":"Reject item (post or comment)","operationId":"rejectItempostOrComment","description":"Marks a single pending post or comment as `rejected`. Records the moderator id\nand timestamp on the item. Rejected posts and comments are hidden from end users\nbut retained for admin review via `GET /api/v1/moderation/rejected`.","parameters":[],"responses":[],"tags":["Moderation"],"x-mcp":true,"x-mcp-name":"moderation.reject"},"parameters":[{"in":"path","name":"type","description":"One of `post` or `comment`.","example":"post","required":true,"schema":{"type":"string"}},{"in":"path","name":"identifier","description":"Post `portal_post_key` for posts, numeric id for comments.","example":"0UFQQh","required":true,"schema":{"type":"string"}}]},"/api/v1/moderation/approve/{type}/{identifier}":{"post":{"summary":"Approve item (post or comment)","operationId":"approveItempostOrComment","description":"Marks a single pending post or comment as `approved`. Records the moderator id\nand timestamp on the item. Approving a post fires the `Post::updated` observer\nwhich re-dispatches translation, auto-tag, and auto-reply jobs that were\nskipped while the post was pending.","parameters":[],"responses":[],"tags":["Moderation"],"x-mcp":true,"x-mcp-name":"moderation.approve"},"parameters":[{"in":"path","name":"type","description":"One of `post` or `comment`.","example":"post","required":true,"schema":{"type":"string"}},{"in":"path","name":"identifier","description":"Post `portal_post_key` for posts, numeric id for comments.","example":"0UFQQh","required":true,"schema":{"type":"string"}}]},"/api/v1/portal":{"get":{"summary":"Get portal","operationId":"getPortal","description":"Retrieves metadata for the current ProductLift portal (name, domain, editor_type,\nlocalization, feature flags). Call this first when writing content via the API to\nknow the portal's default content format.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"guid":"d1db118d-2d73-487a-b03c-dc4c1c1b2d06","title":"aut adipisci","localization":"en","jira_enabled":false,"jira_domain_url":null,"jira_integration_enabled":false,"azure_devops_integration_enabled":false,"editor_type":"wysiwyg"}},"properties":{"data":{"type":"object","properties":{"guid":{"type":"string","example":"d1db118d-2d73-487a-b03c-dc4c1c1b2d06"},"title":{"type":"string","example":"aut adipisci"},"localization":{"type":"string","example":"en"},"jira_enabled":{"type":"boolean","example":false},"jira_domain_url":{"type":"string","example":null,"nullable":true},"jira_integration_enabled":{"type":"boolean","example":false},"azure_devops_integration_enabled":{"type":"boolean","example":false},"editor_type":{"type":"string","example":"wysiwyg"}}}}}}}}},"tags":["Portal"],"x-mcp":true,"x-mcp-name":"portal.get"}},"/api/v1/posts":{"get":{"summary":"List posts","operationId":"listPosts","description":"","parameters":[{"in":"query","name":"tab","description":"Filter by tab (board) slug.","example":"roadmap","required":false,"schema":{"type":"string","description":"Filter by tab (board) slug.","example":"roadmap"}},{"in":"query","name":"category","description":"Filter by category ID.","example":5,"required":false,"schema":{"type":"integer","description":"Filter by category ID.","example":5}},{"in":"query","name":"section","description":"integer|string Filter by section ID or slug.","example":"12","required":false,"schema":{"type":"string","description":"integer|string Filter by section ID or slug.","example":"12"}},{"in":"query","name":"limit","description":"Number of posts to return, 1-100. Default 10.","example":25,"required":false,"schema":{"type":"integer","description":"Number of posts to return, 1-100. Default 10.","example":25}},{"in":"query","name":"skip","description":"Number of posts to skip for pagination. Default 0.","example":0,"required":false,"schema":{"type":"integer","description":"Number of posts to skip for pagination. Default 0.","example":0}},{"in":"query","name":"status","description":"Filter by status ID. Can also be an array of status IDs.","example":3,"required":false,"schema":{"type":"integer","description":"Filter by status ID. Can also be an array of status IDs.","example":3}},{"in":"query","name":"order_by","description":"Sort field. Allowed: id, title, created_at, updated_at, content_updated_at, votes_count, comments_count, latest_status_change, latest_vote, views, sum_mrr, sum_ltv, order, moscow_priority, priority, severity, platform, version, release, trending_score, estimated_date, is_published, is_internal, jira_key, azure_boards_key, author, post_type, assigned_to, section, publish_at, allow_comments. Also accepts cf_<field_id> to sort by a custom field value.","example":"votes_count","required":false,"schema":{"type":"string","description":"Sort field. Allowed: id, title, created_at, updated_at, content_updated_at, votes_count, comments_count, latest_status_change, latest_vote, views, sum_mrr, sum_ltv, order, moscow_priority, priority, severity, platform, version, release, trending_score, estimated_date, is_published, is_internal, jira_key, azure_boards_key, author, post_type, assigned_to, section, publish_at, allow_comments. Also accepts cf_<field_id> to sort by a custom field value.","example":"votes_count"}},{"in":"query","name":"order_direction","description":"Sort direction. Allowed: asc, desc.","example":"desc","required":false,"schema":{"type":"string","description":"Sort direction. Allowed: asc, desc.","example":"desc"}},{"in":"query","name":"platform","description":"Filter by platform. Allowed: ios, android, web, windows, macos, linux, playstation-5, xbox-series-xs, nintendo-switch, alexa, google-assistant, siri, chrome-extension, firefox-extension, safari-extension, edge-extension, api, sdk, mobile.","example":"ios","required":false,"schema":{"type":"string","description":"Filter by platform. Allowed: ios, android, web, windows, macos, linux, playstation-5, xbox-series-xs, nintendo-switch, alexa, google-assistant, siri, chrome-extension, firefox-extension, safari-extension, edge-extension, api, sdk, mobile.","example":"ios"}},{"in":"query","name":"version","description":"Filter by version.","example":"2.1.0","required":false,"schema":{"type":"string","description":"Filter by version.","example":"2.1.0"}},{"in":"query","name":"release","description":"Filter by release.","example":"Autumn Update","required":false,"schema":{"type":"string","description":"Filter by release.","example":"Autumn Update"}},{"in":"query","name":"tag_id","description":"string|string[] Filter by tag ID (single value or array).","example":"550e8400-e29b-41d4-a716-446655440000","required":false,"schema":{"type":"string","description":"string|string[] Filter by tag ID (single value or array).","example":"550e8400-e29b-41d4-a716-446655440000"}},{"in":"query","name":"format","description":"Response format: full or minimal.","example":"full","required":false,"schema":{"type":"string","description":"Response format: full or minimal.","example":"full"}},{"in":"query","name":"saved_query_id","description":"Use a predefined saved query.","example":1,"required":false,"schema":{"type":"integer","description":"Use a predefined saved query.","example":1}}],"responses":{"200":{"description":"success","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":"WEukJy","uid":"WEukJy-uid","title":"Photo upload","description_short":"It would be great if I can upload a photo.","description":"It would be great if I can upload a photo. This feature would allow users to share visual content directly within the platform.","description_changelog":"Added photo upload functionality to enhance user experience.","clean_description_changelog":"Added photo upload functionality to enhance user experience.","image_main":"https://example.com/images/photo-upload.jpg","votes_count":15,"comments_count":3,"views_count":100,"url":"https://your-portal-url.com/p/photo-upload-WEukJy","url_edit":"https://your-portal-url.com/p/photo-upload-WEukJy","url_delete":"https://your-portal-url.com/admin/posts/delete/WEukJy","created_at":"2023-05-15T10:00:00Z","updated_at":"2023-05-16T14:30:00Z","latest_status_change_at":"2023-05-16T14:30:00Z","latest_status_change_at_formatted":"16 May 2023","author":{"id":"420b8cfb-2c3d-4df2-b6bd-7ac6aae1a14a","name":"Madalyn","email":"robyn.oconner@example.net","role":"member","counter_votes":4,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0},"categories":[{"id":3,"name":"Integration","color":"#FFAEA5"}],"status":{"id":3,"name":"💪 Now available","color":"#FFCCB6","tab_name":"Updates"},"section_id":2,"moderation_status":"approved","conversion":0.75,"prioritization_impact":8,"prioritization_confidence":7,"prioritization_ease":6,"prioritization_reach":9}],"hasMore":false,"total":1,"skip":0,"limit":10},"properties":{"data":{"type":"array","example":[{"id":"WEukJy","uid":"WEukJy-uid","title":"Photo upload","description_short":"It would be great if I can upload a photo.","description":"It would be great if I can upload a photo. This feature would allow users to share visual content directly within the platform.","description_changelog":"Added photo upload functionality to enhance user experience.","clean_description_changelog":"Added photo upload functionality to enhance user experience.","image_main":"https://example.com/images/photo-upload.jpg","votes_count":15,"comments_count":3,"views_count":100,"url":"https://your-portal-url.com/p/photo-upload-WEukJy","url_edit":"https://your-portal-url.com/p/photo-upload-WEukJy","url_delete":"https://your-portal-url.com/admin/posts/delete/WEukJy","created_at":"2023-05-15T10:00:00Z","updated_at":"2023-05-16T14:30:00Z","latest_status_change_at":"2023-05-16T14:30:00Z","latest_status_change_at_formatted":"16 May 2023","author":{"id":"420b8cfb-2c3d-4df2-b6bd-7ac6aae1a14a","name":"Madalyn","email":"robyn.oconner@example.net","role":"member","counter_votes":4,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0},"categories":[{"id":3,"name":"Integration","color":"#FFAEA5"}],"status":{"id":3,"name":"💪 Now available","color":"#FFCCB6","tab_name":"Updates"},"section_id":2,"moderation_status":"approved","conversion":0.75,"prioritization_impact":8,"prioritization_confidence":7,"prioritization_ease":6,"prioritization_reach":9}],"items":{"type":"object","properties":{"id":{"type":"string","example":"WEukJy"},"uid":{"type":"string","example":"WEukJy-uid"},"title":{"type":"string","example":"Photo upload"},"description_short":{"type":"string","example":"It would be great if I can upload a photo."},"description":{"type":"string","example":"It would be great if I can upload a photo. This feature would allow users to share visual content directly within the platform."},"description_changelog":{"type":"string","example":"Added photo upload functionality to enhance user experience."},"clean_description_changelog":{"type":"string","example":"Added photo upload functionality to enhance user experience."},"image_main":{"type":"string","example":"https://example.com/images/photo-upload.jpg"},"votes_count":{"type":"integer","example":15},"comments_count":{"type":"integer","example":3},"views_count":{"type":"integer","example":100},"url":{"type":"string","example":"https://your-portal-url.com/p/photo-upload-WEukJy"},"url_edit":{"type":"string","example":"https://your-portal-url.com/p/photo-upload-WEukJy"},"url_delete":{"type":"string","example":"https://your-portal-url.com/admin/posts/delete/WEukJy"},"created_at":{"type":"string","example":"2023-05-15T10:00:00Z"},"updated_at":{"type":"string","example":"2023-05-16T14:30:00Z"},"latest_status_change_at":{"type":"string","example":"2023-05-16T14:30:00Z"},"latest_status_change_at_formatted":{"type":"string","example":"16 May 2023"},"author":{"type":"object","properties":{"id":{"type":"string","example":"420b8cfb-2c3d-4df2-b6bd-7ac6aae1a14a"},"name":{"type":"string","example":"Madalyn"},"email":{"type":"string","example":"robyn.oconner@example.net"},"role":{"type":"string","example":"member"},"counter_votes":{"type":"integer","example":4},"counter_comments":{"type":"integer","example":0},"counter_posts":{"type":"integer","example":0},"counter_comment_votes":{"type":"integer","example":0}}},"categories":{"type":"array","example":[{"id":3,"name":"Integration","color":"#FFAEA5"}],"items":{"type":"object","properties":{"id":{"type":"integer","example":3},"name":{"type":"string","example":"Integration"},"color":{"type":"string","example":"#FFAEA5"}}}},"status":{"type":"object","properties":{"id":{"type":"integer","example":3},"name":{"type":"string","example":"💪 Now available"},"color":{"type":"string","example":"#FFCCB6"},"tab_name":{"type":"string","example":"Updates"}}},"section_id":{"type":"integer","example":2},"moderation_status":{"type":"string","example":"approved"},"conversion":{"type":"number","example":0.75},"prioritization_impact":{"type":"integer","example":8},"prioritization_confidence":{"type":"integer","example":7},"prioritization_ease":{"type":"integer","example":6},"prioritization_reach":{"type":"integer","example":9}}}},"hasMore":{"type":"boolean","example":false},"total":{"type":"integer","example":1},"skip":{"type":"integer","example":0},"limit":{"type":"integer","example":10}}}}}}},"tags":["Posts"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"tab":{"type":"string","description":"Optional tab slug to filter on the tab, example: roadmap.","example":"architecto","nullable":true},"category":{"type":"integer","description":"Optional category ID of the posts. Must match an existing stored value.","example":16,"nullable":true},"section":{"type":"string","description":"Optional section ID of the posts.","example":null,"nullable":true},"limit":{"type":"integer","description":"A limit on the number of posts to be returned (1-100, default 10). Must be at least 1. Must not be greater than 100.","example":22,"nullable":true},"skip":{"type":"integer","description":"The number of posts to skip before starting to fetch (default 0). Must be at least 0.","example":84,"nullable":true},"status":{"type":"string","description":"Optional status ID or array of status IDs to filter the posts.","example":null,"nullable":true},"order_by":{"type":"string","description":"Optional sorting field and direction.","example":"architecto","nullable":true},"order_direction":{"type":"string","description":"","example":"asc","enum":["asc","desc"],"nullable":true},"platform":{"type":"string","description":"Optional platform filter (ios, android, web, etc.).","example":null},"priority":{"type":"string","description":"Optional priority filter (admin-only field).","example":"high","enum":["low","medium","high","critical"],"nullable":true},"severity":{"type":"string","description":"Optional severity filter (admin-only field).","example":"critical","enum":["minor","major","critical","blocker"],"nullable":true},"version":{"type":"string","description":"Optional version filter (e.g., \"2.1.0\", \"v5.4\"). Must not be greater than 255 characters.","example":"n","nullable":true},"release":{"type":"string","description":"Optional release name/number filter (e.g., \"Autumn Update\", \"R24\"). Must not be greater than 255 characters.","example":"g","nullable":true},"saved_query_id":{"type":"string","description":"Optional saved query ID to use predefined filters. Must match an existing stored value.","example":null,"nullable":true},"created_condition":{"type":"string","description":"Created date filtering options. This field is required when <code>created_start</code> is present.","example":"before","enum":["after","before","between"],"nullable":true},"created_start":{"type":"string","description":"This field is required when <code>created_condition</code> is present. Must be a valid date.","example":"2026-08-19T12:15:04","nullable":true},"created_end":{"type":"string","description":"This field is required when <code>created_condition</code> is <code>between</code>. Must be a valid date. Must be a date after <code>created_start</code>.","example":"2052-09-11","nullable":true},"latest_vote_condition":{"type":"string","description":"Latest vote date filtering options. This field is required when <code>latest_vote_start</code> is present.","example":"between","enum":["after","before","between"],"nullable":true},"latest_vote_start":{"type":"string","description":"This field is required when <code>latest_vote_condition</code> is present. Must be a valid date.","example":"2026-08-19T12:15:04","nullable":true},"latest_vote_end":{"type":"string","description":"This field is required when <code>latest_vote_condition</code> is <code>between</code>. Must be a valid date. Must be a date after <code>latest_vote_start</code>.","example":"2052-09-11","nullable":true},"title_condition":{"type":"string","description":"Title filtering options. This field is required when <code>title_value</code> is present.","example":"contains","enum":["contains","does_not_contain"],"nullable":true},"title_value":{"type":"string","description":"This field is required when <code>title_condition</code> is present. Must not be greater than 180 characters.","example":"n","nullable":true},"reference_condition":{"type":"string","description":"References filtering options. This field is required when <code>reference_value</code> is present.","example":"contains","enum":["contains","does_not_contain"],"nullable":true},"reference_value":{"type":"string","description":"This field is required when <code>reference_condition</code> is present.","example":"architecto","nullable":true},"min_votes":{"type":"integer","description":"Votes range filtering. Must be at least 0.","example":39,"nullable":true},"max_votes":{"type":"string","description":"","example":null},"min_mrr":{"type":"number","description":"MRR range filtering. Must be at least 0.","example":84,"nullable":true},"max_mrr":{"type":"string","description":"","example":null},"min_ltv":{"type":"number","description":"LTV range filtering. Must be at least 0.","example":12,"nullable":true},"max_ltv":{"type":"string","description":"","example":null},"tag_id":{"type":"array","description":"Must match an existing stored value.","example":null,"items":{"type":"string"}},"post_type_id":{"type":"integer","description":"Post type filtering (admin posts list). Must match an existing stored value.","example":16,"nullable":true},"format":{"type":"string","description":"Format full or minimal - null = full.","example":"architecto","nullable":true},"group_by":{"type":"string","description":"Collapse the legacy per-column fanout: when set, the response\nreturns one bucket per status_id or section_id instead of a\nflat list. Lets Kanban-style tabs render every column from a\nsingle round trip.","example":"status","enum":["status","section"],"nullable":true},"custom_field_filter":{"type":"array","description":"Custom field filters (admin posts list).","example":null,"items":{"type":"object","nullable":true,"properties":{"field_id":{"type":"integer","description":"This field is required when <code>custom_field_filter</code> is present. Must be at least 1.","example":22},"op":{"type":"string","description":"This field is required when <code>custom_field_filter</code> is present.","example":"not_empty","enum":["eq","contains","empty","not_empty"]},"value":{"type":"string","description":"Must not be greater than 500 characters.","example":"g","nullable":true}}}}}}}}},"x-mcp":true,"x-mcp-name":"posts.list"},"post":{"summary":"Create post","operationId":"createPost","description":"","parameters":[],"responses":[],"tags":["Posts"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"title":{"type":"string","description":"Post title, 2-180 chars.","example":"Add dark mode toggle"},"description":{"type":"string","description":"Post body, max 500000 chars. HTML or markdown depending on portal.editor_type.","example":"Allow users to toggle dark mode from the header.","nullable":true},"category_id":{"type":"integer","description":"Optional category ID. If not found, no category is added.","example":null,"nullable":true},"status_id":{"type":"integer","description":"Optional status ID. If omitted, first status of the board is used as default.","example":null,"nullable":true},"section_id":{"type":"integer","description":"Optional section ID. REQUIRED for knowledgebase boards.","example":null,"nullable":true},"user_id":{"type":"string","description":"UUID of the submitting user. If omitted, uses the authenticated user or falls back to anonymous.","example":null,"nullable":true},"user_email":{"type":"string","description":"Optional author email. Resolves to existing portal user or creates a new member. Ignored if `user_id` is set.","example":null,"nullable":true},"user_name":{"type":"string","description":"Optional display name used when a new user is created from `user_email`.","example":null,"nullable":true},"send_invite":{"type":"boolean","description":"Send an invite email when creating a new user from `user_email`. Defaults to false.","example":null,"nullable":true},"personal_message":{"type":"string","description":"Custom message included in the invite email when `send_invite=true`. Falls back to the portal's standard invite message when omitted.","example":null,"nullable":true},"allow_comments":{"type":"boolean","description":"Allow comments on this post. Defaults to true. NOTE: the effective value may be overridden at the tab level; check `allow_comments_reason` in the response.","example":null,"nullable":true},"jira_key":{"type":"string","description":"JIRA issue key associated with this post.","example":null,"nullable":true},"azure_boards_key":{"type":"string","description":"Azure Boards work item key.","example":null,"nullable":true},"estimated_date":{"type":"string","description":"Estimated completion date (YYYY-MM-DD). Bound to MySQL datetime range (1900-2100).","example":null,"nullable":true},"tags":{"type":"array","description":"Array of tag names (each 2-60 chars).","example":["Feature Request","UX"],"items":{"type":"string","nullable":true}},"created_at":{"type":"string","description":"Custom creation date. Must not be in the future — use `publish_at` for future scheduling.","example":null,"nullable":true},"prioritization_impact":{"type":"integer","description":"Impact score, 1-10.","example":null,"nullable":true},"prioritization_confidence":{"type":"integer","description":"Confidence score, 1-10.","example":null,"nullable":true},"prioritization_ease":{"type":"integer","description":"Effort (RICE/IE) or Ease (ICE) score, 1-10.","example":null,"nullable":true},"prioritization_reach":{"type":"integer","description":"Reach score, 1-10.","example":null,"nullable":true},"moscow_priority":{"type":"string","description":"MoSCoW priority. Allowed: M, S, C, W.","example":null,"nullable":true},"priority":{"type":"string","description":"Priority (admin-only). Allowed: low, medium, high, critical.","example":null,"nullable":true},"severity":{"type":"string","description":"Severity (admin-only). Allowed: minor, major, critical, blocker.","example":null,"nullable":true},"prioritization_color":{"type":"string","description":"Hex color for prioritization status (max 7 chars, e.g. #FF5733).","example":null,"nullable":true},"manual_bonus_votes":{"type":"integer","description":"Bonus vote count added on top of real votes.","example":null,"nullable":true},"is_published":{"type":"boolean","description":"Publish (true) or save as draft (false). Draft posts are admin-only.","example":null,"nullable":true},"is_internal":{"type":"boolean","description":"Mark as internal so the post is hidden from non-admin users (admin-only).","example":null,"nullable":true},"publish_at":{"type":"string","description":"Schedule the post to publish at a specific date/time. When set in the future, the post is stored as draft and will auto-publish (and trigger changelog email) at that time.","example":null,"nullable":true},"notify_subscribers":{"type":"boolean","description":"For changelog-tab posts: controls whether subscribers receive the broadcast email on publish. Defaults to true. Set to false to publish silently. Sticky across trigger paths (initial publish, scheduled publish, status move).","example":null,"nullable":true},"version":{"type":"string","description":"Software version identifier (e.g. 2.1.0).","example":null,"nullable":true},"release":{"type":"string","description":"Release name or number (e.g. Autumn Update).","example":null,"nullable":true},"platform":{"type":"string","description":"Target platform. Allowed: ios, android, web, windows, macos, linux, playstation-5, xbox-series-xs, nintendo-switch, alexa, google-assistant, siri, chrome-extension, firefox-extension, safari-extension, edge-extension, api, sdk, mobile.","example":null},"assigned_to_id":{"type":"integer","description":"User ID to assign this post to.","example":null,"nullable":true},"color":{"type":"string","description":"Hex color for categorization (e.g. #FF5733).","example":null,"nullable":true},"excerpt":{"type":"string","description":"Custom excerpt for preview and SEO (max 150 chars, auto-generated if not provided).","example":null,"nullable":true},"post_type_id":{"type":"integer","description":"Post type ID for custom fields.","example":null,"nullable":true},"custom_fields":{"type":"object","description":"Custom field values as key-value pairs (field_id => value).","example":null,"properties":[],"nullable":true},"capture_data":{"type":"string","description":"Optional: Captured environment data (console logs, system info, screenshot) - sent as JSON string via FormData.","example":null,"nullable":true},"vote_user_keys":{"type":"array","description":"Cast a vote on the new post for each of these existing user UUIDs (admin-only \"vote on behalf\" feature).","example":null,"items":{"type":"string"}},"vote_new_users":{"type":"array","description":"Create new users and immediately cast a vote on the new post for each (admin-only). Each item is {name, email, skip_email?, personal_message?}. `skip_email` defaults to true (silent add); set false to trigger an invite email with the optional `personal_message` (falls back to the portal's standard invite message).","example":null,"items":{"type":"object","nullable":true,"properties":{"name":{"type":"string","description":"This field is required when <code>vote_new_users.*.email</code> is present. Must not be greater than 100 characters.","example":"g"},"email":{"type":"string","description":"This field is required when <code>vote_new_users.*.name</code> is present. Must be a valid email address. Must not be greater than 191 characters.","example":"rowan.gulgowski@example.com"},"skip_email":{"type":"string","description":"Optional per-voter: when false and the user does not exist yet, send an invite email (defaults to true = silent add).","example":null,"nullable":true},"personal_message":{"type":"string","description":"Optional per-voter: personal message included in the invite email when skip_email=false.","example":"architecto","nullable":true}}}},"source_locale":{"type":"string","description":"Round 9 §6.7 / §10.14: explicit source_locale must match the\nportal's default OR one of its enabled languages. Omitted →\nPost::creating hook falls back to portal.localization. Invalid\nstring → 422 here so bogus locales never reach the DB or the\ntranslation pipeline. Must not be greater than 10 characters.","example":"ln_CD","nullable":true},"image":{"type":"string","format":"binary","description":"Optional image (jpeg/gif/png/webp, max 20MB) for the post card/header."}},"required":["title"]}}}},"x-mcp":true,"x-mcp-name":"posts.create"}},"/api/v1/posts/{post}":{"get":{"summary":"Get post","operationId":"getPost","description":"","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"WEukJy","uid":"WEukJy-uid","title":"Photo upload","description_short":"It would be great if I can upload a photo.","description":"It would be great if I can upload a photo. This feature would allow users to share visual content directly within the platform.","description_changelog":"Added photo upload functionality to enhance user experience.","clean_description_changelog":"Added photo upload functionality to enhance user experience.","image_main":"https://example.com/images/photo-upload.jpg","votes_count":15,"comments_count":3,"views_count":100,"url":"https://your-portal-url.com/p/photo-upload-WEukJy","url_edit":"https://your-portal-url.com/p/photo-upload-WEukJy","url_delete":"https://your-portal-url.com/admin/posts/delete/WEukJy","created_at":"2023-05-15T10:00:00Z","updated_at":"2023-05-16T14:30:00Z","latest_status_change_at":"2023-05-16T14:30:00Z","latest_status_change_at_formatted":"16 May 2023","author":{"id":"420b8cfb-2c3d-4df2-b6bd-7ac6aae1a14a","name":"Madalyn","email":"robyn.oconner@example.net","role":"member","counter_votes":4,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0},"categories":[{"id":3,"name":"Integration","color":"#FFAEA5"}],"status":{"id":3,"name":"💪 Now available","color":"#FFCCB6","tab_name":"Updates"},"section_id":2,"moderation_status":"approved","conversion":0.75,"prioritization_impact":8,"prioritization_confidence":7,"prioritization_ease":6,"prioritization_reach":9}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"WEukJy"},"uid":{"type":"string","example":"WEukJy-uid"},"title":{"type":"string","example":"Photo upload"},"description_short":{"type":"string","example":"It would be great if I can upload a photo."},"description":{"type":"string","example":"It would be great if I can upload a photo. This feature would allow users to share visual content directly within the platform."},"description_changelog":{"type":"string","example":"Added photo upload functionality to enhance user experience."},"clean_description_changelog":{"type":"string","example":"Added photo upload functionality to enhance user experience."},"image_main":{"type":"string","example":"https://example.com/images/photo-upload.jpg"},"votes_count":{"type":"integer","example":15},"comments_count":{"type":"integer","example":3},"views_count":{"type":"integer","example":100},"url":{"type":"string","example":"https://your-portal-url.com/p/photo-upload-WEukJy"},"url_edit":{"type":"string","example":"https://your-portal-url.com/p/photo-upload-WEukJy"},"url_delete":{"type":"string","example":"https://your-portal-url.com/admin/posts/delete/WEukJy"},"created_at":{"type":"string","example":"2023-05-15T10:00:00Z"},"updated_at":{"type":"string","example":"2023-05-16T14:30:00Z"},"latest_status_change_at":{"type":"string","example":"2023-05-16T14:30:00Z"},"latest_status_change_at_formatted":{"type":"string","example":"16 May 2023"},"author":{"type":"object","properties":{"id":{"type":"string","example":"420b8cfb-2c3d-4df2-b6bd-7ac6aae1a14a"},"name":{"type":"string","example":"Madalyn"},"email":{"type":"string","example":"robyn.oconner@example.net"},"role":{"type":"string","example":"member"},"counter_votes":{"type":"integer","example":4},"counter_comments":{"type":"integer","example":0},"counter_posts":{"type":"integer","example":0},"counter_comment_votes":{"type":"integer","example":0}}},"categories":{"type":"array","example":[{"id":3,"name":"Integration","color":"#FFAEA5"}],"items":{"type":"object","properties":{"id":{"type":"integer","example":3},"name":{"type":"string","example":"Integration"},"color":{"type":"string","example":"#FFAEA5"}}}},"status":{"type":"object","properties":{"id":{"type":"integer","example":3},"name":{"type":"string","example":"💪 Now available"},"color":{"type":"string","example":"#FFCCB6"},"tab_name":{"type":"string","example":"Updates"}}},"section_id":{"type":"integer","example":2},"moderation_status":{"type":"string","example":"approved"},"conversion":{"type":"number","example":0.75},"prioritization_impact":{"type":"integer","example":8},"prioritization_confidence":{"type":"integer","example":7},"prioritization_ease":{"type":"integer","example":6},"prioritization_reach":{"type":"integer","example":9}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Post] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Post] ..."}}}}}}},"tags":["Posts"],"x-mcp":true,"x-mcp-name":"posts.get"},"patch":{"summary":"Update post","operationId":"updatePost","description":"","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"WEukJy","uid":"WEukJy-uid","title":"Photo upload","description_short":"It would be great if I can upload a photo.","description":"It would be great if I can upload a photo. This feature would allow users to share visual content directly within the platform.","description_changelog":"Added photo upload functionality to enhance user experience.","clean_description_changelog":"Added photo upload functionality to enhance user experience.","image_main":"https://example.com/images/photo-upload.jpg","votes_count":15,"comments_count":3,"views_count":100,"url":"https://your-portal-url.com/p/photo-upload-WEukJy","url_edit":"https://your-portal-url.com/p/photo-upload-WEukJy","url_delete":"https://your-portal-url.com/admin/posts/delete/WEukJy","created_at":"2023-05-15T10:00:00Z","updated_at":"2023-05-16T14:30:00Z","latest_status_change_at":"2023-05-16T14:30:00Z","latest_status_change_at_formatted":"16 May 2023","author":{"id":"420b8cfb-2c3d-4df2-b6bd-7ac6aae1a14a","name":"Madalyn","email":"robyn.oconner@example.net","role":"member","counter_votes":4,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0},"categories":[{"id":3,"name":"Integration","color":"#FFAEA5"}],"status":{"id":3,"name":"💪 Now available","color":"#FFCCB6","tab_name":"Updates"},"section_id":2,"moderation_status":"approved","conversion":0.75,"prioritization_impact":8,"prioritization_confidence":7,"prioritization_ease":6,"prioritization_reach":9}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"WEukJy"},"uid":{"type":"string","example":"WEukJy-uid"},"title":{"type":"string","example":"Photo upload"},"description_short":{"type":"string","example":"It would be great if I can upload a photo."},"description":{"type":"string","example":"It would be great if I can upload a photo. This feature would allow users to share visual content directly within the platform."},"description_changelog":{"type":"string","example":"Added photo upload functionality to enhance user experience."},"clean_description_changelog":{"type":"string","example":"Added photo upload functionality to enhance user experience."},"image_main":{"type":"string","example":"https://example.com/images/photo-upload.jpg"},"votes_count":{"type":"integer","example":15},"comments_count":{"type":"integer","example":3},"views_count":{"type":"integer","example":100},"url":{"type":"string","example":"https://your-portal-url.com/p/photo-upload-WEukJy"},"url_edit":{"type":"string","example":"https://your-portal-url.com/p/photo-upload-WEukJy"},"url_delete":{"type":"string","example":"https://your-portal-url.com/admin/posts/delete/WEukJy"},"created_at":{"type":"string","example":"2023-05-15T10:00:00Z"},"updated_at":{"type":"string","example":"2023-05-16T14:30:00Z"},"latest_status_change_at":{"type":"string","example":"2023-05-16T14:30:00Z"},"latest_status_change_at_formatted":{"type":"string","example":"16 May 2023"},"author":{"type":"object","properties":{"id":{"type":"string","example":"420b8cfb-2c3d-4df2-b6bd-7ac6aae1a14a"},"name":{"type":"string","example":"Madalyn"},"email":{"type":"string","example":"robyn.oconner@example.net"},"role":{"type":"string","example":"member"},"counter_votes":{"type":"integer","example":4},"counter_comments":{"type":"integer","example":0},"counter_posts":{"type":"integer","example":0},"counter_comment_votes":{"type":"integer","example":0}}},"categories":{"type":"array","example":[{"id":3,"name":"Integration","color":"#FFAEA5"}],"items":{"type":"object","properties":{"id":{"type":"integer","example":3},"name":{"type":"string","example":"Integration"},"color":{"type":"string","example":"#FFAEA5"}}}},"status":{"type":"object","properties":{"id":{"type":"integer","example":3},"name":{"type":"string","example":"💪 Now available"},"color":{"type":"string","example":"#FFCCB6"},"tab_name":{"type":"string","example":"Updates"}}},"section_id":{"type":"integer","example":2},"moderation_status":{"type":"string","example":"approved"},"conversion":{"type":"number","example":0.75},"prioritization_impact":{"type":"integer","example":8},"prioritization_confidence":{"type":"integer","example":7},"prioritization_ease":{"type":"integer","example":6},"prioritization_reach":{"type":"integer","example":9}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"oneOf":[{"description":"","type":"object","example":{"message":"No query results for model [App\\Post] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Post] ..."}}},{"description":"","type":"object","example":{"message":"No query results for model [App\\User] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\User] ..."}}}]}}}},"422":{"description":"","content":{"text/plain":{"schema":{"type":"string","example":"{\n \"message\": \"The given data was invalid.\",\n \"errors\": {\n   \"user_id\": [\n     \"The user id must be a valid UUID.\"\n   ]\n}"}}}}},"tags":["Posts"],"requestBody":{"required":false,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"title":{"type":"string","description":"The post title (2-180 chars).","example":"Allow dark mode toggle","nullable":true},"description":{"type":"string","description":"The post description. HTML or markdown depending on the post's `editor_type` (which may differ from the portal default — check the post's own field). Max 500000 chars.","example":"Updated description for dark mode feature.","nullable":true},"user_id":{"type":"string","description":"UUID of the post author. Set null to make anonymous.","example":null,"nullable":true},"section_id":{"type":"integer","description":"Section ID for the post.","example":null,"nullable":true},"allow_comments":{"type":"boolean","description":"Allow comments on this post. NOTE: effective value may be overridden at tab level; inspect `allow_comments_reason` in the response instead of diffing this field.","example":null,"nullable":true},"jira_key":{"type":"string","description":"JIRA issue key (e.g. PROJ-123).","example":null,"nullable":true},"azure_boards_key":{"type":"string","description":"Azure Boards work item key (e.g. AB-456).","example":null,"nullable":true},"estimated_date":{"type":"string","description":"Estimated completion date (e.g. 2025-06-15).","example":null,"nullable":true},"status_id":{"type":"integer","description":"New status ID for the post. This is the CANONICAL field (matches DB column). Legacy `status` is accepted and normalized to `status_id`. Cross-board moves happen by picking a `status_id` on the destination tab.","example":3,"nullable":true},"status_update_user_id":{"type":"string","description":"UUID of user making the status change.","example":null},"status_update_email":{"type":"string","description":"Plain-text email body sent to voters when status changes. NOTE: presence of this field triggers the notification email — omit the key entirely to stay silent. Max 6000 chars.","example":null,"nullable":true},"status_update_comment":{"type":"string","description":"HTML comment posted alongside the status change (shown on post page and as changelog description when tagged). Max 6000 chars.","example":null,"nullable":true},"status_update_replace_prior":{"type":"string","description":"Replace the prior status entry.","example":null,"nullable":true},"status_update_comment_tagged_for_changelog":{"type":"boolean","description":"Tag the status comment as the post's changelog description. NOTE: only ONE comment per post can be tagged for changelog; setting it on a new comment replaces the previous. Some clients require the string \"true\" here; boolean true works via JSON.","example":null,"nullable":true},"status_update_comment_pinned_to_top":{"type":"boolean","description":"Pin the status comment to the top of the post. Some clients require the string \"true\"; boolean true works via JSON.","example":null,"nullable":true},"latest_status_change":{"type":"string","description":"Override the changelog date on this post. Sets the sort/display date on the changelog card, backdates the most recent status_history row and the tagged changelog comment, and moves created_at earlier if the new date is older. Works with or without a status change in the same request.","example":"2026-01-15 10:30:00","nullable":true},"tags":{"type":"array","description":"Array of tag names.","example":["Feature Request","UX"],"items":{"type":"string","nullable":true}},"category_id":{"type":"integer","description":"Category ID.","example":null,"nullable":true},"created_at":{"type":"string","description":"Custom creation date (e.g. 2025-01-15 10:30:00). Must not be in the future — use `publish_at` for scheduling.","example":null,"nullable":true},"prioritization_impact":{"type":"integer","description":"Impact score (1-10).","example":null,"nullable":true},"prioritization_confidence":{"type":"integer","description":"Confidence score (1-10).","example":null,"nullable":true},"prioritization_ease":{"type":"integer","description":"Effort score for RICE/IE, Ease score for ICE (1-10).","example":null,"nullable":true},"prioritization_reach":{"type":"integer","description":"Reach score (1-10).","example":null,"nullable":true},"moscow_priority":{"type":"string","description":"MoSCoW priority. Allowed: M, S, C, W.","example":null,"nullable":true},"priority":{"type":"string","description":"Priority (admin-only). Allowed: low, medium, high, critical.","example":null,"nullable":true},"severity":{"type":"string","description":"Severity (admin-only). Allowed: minor, major, critical, blocker.","example":null,"nullable":true},"prioritization_color":{"type":"string","description":"Hex color for prioritization (e.g. #FF5733).","example":null,"nullable":true},"manual_bonus_votes":{"type":"integer","description":"Manual bonus votes.","example":null,"nullable":true},"is_published":{"type":"boolean","description":"Publish or draft status. Draft posts are admin-only.","example":null,"nullable":true},"is_internal":{"type":"boolean","description":"Mark as internal (hidden from non-admin users). Admin-only.","example":null,"nullable":true},"publish_at":{"type":"string","description":"Schedule the post to publish at a specific date/time. Pass null to clear an existing schedule.","example":null,"nullable":true},"notify_subscribers":{"type":"boolean","description":"For changelog-tab posts: controls whether subscribers receive the broadcast email. Send `false` in the same PATCH that publishes the post (draft->live) OR moves it onto a changelog status, to stage the entry silently. Persists to `changelog_notify_subscribers` so scheduled-publish honors it too. Ignored once `changelog_notified_at` has been set (broadcast already happened).","example":null,"nullable":true},"description_changelog":{"type":"string","description":"The changelog-specific copy shown on the changelog card and in the broadcast email. Backed by a `tagged_for_changelog=true` comment on the post: this field upserts that comment. Set BEFORE flipping to a changelog status (with `notify_subscribers=false` if you want to notify manually later) to avoid the notification going out with the pre-move title/description.","example":null,"nullable":true},"version":{"type":"string","description":"Software version (e.g. 2.1.0).","example":null,"nullable":true},"release":{"type":"string","description":"Release name (e.g. Autumn Update).","example":null,"nullable":true},"platform":{"type":"string","description":"Target platform. Allowed: ios, android, web, windows, macos, linux, playstation-5, xbox-series-xs, nintendo-switch, alexa, google-assistant, siri, chrome-extension, firefox-extension, safari-extension, edge-extension, api, sdk, mobile.","example":null},"assigned_to_id":{"type":"integer","description":"User ID to assign this post to.","example":null,"nullable":true},"color":{"type":"string","description":"Hex color (e.g. #FF5733).","example":null,"nullable":true},"excerpt":{"type":"string","description":"Custom excerpt (max 150 chars).","example":null,"nullable":true},"post_type_id":{"type":"integer","description":"Post type ID for custom fields.","example":null,"nullable":true},"custom_fields":{"type":"object","description":"Custom field values as key-value pairs (field_id => value).","example":null,"properties":[],"nullable":true},"image":{"type":"string","format":"binary","description":"Optional image (jpeg/gif/png/webp, max 20MB).","nullable":true},"delete_image":{"type":"boolean","description":"Set to true to remove the existing image.","example":null,"nullable":true}}}}}},"x-mcp":true,"x-mcp-name":"posts.update"},"delete":{"summary":"Delete post","operationId":"deletePost","description":"","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":[],"properties":[]}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Post] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Post] ..."}}}}}}},"tags":["Posts"],"x-mcp":true,"x-mcp-name":"posts.delete"},"parameters":[{"in":"path","name":"post","description":"The post.","example":"all$0Y5","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","description":"The portal_post_key of the post.","example":"0UFQQh","required":true,"schema":{"type":"string"}}]},"/api/v1/posts/{key}/toggle-publish":{"put":{"summary":"Toggle post publish status","operationId":"togglePostPublishStatus","description":"","parameters":[],"responses":{"200":{"description":"","content":{"text/plain":{"schema":{"type":"string","example":"{\n  \"data\": {\n    \"id\": \"0UFQQh\",\n    \"title\": \"Sample Post\",\n    \"description\": \"This is a sample post\",\n    \"is_published\": true,\n    // ... other post attributes\n  }\n}"}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Post] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Post] ..."}}}}}}},"tags":["Posts"],"x-mcp":true,"x-mcp-name":"posts.togglePublish"},"parameters":[{"in":"path","name":"key","description":"The portal_post_key of the post.","example":"0UFQQh","required":true,"schema":{"type":"string"}}]},"/api/v1/posts/{key}/merge":{"post":{"summary":"Merge one post INTO another.","operationId":"mergeOnePostINTOAnother","description":"","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"success":true,"source_key":"abc123","target_key":"def456","stats":{"votes_moved":8,"duplicate_voters_skipped":2,"followers_moved":3,"comments_moved":5}},"properties":{"success":{"type":"boolean","example":true},"source_key":{"type":"string","example":"abc123"},"target_key":{"type":"string","example":"def456"},"stats":{"type":"object","properties":{"votes_moved":{"type":"integer","example":8},"duplicate_voters_skipped":{"type":"integer","example":2},"followers_moved":{"type":"integer","example":3},"comments_moved":{"type":"integer","example":5}}}}}}}},"409":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"error":"Source post is already merged."},"properties":{"error":{"type":"string","example":"Source post is already merged."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The target_key must be different from the source post key.","errors":{"target_key":["The target_key must be different from the source post key."]}},"properties":{"message":{"type":"string","example":"The target_key must be different from the source post key."},"errors":{"type":"object","properties":{"target_key":{"type":"array","example":["The target_key must be different from the source post key."],"items":{"type":"string"}}}}}}}}}},"tags":["Posts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"target_key":{"type":"string","description":"The portal_post_key of the TARGET post (will absorb the source). Must differ from the URL key.","example":"XyZ123"}},"required":["target_key"]}}}},"x-mcp":true,"x-mcp-name":"posts.merge"},"parameters":[{"in":"path","name":"key","description":"The portal_post_key of the SOURCE post (will become a redirect).","example":"0UFQQh","required":true,"schema":{"type":"string"}}]},"/api/v1/posts/{key}/duplicates":{"get":{"summary":"Find potential duplicates for a given post.","operationId":"findPotentialDuplicatesForAGivenPost","description":"","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"source":{"key":"abc123","title":"Dark mode toggle"},"candidates":[{"key":"def456","title":"Add dark mode","score":8.4,"votes":12}]},"properties":{"source":{"type":"object","properties":{"key":{"type":"string","example":"abc123"},"title":{"type":"string","example":"Dark mode toggle"}}},"candidates":{"type":"array","example":[{"key":"def456","title":"Add dark mode","score":8.4,"votes":12}],"items":{"type":"object","properties":{"key":{"type":"string","example":"def456"},"title":{"type":"string","example":"Add dark mode"},"score":{"type":"number","example":8.4},"votes":{"type":"integer","example":12}}}}}}}}}},"tags":["Posts"],"x-mcp":true,"x-mcp-name":"posts.findDuplicates"},"parameters":[{"in":"path","name":"key","description":"The portal_post_key of the source post.","example":"0UFQQh","required":true,"schema":{"type":"string"}}]},"/api/v1/posts/search_duplicates":{"get":{"summary":"Find duplicates","operationId":"findDuplicates","description":"Searches the database for duplicate posts.","parameters":[{"in":"query","name":"title","description":"The post title to search for duplicates.","example":"Dark mode toggle","required":true,"schema":{"type":"string","description":"The post title to search for duplicates.","example":"Dark mode toggle"}},{"in":"query","name":"description","description":"Optional description to improve matching.","example":"Allow users to toggle dark mode","required":false,"schema":{"type":"string","description":"Optional description to improve matching.","example":"Allow users to toggle dark mode"}}],"responses":{"200":{"description":"","content":{"text/plain":{"schema":{"oneOf":[{"description":"","type":"string","example":"{\n \"count\": 0,\n}"},{"description":"","type":"string","example":"{\n \"count\": 1,\n \"posts\": {\n             {\n                 \"id\": \"d8QEXH\",\n                 \"title\": \"Open links in new window\",\n                 \"description\": \"Option to open links in a new window.\"\n              }\n          }\n}"}]}}}}},"tags":["Posts"]}},"/api/v1/posts/search":{"get":{"summary":"Search posts","operationId":"searchPosts","description":"","parameters":[{"in":"query","name":"q","description":"Search keywords, 2-100 chars.","example":"dark mode","required":true,"schema":{"type":"string","description":"Search keywords, 2-100 chars.","example":"dark mode"}},{"in":"query","name":"tab_id","description":"Optional. Restrict search to a single board. Omit to search all boards.","example":3,"required":false,"schema":{"type":"integer","description":"Optional. Restrict search to a single board. Omit to search all boards.","example":3}},{"in":"query","name":"limit","description":"Results per page, 1-100. Default 10.","example":25,"required":false,"schema":{"type":"integer","description":"Results per page, 1-100. Default 10.","example":25}},{"in":"query","name":"skip","description":"Offset for pagination. Default 0.","example":0,"required":false,"schema":{"type":"integer","description":"Offset for pagination. Default 0.","example":0}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"portal_post_key":"0UFQQh","title":"Dark mode toggle","votes_count":12}],"total":1,"keyword":"dark mode","hasMore":false,"skip":0,"limit":10},"properties":{"data":{"type":"array","example":[{"portal_post_key":"0UFQQh","title":"Dark mode toggle","votes_count":12}],"items":{"type":"object","properties":{"portal_post_key":{"type":"string","example":"0UFQQh"},"title":{"type":"string","example":"Dark mode toggle"},"votes_count":{"type":"integer","example":12}}}},"total":{"type":"integer","example":1},"keyword":{"type":"string","example":"dark mode"},"hasMore":{"type":"boolean","example":false},"skip":{"type":"integer","example":0},"limit":{"type":"integer","example":10}}}}}}},"tags":["Posts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"q":{"type":"string","description":"Must be at least 2 characters. Must not be greater than 100 characters.","example":"b"},"tab_id":{"type":"number","description":"Must match an existing stored value.","example":4326.41688,"nullable":true},"limit":{"type":"number","description":"Must be at least 1. Must not be greater than 100.","example":17,"nullable":true},"skip":{"type":"number","description":"Must be at least 0.","example":8,"nullable":true}},"required":["q"]}}}},"x-mcp":true,"x-mcp-name":"posts.search"}},"/api/v1/product-vision":{"get":{"summary":"Get product vision","operationId":"getProductVision","description":"Retrieves the portal's product vision board (vision, target group, needs,\nproduct, business goals). Returns 404 when no vision has been set yet.","parameters":[],"responses":{"401":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Unauthenticated."},"properties":{"message":{"type":"string","example":"Unauthenticated."}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No vision found"},"properties":{"message":{"type":"string","example":"No vision found"}}}}}}},"tags":["Product Vision"],"x-mcp":true,"x-mcp-name":"productVision.get"},"post":{"summary":"Create or update product vision","operationId":"createOrUpdateProductVision","description":"Upserts the portal's product vision board. All five fields are required and\ncapped at 2000 chars each. If a vision already exists it is updated in place;\notherwise a new one is created. Emits an `saved_product_vision` app event.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Vision saved successfully","data":{"vision":"...","target_group":"...","needs":"...","product":"...","business_goals":"..."}},"properties":{"message":{"type":"string","example":"Vision saved successfully"},"data":{"type":"object","properties":{"vision":{"type":"string","example":"..."},"target_group":{"type":"string","example":"..."},"needs":{"type":"string","example":"..."},"product":{"type":"string","example":"..."},"business_goals":{"type":"string","example":"..."}}}}}}}}},"tags":["Product Vision"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"vision":{"type":"string","description":"Long-term product vision statement. Max 2000 chars.","example":"Become the go-to feedback platform for indie SaaS teams."},"target_group":{"type":"string","description":"Who the product serves. Max 2000 chars.","example":"Solo founders and small product teams shipping SaaS."},"needs":{"type":"string","description":"Customer needs the product addresses. Max 2000 chars.","example":"A frictionless way to collect, prioritize, and close the loop on user feedback."},"product":{"type":"string","description":"What the product actually is. Max 2000 chars.","example":"Hosted feedback boards + changelog + roadmap with a public voting UI."},"business_goals":{"type":"string","description":"Business outcomes the vision drives. Max 2000 chars.","example":"Grow to 1000 paying portals by end of year."}},"required":["vision","target_group","needs","product","business_goals"]}}}},"x-mcp":true,"x-mcp-name":"productVision.set"}},"/api/v1/redirects":{"get":{"summary":"List redirects","operationId":"listRedirects","description":"Returns every redirect on the current portal, ordered by source_path.","parameters":[{"in":"query","name":"search","description":"Substring to filter by source_path, destination, or notes.","example":"/old-blog","required":false,"schema":{"type":"string","description":"Substring to filter by source_path, destination, or notes.","example":"/old-blog"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":42,"portal_id":1,"source_path":"/old-blog/hello","destination":"/blog/hello","status_code":301,"match_type":"exact","notes":"Migrated 2026-07-28","created_at":"2026-07-28T12:00:00.000000Z","updated_at":"2026-07-28T12:00:00.000000Z"}]},"properties":{"data":{"type":"array","example":[{"id":42,"portal_id":1,"source_path":"/old-blog/hello","destination":"/blog/hello","status_code":301,"match_type":"exact","notes":"Migrated 2026-07-28","created_at":"2026-07-28T12:00:00.000000Z","updated_at":"2026-07-28T12:00:00.000000Z"}],"items":{"type":"object","properties":{"id":{"type":"integer","example":42},"portal_id":{"type":"integer","example":1},"source_path":{"type":"string","example":"/old-blog/hello"},"destination":{"type":"string","example":"/blog/hello"},"status_code":{"type":"integer","example":301},"match_type":{"type":"string","example":"exact"},"notes":{"type":"string","example":"Migrated 2026-07-28"},"created_at":{"type":"string","example":"2026-07-28T12:00:00.000000Z"},"updated_at":{"type":"string","example":"2026-07-28T12:00:00.000000Z"}}}}}}}}}},"tags":["Redirects"]},"post":{"summary":"Create a redirect","operationId":"createARedirect","description":"Registers a new source_path -> destination rule for the current portal.\nIf destination is null and status_code is 410, the rule serves a Gone\nresponse instead of a redirect.","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":42,"portal_id":1,"source_path":"/old-blog/hello","destination":"/blog/hello","status_code":301,"match_type":"exact","notes":null}},"properties":{"data":{"type":"object","properties":{"id":{"type":"integer","example":42},"portal_id":{"type":"integer","example":1},"source_path":{"type":"string","example":"/old-blog/hello"},"destination":{"type":"string","example":"/blog/hello"},"status_code":{"type":"integer","example":301},"match_type":{"type":"string","example":"exact"},"notes":{"type":"string","example":null,"nullable":true}}}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The given data was invalid.","errors":{"source_path":["source_path must start with /"]}},"properties":{"message":{"type":"string","example":"The given data was invalid."},"errors":{"type":"object","properties":{"source_path":{"type":"array","example":["source_path must start with /"],"items":{"type":"string"}}}}}}}}}},"tags":["Redirects"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"source_path":{"type":"string","description":"Path on this portal to intercept, must start with \"/\".","example":"/old-blog/hello"},"destination":{"type":"string","description":"Absolute URL or portal-relative path to redirect to. Leave null for a 410 Gone.","example":"/blog/hello"},"status_code":{"type":"integer","description":"HTTP code. One of 301, 302, 307, 308, 410. Defaults to 301.","example":301},"notes":{"type":"string","description":"Internal note visible in the admin UI.","example":"Migrated from Beamer 2026-07-28"}},"required":["source_path"]}}}}}},"/api/v1/redirects/{id}":{"patch":{"summary":"Update a redirect","operationId":"updateARedirect","description":"Partial update: any omitted field keeps its current value.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":42,"source_path":"/old-blog/hello","destination":"/blog/hello","status_code":301}},"properties":{"data":{"type":"object","properties":{"id":{"type":"integer","example":42},"source_path":{"type":"string","example":"/old-blog/hello"},"destination":{"type":"string","example":"/blog/hello"},"status_code":{"type":"integer","example":301}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\PortalRedirect] 42"},"properties":{"message":{"type":"string","example":"No query results for model [App\\PortalRedirect] 42"}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The given data was invalid.","errors":{"source_path":["source_path already exists"]}},"properties":{"message":{"type":"string","example":"The given data was invalid."},"errors":{"type":"object","properties":{"source_path":{"type":"array","example":["source_path already exists"],"items":{"type":"string"}}}}}}}}}},"tags":["Redirects"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"source_path":{"type":"string","description":"Updated source path (must start with \"/\").","example":"/old-blog/hello"},"destination":{"type":"string","description":"Updated destination URL or portal-relative path.","example":"/blog/hello"},"status_code":{"type":"integer","description":"301, 302, 307, 308 or 410.","example":301},"notes":{"type":"string","description":"Internal note.","example":"Fixed typo in destination"}}}}}}},"delete":{"summary":"Delete a redirect","operationId":"deleteARedirect","description":"","parameters":[],"responses":{"200":{"description":"","content":{"text/plain":{"schema":{"type":"string","example":"null"}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\PortalRedirect] 42"},"properties":{"message":{"type":"string","example":"No query results for model [App\\PortalRedirect] 42"}}}}}}},"tags":["Redirects"]},"parameters":[{"in":"path","name":"id","description":"The redirect id.","example":42,"required":true,"schema":{"type":"integer"}}]},"/api/v1/redirects/bulk-delete":{"post":{"summary":"Bulk delete redirects","operationId":"bulkDeleteRedirects","description":"Deletes every rule whose id is in the ids list AND belongs to the\ncurrent portal (cross-portal ids are silently ignored by the model's\nglobal scope).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"deleted":3},"properties":{"deleted":{"type":"integer","example":3}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No ids provided."},"properties":{"message":{"type":"string","example":"No ids provided."}}}}}}},"tags":["Redirects"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"ids":{"type":"array","description":"List of redirect IDs to delete.","example":[42,43,44],"items":{"type":"integer"}}},"required":["ids"]}}}}}},"/api/v1/redirects/csv/preview":{"post":{"summary":"Preview a CSV import (no writes).","operationId":"previewACSVImportnoWrites","description":"Parses the CSV and returns per-row diagnostics (new / update / conflict\n/ invalid) without persisting anything. Accepts either a raw csv body\nfield or a multipart file upload under `file`.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"rows":[{"source_path":"/old","destination":"/new","status_code":301,"action":"create"},{"source_path":"/other","destination":"/other-new","status_code":301,"action":"update"}],"summary":{"create":1,"update":1,"conflict":0,"invalid":0}},"properties":{"rows":{"type":"array","example":[{"source_path":"/old","destination":"/new","status_code":301,"action":"create"},{"source_path":"/other","destination":"/other-new","status_code":301,"action":"update"}],"items":{"type":"object","properties":{"source_path":{"type":"string","example":"/old"},"destination":{"type":"string","example":"/new"},"status_code":{"type":"integer","example":301},"action":{"type":"string","example":"create"}}}},"summary":{"type":"object","properties":{"create":{"type":"integer","example":1},"update":{"type":"integer","example":1},"conflict":{"type":"integer","example":0},"invalid":{"type":"integer","example":0}}}}}}}}},"tags":["Redirects"],"requestBody":{"required":false,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"csv":{"type":"string","description":"Raw CSV content (columns: source, destination, status_code, notes).","example":"source,destination,status_code,notes\\n/old,/new,301,"},"file":{"type":"string","format":"binary","description":"A CSV file upload. Takes precedence over the csv field when both are supplied."}}}}}}}},"/api/v1/redirects/csv/import":{"post":{"summary":"Execute a CSV import (transactional).","operationId":"executeACSVImporttransactional","description":"Persists every valid row in the CSV. Runs inside a DB transaction so a\nmid-file failure rolls back all writes. Existing source_path values are\noverwritten (upsert semantics).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"created":12,"updated":3,"total":15},"properties":{"created":{"type":"integer","example":12},"updated":{"type":"integer","example":3},"total":{"type":"integer","example":15}}}}}}},"tags":["Redirects"],"requestBody":{"required":false,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"csv":{"type":"string","description":"Raw CSV content.","example":"source,destination,status_code,notes\\n/old,/new,301,"},"file":{"type":"string","format":"binary","description":"A CSV file upload. Takes precedence over the csv field."}}}}}}}},"/api/v1/redirects/csv/export":{"get":{"summary":"Export all redirects as CSV","operationId":"exportAllRedirectsAsCSV","description":"Streams a CSV in the same column order the importer accepts, so an\nexported file can be re-imported (or edited in a spreadsheet and\nre-imported) without reshaping. Columns: source, destination,\nstatus_code, notes.","parameters":[],"responses":{"200":{"description":"CSV download","content":{"application/json":{"schema":{"type":"string","example":"source,destination,status_code,notes\n/old-blog/hello,/blog/hello,301,\"Migrated 2026-07-28\"\n"}}}}},"tags":["Redirects"]}},"/api/v1/sections":{"get":{"summary":"List sections","operationId":"listSections","description":"Retrieves sections in the portal. Pass `tab_id` to scope to a single\nboard and/or `parent_id` to fetch children of a section. With no\nfilters, returns every section in the portal across all boards.\n\nNOTE: sections are primarily used on knowledgebase boards (where they group\narticles into folders). On voting/changelog boards sections are optional and\noften unused. To create a KB article you MUST provide a `section_id`; other\nboard types treat `section_id` as optional.\nNOTE: sections nest via `parent_id`. Pass `parent_id` to walk one level down,\nor omit both `tab_id` and `parent_id` to list every section in the portal.\nNOTE: results are scoped to the current portal automatically via the model's\nglobal scope; you cannot fetch sections from another tenant.","parameters":[{"in":"query","name":"tab_id","description":"Scope to a single board.","example":1,"required":false,"schema":{"type":"integer","description":"Scope to a single board.","example":1}},{"in":"query","name":"parent_id","description":"Scope to children of a given section.","example":5,"required":false,"schema":{"type":"integer","description":"Scope to children of a given section.","example":5}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":3761,"tab_id":1413,"title":"eius et","sync_slug":null,"description":null,"icon":null,"parent_id":null,"order":66,"created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z","is_hidden_for_users":false,"color":null,"translations":null,"posts_count":null}},"properties":{"data":{"type":"object","properties":{"id":{"type":"integer","example":3761},"tab_id":{"type":"integer","example":1413},"title":{"type":"string","example":"eius et"},"sync_slug":{"type":"string","example":null,"nullable":true},"description":{"type":"string","example":null,"nullable":true},"icon":{"type":"string","example":null,"nullable":true},"parent_id":{"type":"string","example":null,"nullable":true},"order":{"type":"integer","example":66},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"updated_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"is_hidden_for_users":{"type":"boolean","example":false},"color":{"type":"string","example":null,"nullable":true},"translations":{"type":"string","example":null,"nullable":true},"posts_count":{"type":"string","example":null,"nullable":true}}}}}}}}},"tags":["Sections"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"tab_id":{"type":"integer","description":"Must match an existing stored value.","example":16,"nullable":true},"parent_id":{"type":"integer","description":"Must match an existing stored value.","example":16,"nullable":true},"all":{"type":"string","description":"","example":"1","enum":["true","false","0","1"],"nullable":true}}}}}},"x-mcp":true,"x-mcp-name":"sections.list"},"post":{"summary":"Create section","operationId":"createSection","description":"Creates a new section within a specified board (`tab_id`). Sections group posts\nwithin a board and are the primary organizing unit for knowledgebase boards.\n\nCRITICAL: on a knowledgebase board, every article REQUIRES a `section_id`. Create\nthe section BEFORE creating KB posts, otherwise post creation will fail. On voting\nand changelog boards sections are optional.\nNOTE: for bulk KB edits prefer the sync workflow (`/api/v1/knowledgebase/sync/*`)\nover creating sections + posts individually — the sync handles internal links and\nimage references that per-call creation misses.\nNOTE: `parent_id` nests this section under another section (same board only).\n`after_id` controls sibling ordering: the new section is placed immediately after\nthe referenced section and following siblings shift down.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":3762,"tab_id":1414,"title":"perspiciatis quo","sync_slug":null,"description":null,"icon":null,"parent_id":null,"order":99,"created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z","is_hidden_for_users":false,"color":null,"translations":null,"posts_count":null}},"properties":{"data":{"type":"object","properties":{"id":{"type":"integer","example":3762},"tab_id":{"type":"integer","example":1414},"title":{"type":"string","example":"perspiciatis quo"},"sync_slug":{"type":"string","example":null,"nullable":true},"description":{"type":"string","example":null,"nullable":true},"icon":{"type":"string","example":null,"nullable":true},"parent_id":{"type":"string","example":null,"nullable":true},"order":{"type":"integer","example":99},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"updated_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"is_hidden_for_users":{"type":"boolean","example":false},"color":{"type":"string","example":null,"nullable":true},"translations":{"type":"string","example":null,"nullable":true},"posts_count":{"type":"string","example":null,"nullable":true}}}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The given data was invalid.","errors":{"title":["The title has already been taken."]}},"properties":{"message":{"type":"string","example":"The given data was invalid."},"errors":{"type":"object","properties":{"title":{"type":"array","example":["The title has already been taken."],"items":{"type":"string"}}}}}}}}}},"tags":["Sections"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","description":"The section title (2-60 chars).","example":"Feature Requests"},"description":{"type":"string","description":"Section description (max 1000 chars).","example":"Submit and track feature requests here.","nullable":true},"icon":{"type":"string","description":"Icon identifier (max 50 chars).","example":"lightbulb","nullable":true},"tab_id":{"type":"integer","description":"The board this section belongs to. Sections cannot move between boards after creation.","example":1},"parent_id":{"type":"integer","description":"Parent section ID for nested sections. Must belong to the same board.","example":5,"nullable":true},"after_id":{"type":"integer","description":"Insert immediately after this sibling section (reorders following siblings).","example":3,"nullable":true},"color":{"type":"string","description":"Hex color code (max 7 chars, format `#RRGGBB`).","example":"#E74C3C","nullable":true}},"required":["title","tab_id"]}}}},"x-mcp":true,"x-mcp-name":"sections.create"}},"/api/v1/sections/{id}":{"put":{"summary":"Update Section","operationId":"updateSection","description":"Modifies the details of an existing section.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":3763,"tab_id":1415,"title":"ab provident","sync_slug":null,"description":null,"icon":null,"parent_id":null,"order":43,"created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z","is_hidden_for_users":false,"color":null,"translations":null,"posts_count":null}},"properties":{"data":{"type":"object","properties":{"id":{"type":"integer","example":3763},"tab_id":{"type":"integer","example":1415},"title":{"type":"string","example":"ab provident"},"sync_slug":{"type":"string","example":null,"nullable":true},"description":{"type":"string","example":null,"nullable":true},"icon":{"type":"string","example":null,"nullable":true},"parent_id":{"type":"string","example":null,"nullable":true},"order":{"type":"integer","example":43},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"updated_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"is_hidden_for_users":{"type":"boolean","example":false},"color":{"type":"string","example":null,"nullable":true},"translations":{"type":"string","example":null,"nullable":true},"posts_count":{"type":"string","example":null,"nullable":true}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Section] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Section] ..."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The given data was invalid.","errors":{"title":["The title must be given."]}},"properties":{"message":{"type":"string","example":"The given data was invalid."},"errors":{"type":"object","properties":{"title":{"type":"array","example":["The title must be given."],"items":{"type":"string"}}}}}}}}}},"tags":["Sections"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","description":"The section title (2-60 chars).","example":"Feature Requests"},"description":{"type":"string","description":"Section description (max 1000 chars).","example":"Updated description for this section.","nullable":true},"icon":{"type":"string","description":"Icon identifier (max 50 chars).","example":"lightbulb","nullable":true},"is_hidden_for_users":{"type":"boolean","description":"Hide this section from end users.","example":false,"nullable":true},"parent_id":{"type":"integer","description":"Parent section ID.","example":5,"nullable":true},"color":{"type":"string","description":"Hex color code.","example":"#E74C3C","nullable":true}},"required":["title"]}}}},"x-mcp":true,"x-mcp-name":"sections.update"},"delete":{"summary":"Delete Section","operationId":"deleteSection","description":"Removes a specified section from the database.\n\nCRITICAL: If the section has posts or child sections, the first call returns 422 with\n`posts_count` + `children_count`. Retry with `delete_posts=true` to CASCADE DELETE all\nposts and child sections. Destructive and irreversible — surface the counts to the user\nbefore retrying.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":[],"properties":[]}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Section] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Section] ..."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Section has content that will be deleted","posts_count":5,"children_count":2},"properties":{"message":{"type":"string","example":"Section has content that will be deleted"},"posts_count":{"type":"integer","example":5},"children_count":{"type":"integer","example":2}}}}}}},"tags":["Sections"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"delete_posts":{"type":"boolean","description":"Confirm cascade delete of posts + child sections when section is non-empty.","example":false}}}}}},"x-mcp":true,"x-mcp-name":"sections.delete"},"parameters":[{"in":"path","name":"id","description":"The section ID.","example":12,"required":true,"schema":{"type":"integer"}}]},"/api/v1/sections/reorder":{"post":{"summary":"Reorder Sections","operationId":"reorderSections","description":"Adjusts the order of sections within a specified tab. Accepts the full desired ordering\nas a comma-separated list of section IDs. Pass `parent_id` when reordering child sections\n(nested); omit it when reordering top-level sections of the tab.\n\nNOTE: `order` is a CSV string, not an array — e.g. `\"12,8,15,3\"`. Every ID in the list\nmust belong to the target tab (and parent, if provided), otherwise the whole call 404s.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Sections reordered successfully."},"properties":{"message":{"type":"string","example":"Sections reordered successfully."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The given data was invalid.","errors":{"order":["The order field is required."],"tab_id":["The tab id field is required."]}},"properties":{"message":{"type":"string","example":"The given data was invalid."},"errors":{"type":"object","properties":{"order":{"type":"array","example":["The order field is required."],"items":{"type":"string"}},"tab_id":{"type":"array","example":["The tab id field is required."],"items":{"type":"string"}}}}}}}}}},"tags":["Sections"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"string","description":"Comma-separated section IDs in the desired order.","example":"12,8,15,3"},"tab_id":{"type":"integer","description":"The tab these sections belong to.","example":42},"parent_id":{"type":"integer","description":"Parent section ID when reordering child sections. Omit for top-level.","example":5,"nullable":true}},"required":["order","tab_id"]}}}},"x-mcp":true,"x-mcp-name":"sections.reorder"}},"/api/v1/sections/{parentId}/children":{"get":{"summary":"Get Child Sections","operationId":"getChildSections","description":"Retrieves all child sections for a given parent section, ordered by their `order` field.\nUseful when building/navigating a KB tree — call `sections.list` for a tab's top-level,\nthen this to walk into any parent.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":1,"title":"Child Section 1","description":"This is a child section","icon":"folder","parent_id":5,"order":1},{"id":2,"title":"Child Section 2","description":"Another child section","icon":"file","parent_id":5,"order":2}]},"properties":{"data":{"type":"array","example":[{"id":1,"title":"Child Section 1","description":"This is a child section","icon":"folder","parent_id":5,"order":1},{"id":2,"title":"Child Section 2","description":"Another child section","icon":"file","parent_id":5,"order":2}],"items":{"type":"object","properties":{"id":{"type":"integer","example":1},"title":{"type":"string","example":"Child Section 1"},"description":{"type":"string","example":"This is a child section"},"icon":{"type":"string","example":"folder"},"parent_id":{"type":"integer","example":5},"order":{"type":"integer","example":1}}}}}}}}}},"tags":["Sections"],"x-mcp":true,"x-mcp-name":"sections.children"},"parameters":[{"in":"path","name":"parentId","description":"The parent section ID.","example":5,"required":true,"schema":{"type":"integer"}}]},"/api/v1/statuses":{"get":{"summary":"List statuses","operationId":"listStatuses","description":"Lists all statuses across every board in the portal. Each status belongs to\nexactly one board (via `tab_id`) — statuses are not shared between boards.\n\nCRITICAL: to \"move a post to another board\" you change its `status` (on PATCH\n/posts/{key}) to a status whose `tab_id` matches the destination board. There\nis no direct board-transfer action; the status IS the move.\nNOTE: the response includes each status's `tab_id` and eager-loaded `tab` and\n`sections` — filter client-side by `tab_id` to get the statuses for one board.\nNOTE: a post always has exactly one status. Statuses with `is_hidden_for_users=true`\nhide their posts from end users but keep them visible to admins.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":null,"name":"consequatur","color":"#692b45","order":null,"tab_id":1406,"tab_name":"adipisci molestias","tab":{"id":1406,"nav_title":"adipisci molestias","type":"list"},"text_color":"#ffc1db","is_hidden_for_users":null},{"id":null,"name":"fugit","color":"#b58b0f","order":null,"tab_id":1408,"tab_name":"voluptate accusamus","tab":{"id":1408,"nav_title":"voluptate accusamus","type":"list"},"text_color":"#ffffa5","is_hidden_for_users":null}]},"properties":{"data":{"type":"array","example":[{"id":null,"name":"consequatur","color":"#692b45","order":null,"tab_id":1406,"tab_name":"adipisci molestias","tab":{"id":1406,"nav_title":"adipisci molestias","type":"list"},"text_color":"#ffc1db","is_hidden_for_users":null},{"id":null,"name":"fugit","color":"#b58b0f","order":null,"tab_id":1408,"tab_name":"voluptate accusamus","tab":{"id":1408,"nav_title":"voluptate accusamus","type":"list"},"text_color":"#ffffa5","is_hidden_for_users":null}],"items":{"type":"object","properties":{"id":{"type":"string","example":null,"nullable":true},"name":{"type":"string","example":"consequatur"},"color":{"type":"string","example":"#692b45"},"order":{"type":"string","example":null,"nullable":true},"tab_id":{"type":"integer","example":1406},"tab_name":{"type":"string","example":"adipisci molestias"},"tab":{"type":"object","properties":{"id":{"type":"integer","example":1406},"nav_title":{"type":"string","example":"adipisci molestias"},"type":{"type":"string","example":"list"}}},"text_color":{"type":"string","example":"#ffc1db"},"is_hidden_for_users":{"type":"string","example":null,"nullable":true}}}}}}}}}},"tags":["Statuses"],"x-mcp":true,"x-mcp-name":"statuses.list"},"post":{"summary":"Create status","operationId":"createStatus","description":"Creates a new status.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":null,"name":"iure","color":"#aa8e3f","order":null,"tab_id":1410,"tab_name":"dolores enim","tab":{"id":1410,"nav_title":"dolores enim","type":"list"},"text_color":"#ffffd5","is_hidden_for_users":null}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":null,"nullable":true},"name":{"type":"string","example":"iure"},"color":{"type":"string","example":"#aa8e3f"},"order":{"type":"string","example":null,"nullable":true},"tab_id":{"type":"integer","example":1410},"tab_name":{"type":"string","example":"dolores enim"},"tab":{"type":"object","properties":{"id":{"type":"integer","example":1410},"nav_title":{"type":"string","example":"dolores enim"},"type":{"type":"string","example":"list"}}},"text_color":{"type":"string","example":"#ffffd5"},"is_hidden_for_users":{"type":"string","example":null,"nullable":true}}}}}}}},"422":{"description":"","content":{"text/plain":{"schema":{"type":"string","example":"{\n \"message\": \"The given data was invalid.\",\n \"errors\": {\n \"name\": [\n     \"The name has already been taken.\"\n ]\n}"}}}}},"tags":["Statuses"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The status name (2-60 chars).","example":"In Progress"},"color":{"type":"string","description":"Hex color code for the status.","example":"#3498DB","nullable":true},"tab_id":{"type":"integer","description":"The tab this status belongs to.","example":1,"nullable":true},"is_hidden_for_users":{"type":"boolean","description":"","example":true,"nullable":true}},"required":["name"]}}}}}},"/api/v1/statuses/{id}":{"put":{"summary":"Update status","operationId":"updateStatus","description":"Updates the details of a status.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":null,"name":"odit","color":"#559641","order":null,"tab_id":1412,"tab_name":"aut dolores","tab":{"id":1412,"nav_title":"aut dolores","type":"list"},"text_color":"#ebffd7","is_hidden_for_users":null}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":null,"nullable":true},"name":{"type":"string","example":"odit"},"color":{"type":"string","example":"#559641"},"order":{"type":"string","example":null,"nullable":true},"tab_id":{"type":"integer","example":1412},"tab_name":{"type":"string","example":"aut dolores"},"tab":{"type":"object","properties":{"id":{"type":"integer","example":1412},"nav_title":{"type":"string","example":"aut dolores"},"type":{"type":"string","example":"list"}}},"text_color":{"type":"string","example":"#ebffd7"},"is_hidden_for_users":{"type":"string","example":null,"nullable":true}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Status] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Status] ..."}}}}}},"422":{"description":"","content":{"text/plain":{"schema":{"type":"string","example":"{\n \"message\": \"The given data was invalid.\",\n \"errors\": {\n \"name\": [\n     \"The name has already been taken.\"\n ]\n}"}}}}},"tags":["Statuses"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"The status name (2-60 chars).","example":"In Progress"},"color":{"type":"string","description":"Hex color code for the status.","example":"#3498DB"},"tab_id":{"type":"integer","description":"The tab this status belongs to.","example":1,"nullable":true},"is_hidden_for_users":{"type":"boolean","description":"","example":false}}}}}}},"delete":{"summary":"Delete status","operationId":"deleteStatus","description":"Deletes a specified status.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":[],"properties":[]}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Status] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Status] ..."}}}}}}},"tags":["Statuses"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"new_status_id":{"type":"integer","description":"If the status has posts, reassign them to this status ID.","example":2}}}}}}},"parameters":[{"in":"path","name":"id","description":"The ID of the status.","example":16,"required":true,"schema":{"type":"integer"}}]},"/api/v1/tabs/{key}":{"get":{"summary":"Get tab","operationId":"getTab","description":"Retrieves the details of a specific tab by its slug.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":1,"name":"General","slug":"general","created_at":"2023-01-01T00:00:00Z","updated_at":"2023-01-01T00:00:00Z"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"General"},"slug":{"type":"string","example":"general"},"created_at":{"type":"string","example":"2023-01-01T00:00:00Z"},"updated_at":{"type":"string","example":"2023-01-01T00:00:00Z"}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Tab] general"},"properties":{"message":{"type":"string","example":"No query results for model [App\\Tab] general"}}}}}}},"tags":["Tabs"],"security":[]},"parameters":[{"in":"path","name":"key","description":"The slug of the tab.","example":"general","required":true,"schema":{"type":"string"}}]},"/api/v1/tabs":{"get":{"summary":"List tabs","operationId":"listTabs","description":"Retrieves a collection of all tabs (boards) on the current portal, ordered by\ntheir display order. Use this to discover board ids, slugs, and types before\ncreating or moving posts.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":1,"name":"General","slug":"general","type":"list","created_at":"2023-01-01T00:00:00Z","updated_at":"2023-01-01T00:00:00Z"},{"id":2,"name":"Support","slug":"support","type":"knowledgebase","created_at":"2023-01-02T00:00:00Z","updated_at":"2023-01-02T00:00:00Z"}]},"properties":{"data":{"type":"array","example":[{"id":1,"name":"General","slug":"general","type":"list","created_at":"2023-01-01T00:00:00Z","updated_at":"2023-01-01T00:00:00Z"},{"id":2,"name":"Support","slug":"support","type":"knowledgebase","created_at":"2023-01-02T00:00:00Z","updated_at":"2023-01-02T00:00:00Z"}],"items":{"type":"object","properties":{"id":{"type":"integer","example":1},"name":{"type":"string","example":"General"},"slug":{"type":"string","example":"general"},"type":{"type":"string","example":"list"},"created_at":{"type":"string","example":"2023-01-01T00:00:00Z"},"updated_at":{"type":"string","example":"2023-01-01T00:00:00Z"}}}}}}}}}},"tags":["Tabs"],"x-mcp":true,"x-mcp-name":"tabs.list"}},"/api/v1/tags":{"get":{"summary":"List tags","operationId":"listTags","description":"Lists all tags of your portal. Tags are internal/admin-only labels used to\norganize posts behind the scenes (compare with Categories, which ARE user-visible).\nA post can have multiple tags.\n\nNOTE: results are scoped to the current portal automatically. Tags are portal-wide,\nnot tied to a specific board.\nNOTE: end users of the portal never see tag names; use categories for anything\nyou want the audience to filter by.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":"253dd7e3-bce8-456e-a2d7-21853d6dc80d","portal_id":1043,"tag":"aut","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"},{"id":"ab5f2f59-d5d2-4524-9f37-b80fa4bbbf0b","portal_id":1044,"tag":"iure","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"}]},"properties":{"data":{"type":"array","example":[{"id":"253dd7e3-bce8-456e-a2d7-21853d6dc80d","portal_id":1043,"tag":"aut","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"},{"id":"ab5f2f59-d5d2-4524-9f37-b80fa4bbbf0b","portal_id":1044,"tag":"iure","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"}],"items":{"type":"object","properties":{"id":{"type":"string","example":"253dd7e3-bce8-456e-a2d7-21853d6dc80d"},"portal_id":{"type":"integer","example":1043},"tag":{"type":"string","example":"aut"},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"updated_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"}}}}}}}}}},"tags":["Tags"],"x-mcp":true,"x-mcp-name":"tags.list"},"post":{"summary":"Create tag","operationId":"createTag","description":"Creates a new tag in your portal. Tags are admin-only internal labels — end users\nnever see the tag name, so you can name them for internal triage (e.g. \"needs-triage\",\n\"customer-request\", \"q3-target\") without worrying about public copy.\n\nNOTE: the field is `tag` (not `name`). Names must be unique per portal.\nNOTE: to attach tags to a post, pass `tags[]` (array of tag IDs) on POST /posts.\nNOTE: if you want a user-visible taxonomy instead, create a Category, not a Tag.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"209f10b6-ba3f-4452-89ac-8cdb5f52f12b","portal_id":1045,"tag":"aut","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"209f10b6-ba3f-4452-89ac-8cdb5f52f12b"},"portal_id":{"type":"integer","example":1045},"tag":{"type":"string","example":"aut"},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"updated_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"}}}}}}}},"422":{"description":"","content":{"text/plain":{"schema":{"type":"string","example":"{\n \"message\": \"The given data was invalid.\",\n \"errors\": {\n \"tag\": [\n     \"The tag has already been taken.\"\n ]\n}"}}}}},"tags":["Tags"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"type":"string","description":"The tag name (2-60 chars, unique within the portal).","example":"Feature Request"}},"required":["tag"]}}}},"x-mcp":true,"x-mcp-name":"tags.create"}},"/api/v1/tags/{id}":{"put":{"summary":"Update tag","operationId":"updateTag","description":"Updates the details of a tag.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"e89eecfc-8a3f-41b6-aa41-e58cddf30998","portal_id":1046,"tag":"aut","created_at":"2026-08-19T12:15:05.000000Z","updated_at":"2026-08-19T12:15:05.000000Z"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"e89eecfc-8a3f-41b6-aa41-e58cddf30998"},"portal_id":{"type":"integer","example":1046},"tag":{"type":"string","example":"aut"},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"},"updated_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Tag] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Tag] ..."}}}}}},"422":{"description":"","content":{"text/plain":{"schema":{"type":"string","example":"{\n \"message\": \"The given data was invalid.\",\n \"errors\": {\n \"tag\": [\n     \"The tag has already been taken.\"\n ]\n}"}}}}},"tags":["Tags"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"tag":{"type":"string","description":"The tag name (2-60 chars).","example":"Bug Report"}},"required":["tag"]}}}}},"delete":{"summary":"Delete tag","operationId":"deleteTag","description":"Deletes a specified tag.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":[],"properties":[]}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Tag] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Tag] ..."}}}}}}},"tags":["Tags"]},"parameters":[{"in":"path","name":"id","description":"The ID of the tag.","example":16,"required":true,"schema":{"type":"integer"}}]},"/api/v1/users/find_by_email":{"get":{"summary":"Find by email","operationId":"findByEmail","description":"Looks up a user by their email address and returns the full UserResource (including both the integer `id` and the UUID `key`). Use the returned `key` for subsequent users.get / users.update calls.","parameters":[{"in":"query","name":"email","description":"The user's email address.","example":"jane@example.com","required":true,"schema":{"type":"string","description":"The user's email address.","example":"jane@example.com"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"e36a2b6a-a70a-424c-bece-7dab0a6c4c79","database_id":4678,"name":"Rowan","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4678","avatar_small_url":"https://www.gravatar.com/avatar/a97fddea1c907f31cdcdab0d77c4cee3?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Rowan/60/A2E1DB/0c4b45","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:04.000000Z"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"e36a2b6a-a70a-424c-bece-7dab0a6c4c79"},"database_id":{"type":"integer","example":4678},"name":{"type":"string","example":"Rowan"},"role":{"type":"string","example":"member"},"profile_url":{"type":"string","example":"{YOUR-PORTAL-URL}/user/profile/4678"},"avatar_small_url":{"type":"string","example":"https://www.gravatar.com/avatar/a97fddea1c907f31cdcdab0d77c4cee3?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Rowan/60/A2E1DB/0c4b45"},"counter_votes":{"type":"integer","example":0},"counter_comments":{"type":"integer","example":0},"counter_posts":{"type":"integer","example":0},"counter_comment_votes":{"type":"integer","example":0},"created_at":{"type":"string","example":"2026-08-19T12:15:04.000000Z"}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"User not found"},"properties":{"message":{"type":"string","example":"User not found"}}}}}},"422":{"description":"","content":{"text/plain":{"schema":{"type":"string","example":"{\n \"message\": \"The given data was invalid.\",\n \"errors\": {\n \"role\": [\n     \"The email must be a valid email address.\"\n ]\n}"}}}}},"tags":["Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"The user's email. Must be a valid email address. Must not be greater than 191 characters.","example":"gbailey@example.net"}},"required":["email"]}}}},"x-mcp":true,"x-mcp-name":"users.findByEmail"}},"/api/v1/users/segment-metadata-keys":{"get":{"summary":"Get unique segment metadata keys","operationId":"getUniqueSegmentMetadataKeys","description":"Returns a list of all unique keys currently used in `segment_metadata`\nacross all users on the portal. Useful for building dynamic filter UIs\nagainst an unknown segment vocabulary.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"example":["segment_1","segment_2","industry","region"]}}}}},"tags":["Users"]}},"/api/v1/users/plan-types":{"get":{"summary":"Get unique plan types","operationId":"getUniquePlanTypes","description":"Returns a list of all distinct non-null `customer_plan_type` values across\nusers on the portal. Useful for building plan filter dropdowns.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"},"example":["Enterprise","Pro","Starter","Free"]}}}}},"tags":["Users"]}},"/api/v1/users/groups":{"get":{"summary":"Get all groups","operationId":"getAllGroups","description":"Returns every group on the portal. Convenience endpoint that mirrors\n`GET /api/v1/groups` and is kept here so user-segmentation UIs can fetch\nusers and groups from the same controller namespace.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":"72a23f6d-3044-4e48-ad4e-7883d13fe072","portal_id":1018,"name":"Aut","description":"Quidem nostrum qui commodi incidunt iure odit.","created_at":"2026-08-19T12:15:04.000000Z","updated_at":"2026-08-19T12:15:04.000000Z"},{"id":"2b4a41c5-2087-43bc-8f5b-89177da40b13","portal_id":1019,"name":"Autem","description":"Consequatur aut dolores enim non facere tempora.","created_at":"2026-08-19T12:15:04.000000Z","updated_at":"2026-08-19T12:15:04.000000Z"}]},"properties":{"data":{"type":"array","example":[{"id":"72a23f6d-3044-4e48-ad4e-7883d13fe072","portal_id":1018,"name":"Aut","description":"Quidem nostrum qui commodi incidunt iure odit.","created_at":"2026-08-19T12:15:04.000000Z","updated_at":"2026-08-19T12:15:04.000000Z"},{"id":"2b4a41c5-2087-43bc-8f5b-89177da40b13","portal_id":1019,"name":"Autem","description":"Consequatur aut dolores enim non facere tempora.","created_at":"2026-08-19T12:15:04.000000Z","updated_at":"2026-08-19T12:15:04.000000Z"}],"items":{"type":"object","properties":{"id":{"type":"string","example":"72a23f6d-3044-4e48-ad4e-7883d13fe072"},"portal_id":{"type":"integer","example":1018},"name":{"type":"string","example":"Aut"},"description":{"type":"string","example":"Quidem nostrum qui commodi incidunt iure odit."},"created_at":{"type":"string","example":"2026-08-19T12:15:04.000000Z"},"updated_at":{"type":"string","example":"2026-08-19T12:15:04.000000Z"}}}}}}}}}},"tags":["Users"]}},"/api/v1/users/bulk-block":{"post":{"summary":"Bulk block users","operationId":"bulkBlockUsers","description":"Sets `is_blocked = true` on multiple users in a single call. Accepts a\nmix of user UUID keys and integer database ids in the same payload.\nUnknown ids are silently skipped (reflected in the returned `count`).\nSessions cannot block themselves; the actor's own id is dropped if\npresent.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"success":true,"message":"3 user(s) blocked successfully","count":3},"properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"3 user(s) blocked successfully"},"count":{"type":"integer","example":3}}}}}}},"tags":["Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_ids":{"type":"array","description":"List of user UUIDs or integer ids to block.","example":["8cbfbfd6-ecc3-4ec3-8b31-ea76b11c1f88"],"items":{"type":"string"}}},"required":["user_ids"]}}}}}},"/api/v1/users/bulk-unblock":{"post":{"summary":"Bulk unblock users","operationId":"bulkUnblockUsers","description":"Sets `is_blocked = false` on multiple users in a single call. Accepts a\nmix of user UUID keys and integer database ids. Unknown ids are\nsilently skipped (reflected in the returned `count`).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"success":true,"message":"3 user(s) unblocked successfully","count":3},"properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"3 user(s) unblocked successfully"},"count":{"type":"integer","example":3}}}}}}},"tags":["Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_ids":{"type":"array","description":"List of user UUIDs or integer ids to unblock.","example":["8cbfbfd6-ecc3-4ec3-8b31-ea76b11c1f88"],"items":{"type":"string"}}},"required":["user_ids"]}}}}}},"/api/v1/users/bulk-delete":{"post":{"summary":"Bulk delete users","operationId":"bulkDeleteUsers","description":"Soft-deletes multiple users in a single call. Accepts a mix of UUID keys\nand integer database ids. Unknown ids are silently skipped (reflected\nin the returned `count`). Sessions cannot delete themselves; the\nactor's own id is dropped if present. Personal data (name, email,\npassword) is nulled out on the user row, and audit-log PII is purged.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"success":true,"message":"3 user(s) deleted successfully","count":3},"properties":{"success":{"type":"boolean","example":true},"message":{"type":"string","example":"3 user(s) deleted successfully"},"count":{"type":"integer","example":3}}}}}}},"tags":["Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"user_ids":{"type":"array","description":"List of user UUIDs or integer ids to delete.","example":["8cbfbfd6-ecc3-4ec3-8b31-ea76b11c1f88"],"items":{"type":"string"}},"delete_data":{"type":"boolean","description":"Set true to also delete all user content (posts, comments, votes). When false, content remains with [User Deleted] as the author.","example":false,"nullable":true}},"required":["user_ids"]}}}}}},"/api/v1/users":{"delete":{"summary":"Delete user by email or SSO UID","operationId":"deleteUserByEmailOrSSOUID","description":"Deletes a user identified by email address or SSO UID, provided as a query parameter.\nExactly one of `email` or `sso_uid` must be provided. Useful when you only\nhave the upstream identifier and not the ProductLift UUID key.","parameters":[{"in":"query","name":"email","description":"The user's email address.","example":"user@example.com","required":false,"schema":{"type":"string","description":"The user's email address.","example":"user@example.com"}},{"in":"query","name":"sso_uid","description":"The user's SSO UID.","example":"sso-12345","required":false,"schema":{"type":"string","description":"The user's SSO UID.","example":"sso-12345"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":[],"properties":[]}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\User] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\User] ..."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"Provide exactly one of email or sso_uid."},"properties":{"message":{"type":"string","example":"Provide exactly one of email or sso_uid."}}}}}}},"tags":["Users"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"Must be a valid email address. Must not be greater than 191 characters.","example":"gbailey@example.net","nullable":true},"sso_uid":{"type":"string","description":"Must not be greater than 191 characters.","example":"m","nullable":true},"delete_all_data":{"type":"boolean","description":"Set true or 1 to also delete all content this user created (posts, comments). When false, content remains with [User Deleted] as the author.","example":false,"nullable":true}}}}}}},"get":{"summary":"List users","operationId":"listUsers","description":"Lists all users of your portal. Supports advanced filtering and sorting.","parameters":[{"in":"query","name":"limit","description":"Number of users to return (1-100, default 10 for simple pagination, 50 when advanced filters are used).","example":25,"required":false,"schema":{"type":"integer","description":"Number of users to return (1-100, default 10 for simple pagination, 50 when advanced filters are used).","example":25}},{"in":"query","name":"skip","description":"Number of users to skip (default 0).","example":0,"required":false,"schema":{"type":"integer","description":"Number of users to skip (default 0).","example":0}},{"in":"query","name":"order_by","description":"Sort field. Allowed: name, email, created_at, last_login_at, counter_votes, counter_posts, counter_comments, customer_mrr, customer_ltv, customer_signup_date, role.","example":"created_at","required":false,"schema":{"type":"string","description":"Sort field. Allowed: name, email, created_at, last_login_at, counter_votes, counter_posts, counter_comments, customer_mrr, customer_ltv, customer_signup_date, role.","example":"created_at"}},{"in":"query","name":"order_direction","description":"Sort direction (asc or desc).","example":"desc","required":false,"schema":{"type":"string","description":"Sort direction (asc or desc).","example":"desc"}},{"in":"query","name":"search","description":"Search by name or email.","example":"john","required":false,"schema":{"type":"string","description":"Search by name or email.","example":"john"}},{"in":"query","name":"role","description":"Filter by role. Only `admin` and `member` exist.","example":"member","required":false,"schema":{"type":"string","description":"Filter by role. Only `admin` and `member` exist.","example":"member"}},{"in":"query","name":"status","description":"Filter by status. Allowed: active, blocked, invited, verifying, in_acceptance, imported.","example":"active","required":false,"schema":{"type":"string","description":"Filter by status. Allowed: active, blocked, invited, verifying, in_acceptance, imported.","example":"active"}},{"in":"query","name":"group_id","description":"Filter by group IDs.","example":["1","2"],"required":false,"schema":{"type":"array","description":"Filter by group IDs.","example":["1","2"],"items":{"type":"string"}}},{"in":"query","name":"customer_status","description":"Filter by customer status. Allowed: active, churned, trial, prospect.","example":"active","required":false,"schema":{"type":"string","description":"Filter by customer status. Allowed: active, churned, trial, prospect.","example":"active"}},{"in":"query","name":"customer_plan_type","description":"Filter by plan type (free-form string matching values previously written via users.create/users.update).","example":"Enterprise","required":false,"schema":{"type":"string","description":"Filter by plan type (free-form string matching values previously written via users.create/users.update).","example":"Enterprise"}},{"in":"query","name":"saved_query_id","description":"Use a predefined saved query.","example":1,"required":false,"schema":{"type":"integer","description":"Use a predefined saved query.","example":1}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":"7f471b52-bde0-4ecf-b6f5-ac0117ec605e","database_id":4679,"name":"Lafayette","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4679","avatar_small_url":"https://www.gravatar.com/avatar/3913338b5e645403640f667bf0434110?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Lafayette/60/FFDBCC/694536","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:04.000000Z"},{"id":"ce2f312b-809e-4dd7-9a60-59b319780a17","database_id":4680,"name":"Amber","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4680","avatar_small_url":"https://www.gravatar.com/avatar/05ceb004ca178a37392257a23ea75618?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Amber/60/D4F0F0/3e5a5a","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:04.000000Z"}]},"properties":{"data":{"type":"array","example":[{"id":"7f471b52-bde0-4ecf-b6f5-ac0117ec605e","database_id":4679,"name":"Lafayette","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4679","avatar_small_url":"https://www.gravatar.com/avatar/3913338b5e645403640f667bf0434110?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Lafayette/60/FFDBCC/694536","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:04.000000Z"},{"id":"ce2f312b-809e-4dd7-9a60-59b319780a17","database_id":4680,"name":"Amber","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4680","avatar_small_url":"https://www.gravatar.com/avatar/05ceb004ca178a37392257a23ea75618?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Amber/60/D4F0F0/3e5a5a","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:04.000000Z"}],"items":{"type":"object","properties":{"id":{"type":"string","example":"7f471b52-bde0-4ecf-b6f5-ac0117ec605e"},"database_id":{"type":"integer","example":4679},"name":{"type":"string","example":"Lafayette"},"role":{"type":"string","example":"member"},"profile_url":{"type":"string","example":"{YOUR-PORTAL-URL}/user/profile/4679"},"avatar_small_url":{"type":"string","example":"https://www.gravatar.com/avatar/3913338b5e645403640f667bf0434110?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Lafayette/60/FFDBCC/694536"},"counter_votes":{"type":"integer","example":0},"counter_comments":{"type":"integer","example":0},"counter_posts":{"type":"integer","example":0},"counter_comment_votes":{"type":"integer","example":0},"created_at":{"type":"string","example":"2026-08-19T12:15:04.000000Z"}}}}}}}}}},"tags":["Users"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"type":"integer","description":"Pagination. Must be at least 1. Must not be greater than 100.","example":1,"nullable":true},"skip":{"type":"integer","description":"Must be at least 0.","example":39,"nullable":true},"order_by":{"type":"string","description":"Sorting.","example":"name","enum":["name","email","created_at","last_login_at","counter_votes","counter_posts","counter_comments","customer_mrr","customer_ltv","customer_signup_date","role"],"nullable":true},"order_direction":{"type":"string","description":"","example":"asc","enum":["asc","desc"],"nullable":true},"search":{"type":"string","description":"Filters. Must not be greater than 100 characters.","example":"g","nullable":true},"role":{"type":"string","description":"","example":"member","enum":["admin","member"],"nullable":true},"status":{"type":"string","description":"","example":"imported","enum":["active","blocked","invited","verifying","in_acceptance","imported"],"nullable":true},"group_id":{"type":"array","description":"Must match an existing stored value.","example":["architecto"],"items":{"type":"string"}},"customer_status":{"type":"string","description":"","example":"churned","enum":["active","churned","trial","prospect"],"nullable":true},"customer_plan_type":{"type":"string","description":"Must not be greater than 50 characters.","example":"n","nullable":true},"min_counter_votes":{"type":"integer","description":"Range filters. Must be at least 0.","example":84,"nullable":true},"max_counter_votes":{"type":"integer","description":"Must be at least 0.","example":12,"nullable":true},"min_counter_posts":{"type":"integer","description":"Must be at least 0.","example":77,"nullable":true},"max_counter_posts":{"type":"integer","description":"Must be at least 0.","example":8,"nullable":true},"min_mrr":{"type":"number","description":"Must be at least 0.","example":76,"nullable":true},"max_mrr":{"type":"number","description":"Must be at least 0.","example":60,"nullable":true},"min_ltv":{"type":"number","description":"Must be at least 0.","example":42,"nullable":true},"max_ltv":{"type":"number","description":"Must be at least 0.","example":37,"nullable":true},"created_condition":{"type":"string","description":"Date filters.","example":"before","enum":["after","before","between"],"nullable":true},"created_start":{"type":"string","description":"Must be a valid date.","example":"2026-08-19T12:15:04","nullable":true},"created_end":{"type":"string","description":"Must be a valid date.","example":"2026-08-19T12:15:04","nullable":true},"last_login_condition":{"type":"string","description":"","example":"after","enum":["after","before","between"],"nullable":true},"last_login_start":{"type":"string","description":"Must be a valid date.","example":"2026-08-19T12:15:04","nullable":true},"last_login_end":{"type":"string","description":"Must be a valid date.","example":"2026-08-19T12:15:04","nullable":true},"signup_condition":{"type":"string","description":"","example":"after","enum":["after","before","between"],"nullable":true},"signup_start":{"type":"string","description":"Must be a valid date.","example":"2026-08-19T12:15:04","nullable":true},"signup_end":{"type":"string","description":"Must be a valid date.","example":"2026-08-19T12:15:04","nullable":true},"saved_query_id":{"type":"string","description":"Saved query. Must match an existing stored value.","example":null,"nullable":true},"segment_filter":{"type":"array","description":"Custom segment_metadata filters.","example":null,"items":{"type":"object","nullable":true,"properties":{"key":{"type":"string","description":"This field is required when <code>segment_filter</code> is present. Must match the regex /^[A-Za-z0-9_\\-]+$/. Must not be greater than 100 characters.","example":"j"},"op":{"type":"string","description":"This field is required when <code>segment_filter</code> is present.","example":"contains","enum":["eq","contains","empty","not_empty"]},"value":{"type":"string","description":"Must not be greater than 255 characters.","example":"n","nullable":true}}}}}}}}},"x-mcp":true,"x-mcp-name":"users.list"},"post":{"summary":"Create user","operationId":"createUser","description":"Creates a new user in your portal.\nYou can either provide a password, or you can invite the user.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"30d4f34b-253e-4651-b085-653a27e33c19","database_id":4681,"name":"Audra","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4681","avatar_small_url":"https://www.gravatar.com/avatar/1fd9495c9356cf2cb0800acccc8c85c1?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Audra/60/FFAEA5/69180f","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:04.000000Z"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"30d4f34b-253e-4651-b085-653a27e33c19"},"database_id":{"type":"integer","example":4681},"name":{"type":"string","example":"Audra"},"role":{"type":"string","example":"member"},"profile_url":{"type":"string","example":"{YOUR-PORTAL-URL}/user/profile/4681"},"avatar_small_url":{"type":"string","example":"https://www.gravatar.com/avatar/1fd9495c9356cf2cb0800acccc8c85c1?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Audra/60/FFAEA5/69180f"},"counter_votes":{"type":"integer","example":0},"counter_comments":{"type":"integer","example":0},"counter_posts":{"type":"integer","example":0},"counter_comment_votes":{"type":"integer","example":0},"created_at":{"type":"string","example":"2026-08-19T12:15:04.000000Z"}}}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"User already exists"},"properties":{"message":{"type":"string","example":"User already exists"}}}}}}},"tags":["Users"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"The user's email.","example":"jane@example.com","nullable":true},"role":{"type":"string","description":"The user's role. Only `admin` and `member` are valid; no middle tier.","example":"member"},"name":{"type":"string","description":"The user's display name. Auto-generated from email if omitted.","example":"Jane Smith","nullable":true},"invitation_message":{"type":"string","description":"Custom invitation message (only used on the invite branch when `password` is omitted).","example":null,"nullable":true},"skip_email":{"type":"string","description":"Provide any non-null value to skip sending the invitation email (invite branch only).","example":null,"nullable":true},"password":{"type":"string","description":"Set a password to create the user directly instead of sending an invitation (min 8 chars). No email is sent on this branch.","example":null,"nullable":true},"created_at":{"type":"string","description":"Custom creation date (e.g. 2025-01-15).","example":null,"nullable":true},"is_blocked":{"type":"boolean","description":"Whether the user is blocked.","example":null,"nullable":true},"verified":{"type":"boolean","description":"Whether the email is verified.","example":null,"nullable":true},"admin_accepted":{"type":"boolean","description":"Whether the user is accepted by admin.","example":null,"nullable":true},"sso_uid":{"type":"string","description":"SSO unique identifier.","example":null,"nullable":true},"sso_avatar_url":{"type":"string","description":"URL of avatar from SSO provider.","example":null,"nullable":true},"stripe_customer_id":{"type":"string","description":"Stripe customer ID (e.g. cus_abc123).","example":null,"nullable":true},"customer_company_name":{"type":"string","description":"Company name.","example":"Acme Inc.","nullable":true},"customer_mrr":{"type":"numeric","description":"Monthly Recurring Revenue.","example":"49.99","nullable":true},"customer_ltv":{"type":"numeric","description":"Lifetime Value.","example":null,"nullable":true},"customer_plan_type":{"type":"string","description":"Plan name or tier (e.g. Enterprise, Starter).","example":null,"nullable":true},"customer_signup_date":{"type":"string","description":"Customer signup date (e.g. 2024-06-01).","example":null,"nullable":true},"customer_status":{"type":"string","description":"Customer status. Allowed: active, churned, trial, prospect.","example":null,"nullable":true},"segment_metadata":{"type":"object","description":"Flexible metadata as JSON.","example":{"region":"North America","industry":"SaaS"},"properties":[],"nullable":true},"group_ids":{"type":"array","description":"Group IDs to assign.","example":null,"items":{"type":"string"}},"email_unsubscribed_at":{"type":"string","description":"Date user unsubscribed from emails.","example":null,"nullable":true}},"required":["role"]}}}},"x-mcp":true,"x-mcp-name":"users.create"}},"/api/v1/users/{key}/avatar":{"post":{"summary":"Upload avatar","operationId":"uploadAvatar","description":"Upload (or replace) the avatar for the given user. The image is square-\ncropped server-side into two sizes (80px display, 20px inline) and\nstored on S3. Any previous avatar for this user is dropped first so no\norphan objects are left behind.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"key":"8cbfbfd6-...","avatar_url":"https://.../avatars/abc.png","...":"..."},"properties":{"key":{"type":"string","example":"8cbfbfd6-..."},"avatar_url":{"type":"string","example":"https://.../avatars/abc.png"},"...":{"type":"string","example":"..."}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\User] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\User] ..."}}}}}},"422":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"The given data was invalid.","errors":{"avatar":["The avatar must be a file of type: jpeg, gif, png."]}},"properties":{"message":{"type":"string","example":"The given data was invalid."},"errors":{"type":"object","properties":{"avatar":{"type":"array","example":["The avatar must be a file of type: jpeg, gif, png."],"items":{"type":"string"}}}}}}}}}},"tags":["Users"],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"avatar":{"type":"string","format":"binary","description":"JPEG, GIF, or PNG image, up to 2MB."}},"required":["avatar"]}}}}},"delete":{"summary":"Delete avatar","operationId":"deleteAvatar","description":"Remove the avatar for the given user. Clears both stored sizes from S3\nand returns the refreshed profile payload (without the avatar URLs).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"key":"8cbfbfd6-...","avatar_url":null,"...":"..."},"properties":{"key":{"type":"string","example":"8cbfbfd6-..."},"avatar_url":{"type":"string","example":null,"nullable":true},"...":{"type":"string","example":"..."}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\User] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\User] ..."}}}}}}},"tags":["Users"]},"parameters":[{"in":"path","name":"key","description":"The user key (UUID).","example":"8cbfbfd6-ecc3-4ec3-8b31-ea76b11c1f88","required":true,"schema":{"type":"string"}}]},"/api/v1/users/{id}":{"get":{"summary":"Get user","operationId":"getUser","description":"Retrieves the details of a specified user, including their group memberships.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"86b5ab41-55fd-441e-8e77-f314bd98385c","database_id":4682,"name":"Morgan","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4682","avatar_small_url":"https://www.gravatar.com/avatar/eb223eca68ff62b5963b2617950a216b?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Morgan/60/FFC8A2/69320c","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:04.000000Z"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"86b5ab41-55fd-441e-8e77-f314bd98385c"},"database_id":{"type":"integer","example":4682},"name":{"type":"string","example":"Morgan"},"role":{"type":"string","example":"member"},"profile_url":{"type":"string","example":"{YOUR-PORTAL-URL}/user/profile/4682"},"avatar_small_url":{"type":"string","example":"https://www.gravatar.com/avatar/eb223eca68ff62b5963b2617950a216b?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Morgan/60/FFC8A2/69320c"},"counter_votes":{"type":"integer","example":0},"counter_comments":{"type":"integer","example":0},"counter_posts":{"type":"integer","example":0},"counter_comment_votes":{"type":"integer","example":0},"created_at":{"type":"string","example":"2026-08-19T12:15:04.000000Z"}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\User] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\User] ..."}}}}}}},"tags":["Users"],"x-mcp":true,"x-mcp-name":"users.get"},"put":{"summary":"Update user","operationId":"updateUser","description":"Updates the details of a specified user. Any field omitted (or sent as null) is left unchanged.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"714f87cf-b795-4ab1-a08a-df4d277bf747","database_id":4683,"name":"Ashly","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4683","avatar_small_url":"https://www.gravatar.com/avatar/d6cb3c29392b2d22a8c56ef184a71c72?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Ashly/60/97C1A9/012b13","counter_votes":0,"counter_comments":0,"counter_posts":0,"counter_comment_votes":0,"created_at":"2026-08-19T12:15:05.000000Z"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"714f87cf-b795-4ab1-a08a-df4d277bf747"},"database_id":{"type":"integer","example":4683},"name":{"type":"string","example":"Ashly"},"role":{"type":"string","example":"member"},"profile_url":{"type":"string","example":"{YOUR-PORTAL-URL}/user/profile/4683"},"avatar_small_url":{"type":"string","example":"https://www.gravatar.com/avatar/d6cb3c29392b2d22a8c56ef184a71c72?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Ashly/60/97C1A9/012b13"},"counter_votes":{"type":"integer","example":0},"counter_comments":{"type":"integer","example":0},"counter_posts":{"type":"integer","example":0},"counter_comment_votes":{"type":"integer","example":0},"created_at":{"type":"string","example":"2026-08-19T12:15:05.000000Z"}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\User] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\User] ..."}}}}}},"422":{"description":"","content":{"text/plain":{"schema":{"type":"string","example":"{\n \"message\": \"The given data was invalid.\",\n \"errors\": {\n \"role\": [\n     \"The selected role is invalid.\"\n ]\n}"}}}}},"tags":["Users"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","description":"The user's email. Must be unique in the portal. SSO-managed users require `override_sso=true`.","example":"jane@example.com","nullable":true},"role":{"type":"string","description":"The user's role. Only `admin` and `member` are valid; no middle tier.","example":"member","nullable":true},"name":{"type":"string","description":"The user's display name.","example":"Jane Smith","nullable":true},"password":{"type":"string","description":"New password (min 8 chars).","example":null,"nullable":true},"created_at":{"type":"string","description":"Custom creation date (e.g. 2025-01-15).","example":null,"nullable":true},"is_blocked":{"type":"boolean","description":"Whether the user is blocked.","example":null,"nullable":true},"verified":{"type":"boolean","description":"Whether the email is verified.","example":null,"nullable":true},"admin_accepted":{"type":"boolean","description":"Whether the user is accepted by admin.","example":null,"nullable":true},"sso_uid":{"type":"string","description":"SSO unique identifier.","example":null,"nullable":true},"sso_avatar_url":{"type":"string","description":"URL of avatar from SSO provider.","example":null,"nullable":true},"stripe_customer_id":{"type":"string","description":"Stripe customer ID (e.g. cus_abc123).","example":null,"nullable":true},"customer_company_name":{"type":"string","description":"Company name.","example":null,"nullable":true},"customer_mrr":{"type":"numeric","description":"Monthly Recurring Revenue.","example":"49.99","nullable":true},"customer_ltv":{"type":"numeric","description":"Lifetime Value.","example":null,"nullable":true},"customer_plan_type":{"type":"string","description":"Plan name or tier (e.g. Enterprise, Starter).","example":null,"nullable":true},"customer_signup_date":{"type":"string","description":"Customer signup date (e.g. 2024-06-01).","example":null,"nullable":true},"customer_status":{"type":"string","description":"Customer status. Allowed: active, churned, trial, prospect.","example":null,"nullable":true},"segment_metadata":{"type":"object","description":"Flexible metadata as JSON.","example":null,"properties":[],"nullable":true},"group_ids":{"type":"array","description":"Group IDs to assign (replaces current groups).","example":null,"items":{"type":"string"}},"email_unsubscribed_at":{"type":"string","description":"Date user unsubscribed from emails.","example":null,"nullable":true}}}}}},"x-mcp":true,"x-mcp-name":"users.update"},"delete":{"summary":"Delete user by key","operationId":"deleteUserByKey","description":"Deletes a specified user by their key (UUID).\n\nTo delete by email or SSO UID, use `DELETE /users` with a query parameter instead.\n\nCRITICAL: Destructive and irreversible. When `delete_all_data=true`, ALL posts, comments,\nvotes authored by this user are deleted. Otherwise they remain with \"[User Deleted]\" as\nthe author. Common GDPR erasure flow: request the key via `GET /users?search=email`, then\nDELETE with `delete_all_data=true`.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":[],"properties":[]}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\User] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\User] ..."}}}}}}},"tags":["Users"],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"delete_all_data":{"type":"boolean","description":"Set true or 1 to delete all data from this user. Otherwise, posts, comments, will remain with [User Deleted] as the username.","example":false,"nullable":true}}}}}},"x-mcp":true,"x-mcp-name":"users.delete"},"parameters":[{"in":"path","name":"id","description":"The user's UUID key.","example":"8cbfbfd6-ecc3-4ec3-8b31-ea76b11c1f88","required":true,"schema":{"type":"string"}}]},"/api/v1/posts/{key}/votes":{"get":{"summary":"List votes","operationId":"listVotes","description":"Lists all votes on a post, one row per voter.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"created_at":"2026-08-19T12:15:06.270323Z","voter":{"id":"2e074e39-fcb0-485c-a5e8-d74373b8f051","database_id":4695,"name":"Eda","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4695","avatar_small_url":"https://www.gravatar.com/avatar/4f7a65004fa495aa20901e0872f22dd9?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Eda/60/FED7C3/68412d"}},{"created_at":"2026-08-19T12:15:06.271030Z","voter":{"id":"a74483ff-a192-4336-8581-1aadd363eaff","database_id":4697,"name":"Sven","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4697","avatar_small_url":"https://www.gravatar.com/avatar/dadfab7639219cc154852ab34a362b5c?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Sven/60/97C1A9/012b13"}}]},"properties":{"data":{"type":"array","example":[{"created_at":"2026-08-19T12:15:06.270323Z","voter":{"id":"2e074e39-fcb0-485c-a5e8-d74373b8f051","database_id":4695,"name":"Eda","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4695","avatar_small_url":"https://www.gravatar.com/avatar/4f7a65004fa495aa20901e0872f22dd9?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Eda/60/FED7C3/68412d"}},{"created_at":"2026-08-19T12:15:06.271030Z","voter":{"id":"a74483ff-a192-4336-8581-1aadd363eaff","database_id":4697,"name":"Sven","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4697","avatar_small_url":"https://www.gravatar.com/avatar/dadfab7639219cc154852ab34a362b5c?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Sven/60/97C1A9/012b13"}}],"items":{"type":"object","properties":{"created_at":{"type":"string","example":"2026-08-19T12:15:06.270323Z"},"voter":{"type":"object","properties":{"id":{"type":"string","example":"2e074e39-fcb0-485c-a5e8-d74373b8f051"},"database_id":{"type":"integer","example":4695},"name":{"type":"string","example":"Eda"},"role":{"type":"string","example":"member"},"profile_url":{"type":"string","example":"{YOUR-PORTAL-URL}/user/profile/4695"},"avatar_small_url":{"type":"string","example":"https://www.gravatar.com/avatar/4f7a65004fa495aa20901e0872f22dd9?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Eda/60/FED7C3/68412d"}}}}}}}}}}}},"tags":["Votes"],"x-mcp":true,"x-mcp-name":"votes.list"},"parameters":[{"in":"path","name":"key","description":"The portal_post_key of the post (short string, NOT the numeric id).","example":"0UFQQh","required":true,"schema":{"type":"string"}}]},"/api/v1/posts/{key}/votes/{user}":{"delete":{"summary":"Revoke vote","operationId":"revokeVote","description":"Remove a specific user's vote from a post.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":[],"properties":[]}}}},"404":{"description":"","content":{"application/json":{"schema":{"oneOf":[{"description":"","type":"object","example":{"message":"No query results for model [App\\User] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\User] ..."}}},{"description":"","type":"object","example":{"message":"No query results for model [App\\Post] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Post] ..."}}}]}}}},"422":{"description":"","content":{"application/json":{"schema":{"oneOf":[{"description":"","type":"object","example":{"message":"Post is not voteable."},"properties":{"message":{"type":"string","example":"Post is not voteable."}}},{"description":"","type":"object","example":{"message":"User has not voted yet."},"properties":{"message":{"type":"string","example":"User has not voted yet."}}}]}}}}},"tags":["Votes"],"x-mcp":true,"x-mcp-name":"votes.revoke"},"post":{"summary":"Vote","operationId":"vote","description":"Cast a vote on a post on behalf of a specific user.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"created_at":"2026-08-19T12:15:06.353771Z","voter":{"id":"147ae098-ac00-4c37-9016-5ac86b82c4d9","database_id":4699,"name":"Micaela","role":"member","profile_url":"{YOUR-PORTAL-URL}/user/profile/4699","avatar_small_url":"https://www.gravatar.com/avatar/5a098806cb214e8fee3a30942d0b6451?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Micaela/60/FF968A/690000"}}},"properties":{"data":{"type":"object","properties":{"created_at":{"type":"string","example":"2026-08-19T12:15:06.353771Z"},"voter":{"type":"object","properties":{"id":{"type":"string","example":"147ae098-ac00-4c37-9016-5ac86b82c4d9"},"database_id":{"type":"integer","example":4699},"name":{"type":"string","example":"Micaela"},"role":{"type":"string","example":"member"},"profile_url":{"type":"string","example":"{YOUR-PORTAL-URL}/user/profile/4699"},"avatar_small_url":{"type":"string","example":"https://www.gravatar.com/avatar/5a098806cb214e8fee3a30942d0b6451?s=60&d=https%3A%2F%2Fui-avatars.com%2Fapi%2F/Micaela/60/FF968A/690000"}}}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"oneOf":[{"description":"","type":"object","example":{"message":"No query results for model [App\\User] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\User] ..."}}},{"description":"","type":"object","example":{"message":"No query results for model [App\\Post] ..."},"properties":{"message":{"type":"string","example":"No query results for model [App\\Post] ..."}}}]}}}},"422":{"description":"","content":{"application/json":{"schema":{"oneOf":[{"description":"","type":"object","example":{"message":"Post is not voteable."},"properties":{"message":{"type":"string","example":"Post is not voteable."}}},{"description":"","type":"object","example":{"message":"User has already voted for this post."},"properties":{"message":{"type":"string","example":"User has already voted for this post."}}}]}}}}},"tags":["Votes"],"x-mcp":true,"x-mcp-name":"votes.cast"},"parameters":[{"in":"path","name":"key","description":"The portal_post_key of the post (short string, NOT the numeric id).","example":"0UFQQh","required":true,"schema":{"type":"string"}},{"in":"path","name":"user","description":"The UUID (User.key) of the user whose vote is being revoked.","example":"8cbfbfd6-ecc3-4ec3-8b31-ea76b11c1f88","required":true,"schema":{"type":"string"}}]},"/api/v1/widgets":{"get":{"summary":"List widgets","operationId":"listWidgets","description":"Retrieves a collection of all widgets in your portal.","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":[{"id":"widget1","name":"Feedback Widget","type":"feedback","created_at":"2023-01-01T00:00:00Z","updated_at":"2023-01-01T00:00:00Z"},{"id":"widget2","name":"Survey Widget","type":"survey","created_at":"2023-01-02T00:00:00Z","updated_at":"2023-01-02T00:00:00Z"}]},"properties":{"data":{"type":"array","example":[{"id":"widget1","name":"Feedback Widget","type":"feedback","created_at":"2023-01-01T00:00:00Z","updated_at":"2023-01-01T00:00:00Z"},{"id":"widget2","name":"Survey Widget","type":"survey","created_at":"2023-01-02T00:00:00Z","updated_at":"2023-01-02T00:00:00Z"}],"items":{"type":"object","properties":{"id":{"type":"string","example":"widget1"},"name":{"type":"string","example":"Feedback Widget"},"type":{"type":"string","example":"feedback"},"created_at":{"type":"string","example":"2023-01-01T00:00:00Z"},"updated_at":{"type":"string","example":"2023-01-01T00:00:00Z"}}}}}}}}}},"tags":["Widgets"]}},"/api/v1/widgets/{key}":{"get":{"summary":"Get widget","operationId":"getWidget","description":"Retrieves the details of a specific widget by its key (ID).","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"data":{"id":"widget1","name":"Feedback Widget","type":"feedback","created_at":"2023-01-01T00:00:00Z","updated_at":"2023-01-01T00:00:00Z"}},"properties":{"data":{"type":"object","properties":{"id":{"type":"string","example":"widget1"},"name":{"type":"string","example":"Feedback Widget"},"type":{"type":"string","example":"feedback"},"created_at":{"type":"string","example":"2023-01-01T00:00:00Z"},"updated_at":{"type":"string","example":"2023-01-01T00:00:00Z"}}}}}}}},"404":{"description":"","content":{"application/json":{"schema":{"type":"object","example":{"message":"No query results for model [App\\Widget] widget1"},"properties":{"message":{"type":"string","example":"No query results for model [App\\Widget] widget1"}}}}}}},"tags":["Widgets"]},"parameters":[{"in":"path","name":"key","description":"The unique identifier of the widget.","example":"widget1","required":true,"schema":{"type":"string"}}]}},"externalDocs":{"description":"ProductLift developer resources","url":"https://www.productlift.dev/developers/"}}