{
  "openapi": "3.1.0",
  "info": {
    "title": "VoteShip API",
    "version": "1.0.0",
    "description": "AI-native feature request management API. Collect, prioritize, and ship features your users actually want. Works with humans and AI agents equally.",
    "contact": {
      "name": "VoteShip Support",
      "url": "https://voteship.app/contact",
      "email": "support@voteship.app"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "externalDocs": {
    "description": "VoteShip Developer Platform: API, authentication, webhooks, MCP, and agent instructions",
    "url": "https://voteship.app/developers"
  },
  "servers": [
    {
      "url": "https://app.voteship.app/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from project settings. Format: `Bearer sk_...`"
      }
    },
    "schemas": {
      "Post": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "example": "V1StGXR8_Z5jdHi6B-myT" },
          "projectId": { "type": "string" },
          "title": { "type": "string", "example": "PDF export support" },
          "description": { "type": ["string", "null"] },
          "status": { "type": "string", "enum": ["PENDING", "APPROVED", "IN_PROGRESS", "COMPLETE", "CLOSED", "ARCHIVED"] },
          "voteCount": { "type": "integer", "example": 42 },
          "metadata": { "type": ["object", "null"],
            "additionalProperties": true,
            "description": "Private context. Replaces the object; null clears it. Omitted from public responses; available to secret-key APIs and trusted admin webhooks." },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "PostWithTags": {
        "allOf": [
          { "$ref": "#/components/schemas/Post" },
          {
            "type": "object",
            "properties": {
              "tags": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/Tag" }
              }
            }
          }
        ]
      },
      "Tag": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "label": { "type": "string", "example": "Mobile" },
          "theme": { "type": "string", "example": "#6366f1" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "Vote": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "boardUserId": { "type": ["string", "null"] },
          "anonymousId": { "type": ["string", "null"] },
          "createdAt": { "type": "string", "format": "date-time" },
          "boardUserName": { "type": ["string", "null"] },
          "boardUserEmail": { "type": ["string", "null"] }
        }
      },
      "Comment": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "postId": { "type": "string" },
          "body": { "type": "string" },
          "authorName": { "type": ["string", "null"] },
          "isInternalNote": { "type": "boolean" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "Release": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "title": { "type": "string", "example": "v2.1 — PDF Export" },
          "content": { "type": ["string", "null"] },
          "isDraft": { "type": "boolean" },
          "publishedAt": { "type": ["string", "null"], "format": "date-time" },
          "createdAt": { "type": "string", "format": "date-time" },
          "updatedAt": { "type": "string", "format": "date-time" }
        }
      },
      "BoardUser": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "email": { "type": ["string", "null"] },
          "name": { "type": ["string", "null"] },
          "avatarUrl": { "type": ["string", "null"] },
          "userSpend": { "type": "string" },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "ActivityEntry": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "action": { "type": "string" },
          "entityType": { "type": "string" },
          "entityId": { "type": ["string", "null"] },
          "metadata": { "type": ["object", "null"] },
          "createdAt": { "type": "string", "format": "date-time" }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "page": { "type": "integer" },
          "limit": { "type": "integer" },
          "total": { "type": "integer" },
          "totalPages": { "type": "integer" },
          "hasMore": {
            "type": "boolean",
            "description": "Cursor mode only."
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor mode only; null when finished. Use this instead of page/totalPages to advance."
          }
        }
      },
      "ApiError": {
        "type": "object",
        "required": ["error_code", "message", "resolution_hint", "retry", "docs_url"],
        "properties": {
          "error": { "type": "string", "description": "Legacy alias of message for backward compatibility" },
          "error_code": { "type": "string", "example": "POST_NOT_FOUND" },
          "message": { "type": "string", "example": "Post not found" },
          "resolution_hint": { "type": "string", "example": "Use GET /api/v1/posts to list valid post IDs" },
          "suggestion": { "type": "string", "description": "Alias of resolution_hint" },
          "available_actions": { "type": "array", "items": { "type": "string" } },
          "retry": { "type": "boolean" },
          "docs_url": { "type": "string", "example": "https://voteship.app/docs" }
        }
      }
    }
  },
  "paths": {
    "/": {
      "get": {
        "operationId": "apiDiscovery",
        "summary": "API Discovery",
        "description": "Returns API metadata, available endpoints, authentication info, and rate limits. No authentication required.",
        "security": [],
        "responses": {
          "200": {
            "description": "API discovery information",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "name": { "type": "string" },
                    "version": { "type": "string" },
                    "description": { "type": "string" },
                    "docs_url": { "type": "string" },
                    "openapi_url": { "type": "string" },
                    "mcp_package": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/posts": {
      "get": {
        "operationId": "listPosts",
        "summary": "List posts",
        "description": "Stable cursor scans sort by descending creation time (millisecond precision), then ID, and retain the first request time bound. Later inserts outside the initial millisecond are excluded. Boundary deletion and vote changes do not shift pages. This is not a historical snapshot: deletions and changes in filter membership remain live. Offset pages may shift under writes.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "PENDING",
                "APPROVED",
                "IN_PROGRESS",
                "COMPLETE",
                "CLOSED",
                "ARCHIVED"
              ]
            },
            "description": "Filter by status"
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "votes",
                "date"
              ],
              "default": "votes"
            },
            "description": "Legacy default votes; cursor mode defaults to and requires date. Ties use creation time then ID."
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            },
            "description": "Page number"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 100
            },
            "description": "Results per page"
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "allowEmptyValue": true,
            "description": "Empty string starts a scan; then use nextCursor. Cannot combine with page or sort=votes. Keep status and updated_since unchanged."
          },
          {
            "name": "updated_since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Inclusive updatedAt lower bound; does not include deletion tombstones."
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of posts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/PostWithTags" } },
                    "pagination": { "$ref": "#/components/schemas/Pagination" }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createPost",
        "summary": "Create a post",
        "description": "Create a new feature request. Posts are created with PENDING status by default.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["title"],
                "properties": {
                  "title": { "type": "string", "maxLength": 200, "description": "Post title (required, max 200 chars)",
                    "minLength": 1 },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10000
                  },
                  "status": { "type": "string", "enum": ["PENDING", "APPROVED", "IN_PROGRESS", "COMPLETE", "CLOSED", "ARCHIVED"], "default": "PENDING" },
                  "tagIds": { "type": "array", "items": { "type": "string" }, "description": "Array of tag IDs to attach",
                    "maxItems": 100,
                    "uniqueItems": true },
                  "metadata": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": true,
                    "description": "Private context. Replaces the object; null clears it. Omitted from public responses; available to secret-key APIs and trusted admin webhooks."
                  },
                  "createdByBoardUserId": {
                    "type": "string",
                    "description": "Must belong to this project."
                  }
                }
              },
              "example": { "title": "PDF export support", "description": "Allow users to export reports as PDF", "tagIds": ["tag_abc123"] }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Post created",
            "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Post" } } } } }
          },
          "400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    },
    "/posts/{postId}": {
      "get": {
        "operationId": "getPost",
        "summary": "Get a post",
        "description": "Retrieve a single post with its tags.",
        "parameters": [{ "name": "postId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Post details", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/PostWithTags" } } } } } },
          "404": { "description": "Post not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      },
      "patch": {
        "operationId": "updatePost",
        "summary": "Update a post",
        "description": "Update supplied fields atomically. ARCHIVED is accepted directly without a release. Leaving ARCHIVED clears releaseId. At least one editable field is required; notify alone is insufficient. API notification default is false. API PATCH does not send Slack/Discord messages.",
        "parameters": [{ "name": "postId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": { "type": "string", "maxLength": 200,
                    "minLength": 1 },
                  "description": { "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 10000 },
                  "status": { "type": "string", "enum": ["PENDING", "APPROVED", "IN_PROGRESS", "COMPLETE", "CLOSED", "ARCHIVED"] },
                  "tagIds": { "type": "array", "items": { "type": "string" },
                    "maxItems": 100,
                    "uniqueItems": true },
                  "metadata": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": true,
                    "description": "Private context. Replaces the object; null clears it. Omitted from public responses; available to secret-key APIs and trusted admin webhooks."
                  },
                  "notify": {
                    "type": "boolean",
                    "default": false,
                    "description": "Email post subscribers only on an actual status change. Defaults false. Audit records and webhooks still fire; external consumers control their own notifications."
                  }
                }
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": { "description": "Updated post", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Post" } } } } } },
          "404": { "description": "Post not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      },
      "delete": {
        "operationId": "deletePost",
        "summary": "Delete a post",
        "description": "Permanently delete a feature request and all its votes, comments, and tags.",
        "parameters": [{ "name": "postId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Post deleted", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } } },
          "404": { "description": "Post not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    },
    "/posts/similar": {
      "get": {
        "operationId": "searchSimilarPosts",
        "summary": "Semantic search for similar posts",
        "description": "Search for posts semantically similar to the given query text using pgvector embeddings.",
        "parameters": [
          { "name": "query", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Text to search for" },
          { "name": "threshold", "in": "query", "schema": { "type": "number", "default": 0.7 }, "description": "Minimum similarity score (0-1)" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 5 }, "description": "Maximum results to return" }
        ],
        "responses": {
          "200": {
            "description": "Similar posts ranked by similarity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": { "type": "string" },
                          "title": { "type": "string" },
                          "similarity": { "type": "number", "example": 0.92 },
                          "voteCount": { "type": "integer" },
                          "status": { "type": "string" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/posts/from-text": {
      "post": {
        "operationId": "submitFromText",
        "summary": "Submit feedback from natural language",
        "description": "Submit unstructured text. AI extracts a structured post, checks for duplicates, and auto-categorizes. Requires Growth plan or higher.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["text"],
                "properties": {
                  "text": { "type": "string", "description": "Raw text from any source (Slack, email, support ticket, etc.)" },
                  "source": { "type": "string", "description": "Where the feedback came from (e.g., 'slack', 'email')" },
                  "source_url": { "type": "string", "description": "Link back to the original message" }
                }
              },
              "example": { "text": "It would be great if we could export data as PDF. Multiple customers asked.", "source": "slack" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Post created from text",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "action": { "type": "string", "enum": ["created", "matched_duplicate"] },
                        "post": { "$ref": "#/components/schemas/Post" },
                        "similar_posts": { "type": "array", "items": { "type": "object" } }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/posts/{postId}/votes": {
      "get": {
        "operationId": "getVoters",
        "summary": "List voters for a post",
        "parameters": [{ "name": "postId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "List of votes", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Vote" } } } } } } }
        }
      },
      "post": {
        "operationId": "addVote",
        "summary": "Vote on a post",
        "description": "Add a vote to a feature request. Each user can only vote once per post.",
        "parameters": [{ "name": "postId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "boardUserId": { "type": "string", "description": "ID of a registered board user" },
                  "anonymousId": { "type": "string", "description": "Anonymous identifier (if no board user)" }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Vote created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Vote" } } } } } },
          "409": { "description": "Duplicate vote", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    },
    "/posts/{postId}/comments": {
      "get": {
        "operationId": "getComments",
        "summary": "List comments on a post",
        "description": "List comments for a post. By default returns only public comments. Use the `internal` query parameter to include internal notes.",
        "parameters": [
          { "name": "postId", "in": "path", "required": true, "schema": { "type": "string" } },
          { "name": "internal", "in": "query", "schema": { "type": "string", "enum": ["true", "all"] }, "description": "Filter by visibility: 'true' for internal notes only, 'all' for both public and internal. Omit for public comments only." }
        ],
        "responses": {
          "200": { "description": "List of comments", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Comment" } } } } } } }
        }
      },
      "post": {
        "operationId": "addComment",
        "summary": "Add a comment to a post",
        "parameters": [{ "name": "postId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["body"],
                "properties": {
                  "body": { "type": "string", "description": "Comment text" },
                  "authorName": { "type": "string", "description": "Display name of the commenter" },
                  "isInternalNote": { "type": "boolean", "default": false, "description": "If true, creates an internal note visible only to team members" }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Comment created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Comment" } } } } } }
        }
      }
    },
    "/tags": {
      "get": {
        "operationId": "listTags",
        "summary": "List tags",
        "description": "List all tags for the project.",
        "responses": {
          "200": { "description": "List of tags", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Tag" } } } } } } }
        }
      },
      "post": {
        "operationId": "createTag",
        "summary": "Create a tag",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["label"],
                "properties": {
                  "label": { "type": "string", "maxLength": 50 },
                  "theme": { "type": "string", "description": "Hex color code", "default": "#6366f1" }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Tag created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Tag" } } } } } }
        }
      }
    },
    "/tags/{tagId}": {
      "patch": {
        "operationId": "updateTag",
        "summary": "Update a tag",
        "parameters": [{ "name": "tagId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": { "type": "string", "maxLength": 50 },
                  "theme": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated tag", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Tag" } } } } } },
          "404": { "description": "Tag not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      },
      "delete": {
        "operationId": "deleteTag",
        "summary": "Delete a tag",
        "parameters": [{ "name": "tagId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Tag deleted", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } } }
        }
      }
    },
    "/users": {
      "get": {
        "operationId": "listUsers",
        "summary": "List board users",
        "description": "List users who have submitted feedback, voted, or interacted with the board.",
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "maximum": 100 } }
        ],
        "responses": {
          "200": { "description": "Paginated list of board users", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/BoardUser" } }, "pagination": { "$ref": "#/components/schemas/Pagination" } } } } } }
        }
      }
    },
    "/users/{userId}": {
      "get": {
        "operationId": "getUser",
        "summary": "Get a board user",
        "description": "Retrieve a single board user by ID.",
        "parameters": [{ "name": "userId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Board user details", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/BoardUser" } } } } } },
          "404": { "description": "User not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      },
      "patch": {
        "operationId": "updateUser",
        "summary": "Update a board user",
        "description": "Update a board user's spend/revenue value, name, or email. Only provided fields are updated.",
        "parameters": [{ "name": "userId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "userSpend": { "type": "number", "minimum": 0, "description": "Monthly spend/revenue for prioritization (e.g. 49.99)" },
                  "name": { "type": ["string", "null"], "description": "Display name" },
                  "email": { "type": ["string", "null"], "description": "Email address" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated board user", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/BoardUser" } } } } } },
          "400": { "description": "Validation error", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "404": { "description": "User not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    },
    "/roadmap": {
      "get": {
        "operationId": "getRoadmap",
        "summary": "Get the product roadmap",
        "description": "Returns posts grouped by status (APPROVED, IN_PROGRESS, COMPLETE) sorted by vote count.",
        "responses": {
          "200": {
            "description": "Roadmap data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "approved": { "type": "array", "items": { "$ref": "#/components/schemas/PostWithTags" } },
                        "in_progress": { "type": "array", "items": { "$ref": "#/components/schemas/PostWithTags" } },
                        "complete": { "type": "array", "items": { "$ref": "#/components/schemas/PostWithTags" } }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/releases": {
      "get": {
        "operationId": "listReleases",
        "summary": "List releases",
        "description": "List published changelog releases, newest first.",
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 100 } }
        ],
        "responses": {
          "200": { "description": "Paginated releases", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/Release" } }, "pagination": { "$ref": "#/components/schemas/Pagination" } } } } } }
        }
      },
      "post": {
        "operationId": "createRelease",
        "summary": "Create a release",
        "description": "Create a changelog release. Set isDraft to false to publish immediately.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["title"],
                "properties": {
                  "title": { "type": "string" },
                  "content": { "type": "string", "description": "Release notes content (markdown)" },
                  "isDraft": { "type": "boolean", "default": true },
                  "label": { "type": "string", "description": "Optional version label" }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Release created", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Release" } } } } } }
        }
      }
    },
    "/releases/{releaseId}": {
      "get": {
        "operationId": "getRelease",
        "summary": "Get a release",
        "parameters": [{ "name": "releaseId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Release details", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Release" } } } } } },
          "404": { "description": "Release not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      },
      "patch": {
        "operationId": "updateRelease",
        "summary": "Update a release",
        "parameters": [{ "name": "releaseId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": { "type": "string" },
                  "content": { "type": "string" },
                  "isDraft": { "type": "boolean" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated release", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/Release" } } } } } }
        }
      },
      "delete": {
        "operationId": "deleteRelease",
        "summary": "Delete a release",
        "parameters": [{ "name": "releaseId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Release deleted", "content": { "application/json": { "schema": { "type": "object", "properties": { "success": { "type": "boolean" } } } } } }
        }
      }
    },
    "/analytics": {
      "get": {
        "operationId": "getAnalyticsSummary",
        "summary": "Analytics summary",
        "description": "Get aggregated analytics for a time period including stats, top posts, and trending tags.",
        "parameters": [
          { "name": "period", "in": "query", "schema": { "type": "string", "enum": ["week", "month", "quarter"], "default": "week" }, "description": "Time period for aggregation" }
        ],
        "responses": {
          "200": {
            "description": "Analytics data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "period": { "type": "string" },
                        "stats": {
                          "type": "object",
                          "properties": {
                            "new_posts": { "type": "integer" },
                            "total_votes": { "type": "integer" },
                            "new_comments": { "type": "integer" },
                            "page_views": { "type": "integer" }
                          }
                        },
                        "top_posts": { "type": "array", "items": { "$ref": "#/components/schemas/Post" } },
                        "trending_tags": { "type": "array", "items": { "type": "object", "properties": { "label": { "type": "string" }, "post_count": { "type": "integer" } } } }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/activity": {
      "get": {
        "operationId": "listActivity",
        "summary": "Activity log",
        "description": "List recent activity entries for the project.",
        "parameters": [
          { "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50 } },
          { "name": "entityType", "in": "query", "schema": { "type": "string" }, "description": "Filter by entity type (post, tag, release, etc.)" }
        ],
        "responses": {
          "200": { "description": "Activity entries", "content": { "application/json": { "schema": { "type": "object", "properties": { "data": { "type": "array", "items": { "$ref": "#/components/schemas/ActivityEntry" } }, "pagination": { "$ref": "#/components/schemas/Pagination" } } } } } }
        }
      }
    },
    "/ai/triage": {
      "post": {
        "operationId": "triageInbox",
        "summary": "AI-powered inbox triage",
        "description": "AI analyzes unreviewed (PENDING) posts and suggests statuses, tags, and identifies duplicates. Requires Growth plan or higher.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "limit": { "type": "integer", "default": 20, "description": "Max posts to triage" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Triage recommendations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "triaged": { "type": "integer" },
                        "recommendations": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "postId": { "type": "string" },
                              "title": { "type": "string" },
                              "suggested_status": { "type": "string" },
                              "suggested_tags": { "type": "array", "items": { "type": "string" } },
                              "duplicate_of": { "type": ["string", "null"] },
                              "priority": { "type": "string", "enum": ["high", "medium", "low"] },
                              "reason": { "type": "string" }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": { "description": "Plan required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    },
    "/ai/summary": {
      "post": {
        "operationId": "getFeedbackSummary",
        "summary": "AI feedback summary",
        "description": "Generate a natural language summary of recent feedback trends. Requires Growth plan or higher.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "period": { "type": "string", "enum": ["week", "month", "quarter"], "default": "week" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "AI-generated summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "summary": { "type": "string" },
                        "highlights": { "type": "array", "items": { "type": "string" } },
                        "top_themes": { "type": "array", "items": { "type": "string" } },
                        "stats": { "type": "object" }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/ai/sprint-plan": {
      "post": {
        "operationId": "planSprint",
        "summary": "AI sprint planning",
        "description": "AI suggests what to build next based on votes, themes, and strategy. Requires Growth plan or higher.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "capacity": { "type": "integer", "default": 5, "description": "Max items for the sprint" },
                  "strategy": { "type": "string", "enum": ["balanced", "revenue", "popular", "quick-wins"], "default": "balanced" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sprint plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "strategy": { "type": "string" },
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "postId": { "type": "string" },
                              "title": { "type": "string" },
                              "voteCount": { "type": "integer" },
                              "reason": { "type": "string" }
                            }
                          }
                        },
                        "summary": { "type": "string" }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/import": {
      "post": {
        "operationId": "importCSV",
        "summary": "Import from CSV / Canny",
        "description": "Import feature requests from a CSV file or Canny export.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["data"],
                "properties": {
                  "data": { "type": "array", "items": { "type": "object" }, "description": "Array of rows, each containing at least a 'title' field" },
                  "source": { "type": "string", "enum": ["csv", "canny"], "default": "csv" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Import results" }
        }
      }
    },
    "/integrations/slack": {
      "get": {
        "operationId": "getSlackIntegration",
        "summary": "Get Slack integration",
        "responses": { "200": { "description": "Slack config" } }
      },
      "put": {
        "operationId": "upsertSlackIntegration",
        "summary": "Configure Slack integration",
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "required": ["webhookUrl"], "properties": { "webhookUrl": { "type": "string" }, "channel": { "type": "string" }, "enabled": { "type": "boolean" } } } } } },
        "responses": { "200": { "description": "Updated" }, "201": { "description": "Created" } }
      },
      "delete": {
        "operationId": "deleteSlackIntegration",
        "summary": "Remove Slack integration",
        "responses": { "200": { "description": "Removed" } }
      }
    },
    "/integrations/github": {
      "get": {
        "operationId": "getGitHubIntegration",
        "summary": "Get GitHub integration",
        "responses": { "200": { "description": "GitHub config" } }
      },
      "put": {
        "operationId": "upsertGitHubIntegration",
        "summary": "Configure GitHub integration",
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "required": ["repoOwner", "repoName"], "properties": { "repoOwner": { "type": "string" }, "repoName": { "type": "string" }, "accessToken": { "type": "string" }, "enabled": { "type": "boolean" }, "syncLabels": { "type": "boolean" }, "autoCreateIssues": { "type": "boolean" } } } } } },
        "responses": { "200": { "description": "Updated" }, "201": { "description": "Created" } }
      },
      "delete": {
        "operationId": "deleteGitHubIntegration",
        "summary": "Remove GitHub integration",
        "responses": { "200": { "description": "Removed" } }
      }
    },
    "/integrations/linear": {
      "get": {
        "operationId": "getLinearIntegration",
        "summary": "Get Linear integration",
        "responses": { "200": { "description": "Linear config" } }
      },
      "put": {
        "operationId": "upsertLinearIntegration",
        "summary": "Configure Linear integration",
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "required": ["apiKey"], "properties": { "apiKey": { "type": "string" }, "teamId": { "type": "string" }, "enabled": { "type": "boolean" }, "autoCreateIssues": { "type": "boolean" } } } } } },
        "responses": { "200": { "description": "Updated" }, "201": { "description": "Created" } }
      },
      "delete": {
        "operationId": "deleteLinearIntegration",
        "summary": "Remove Linear integration",
        "responses": { "200": { "description": "Removed" } }
      }
    },
    "/integrations/discord": {
      "get": {
        "operationId": "getDiscordIntegration",
        "summary": "Get Discord integration",
        "responses": { "200": { "description": "Discord config" } }
      },
      "put": {
        "operationId": "upsertDiscordIntegration",
        "summary": "Configure Discord integration",
        "requestBody": { "content": { "application/json": { "schema": { "type": "object", "required": ["webhookUrl"], "properties": { "webhookUrl": { "type": "string" }, "enabled": { "type": "boolean" } } } } } },
        "responses": { "200": { "description": "Updated" }, "201": { "description": "Created" } }
      },
      "delete": {
        "operationId": "deleteDiscordIntegration",
        "summary": "Remove Discord integration",
        "responses": { "200": { "description": "Removed" } }
      }
    },
    "/integrations/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "summary": "List webhook endpoints",
        "responses": { "200": { "description": "List of webhook endpoints" } }
      },
      "post": {
        "operationId": "createWebhook",
        "summary": "Create webhook endpoint",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["url", "events"],
                "properties": {
                  "url": { "type": "string", "description": "HTTPS endpoint URL" },
                  "events": { "type": "array", "items": { "type": "string" }, "description": "Event types to subscribe to, or '*' for all" },
                  "description": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": { "201": { "description": "Webhook created (includes full secret)" } }
      }
    },
    "/integrations/stripe/sync": {
      "post": {
        "operationId": "syncStripeRevenue",
        "summary": "Sync customer MRR from Stripe",
        "description": "Triggers a sync of customer MRR data from the connected Stripe account into board user spend values. Requires Growth plan or higher.",
        "responses": {
          "200": {
            "description": "Sync result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": { "type": "string", "enum": ["success", "error", "partial"] },
                        "message": { "type": "string" },
                        "totalCustomers": { "type": "integer" },
                        "matchedUsers": { "type": "integer" },
                        "createdUsers": { "type": "integer" },
                        "skippedNoEmail": { "type": "integer" },
                        "errors": { "type": "array", "items": { "type": "string" } }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": { "description": "Plan upgrade required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
          "404": { "description": "No Stripe integration configured", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      }
    },
    "/integrations/webhooks/{webhookId}": {
      "get": {
        "operationId": "getWebhook",
        "summary": "Get webhook with delivery history",
        "parameters": [{ "name": "webhookId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Webhook details with recent deliveries" },
          "404": { "description": "Webhook not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
        }
      },
      "patch": {
        "operationId": "updateWebhook",
        "summary": "Update webhook endpoint",
        "parameters": [{ "name": "webhookId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": { "type": "string" },
                  "events": { "type": "array", "items": { "type": "string" } },
                  "enabled": { "type": "boolean" },
                  "description": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "Updated webhook" } }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Delete webhook endpoint",
        "parameters": [{ "name": "webhookId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Webhook deleted" } }
      }
    },
    "/posts/{postId}/comments/{commentId}": {
      "patch": {
        "operationId": "updateComment",
        "summary": "Edit or hide a comment without email",
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "commentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Edit or hide a comment without email",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Comment"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Secret API key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Entity not found in this project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "minProperties": 1,
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 5000
                  },
                  "isInternalNote": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteComment",
        "summary": "Delete a comment",
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "commentId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Delete a comment"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Secret API key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Entity not found in this project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/posts/{postId}/votes/{voteId}": {
      "patch": {
        "operationId": "anonymizeVote",
        "summary": "Replace anonymousId and clear boardUserId, retaining the vote",
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "voteId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Replace anonymousId and clear boardUserId, retaining the vote",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Vote"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Secret API key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Entity not found in this project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Anonymous ID already voted on this post",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "anonymousId"
                ],
                "properties": {
                  "anonymousId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteVote",
        "summary": "Delete vote and atomically decrement post count",
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "voteId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Delete vote and atomically decrement post count"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Secret API key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Entity not found in this project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        }
      }
    },
    "/posts/bulk": {
      "patch": {
        "operationId": "bulkUpdatePosts",
        "summary": "Update up to 100 posts with independent per-item results",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Update up to 100 posts with independent per-item results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "index",
                          "id",
                          "status"
                        ],
                        "properties": {
                          "index": {
                            "type": "integer"
                          },
                          "id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "status": {
                            "type": "integer"
                          },
                          "data": {
                            "$ref": "#/components/schemas/Post"
                          },
                          "error": {
                            "type": "string"
                          },
                          "error_code": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Secret API key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Entity not found in this project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "items"
                ],
                "properties": {
                  "notify": {
                    "type": "boolean",
                    "default": false,
                    "description": "Email post subscribers only on an actual status change. Defaults false. Audit records and webhooks still fire; external consumers control their own notifications."
                  },
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "type": "object",
                      "properties": {
                        "title": {
                          "type": "string",
                          "maxLength": 200,
                          "minLength": 1
                        },
                        "description": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 10000
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "PENDING",
                            "APPROVED",
                            "IN_PROGRESS",
                            "COMPLETE",
                            "CLOSED",
                            "ARCHIVED"
                          ]
                        },
                        "tagIds": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "maxItems": 100,
                          "uniqueItems": true
                        },
                        "metadata": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": true,
                          "description": "Private context. Replaces the object; null clears it. Omitted from public responses; available to secret-key APIs and trusted admin webhooks."
                        },
                        "notify": {
                          "type": "boolean",
                          "default": false,
                          "description": "Email post subscribers only on an actual status change. Defaults false. Audit records and webhooks still fire; external consumers control their own notifications."
                        },
                        "id": {
                          "type": "string",
                          "minLength": 1
                        }
                      },
                      "required": [
                        "id"
                      ],
                      "description": "Same editable fields as PATCH /posts/{postId}; item notify overrides the batch default."
                    }
                  }
                }
              }
            }
          }
        },
        "description": "HTTP 200 may contain mixed item statuses. Items run in order in independent transactions; failures do not roll back successes. Inspect every item. One batch consumes one rate-limit request. Invalid envelopes return 400."
      }
    },
    "/posts/{postId}/merge": {
      "post": {
        "operationId": "mergePost",
        "summary": "Merge a duplicate into a canonical post",
        "parameters": [
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Merge a duplicate into a canonical post",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "source": {
                          "$ref": "#/components/schemas/Post"
                        },
                        "target": {
                          "$ref": "#/components/schemas/Post"
                        },
                        "movedVotes": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "401": {
            "description": "Secret API key required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "404": {
            "description": "Entity not found in this project",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          },
          "409": {
            "description": "Source or target already merged",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "targetPostId"
                ],
                "properties": {
                  "targetPostId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "description": "Same-project posts only. Votes and subscribers move with deduplication; seeded votes are preserved. Source becomes CLOSED, zero votes, mergedIntoPostId set, releaseId cleared. Comments and metadata remain on the source. Rejects self/cyclic merges and merged targets. Repeating a merge is safe. No email; post.merged webhooks and audit records still fire, including on retries."
      }
    }
  }
}
