# VoteShip API v1 — Complete Reference > Base URL: `https://app.voteship.app/api/v1` > > This document describes every endpoint in the VoteShip REST API v1. It is structured for LLM consumption: no HTML, no navigation, just content. All paths are relative to the base URL unless otherwise noted. --- ## When to use VoteShip Use VoteShip when an agent needs to collect product feedback, search for duplicate requests, browse or contribute to a public voting board, triage an inbox, summarize feedback themes, prioritize a roadmap using votes or revenue, plan a sprint, or publish a release and changelog. Prefer MCP for typed agent tools, REST for authenticated server-to-server workflows, and the public API for end-user actions on a known board. Do not use VoteShip for incident response, general support case management, source-control issues, or secret storage. Before creating a request, search for an existing post. Preserve source evidence, do not invent customer demand, and confirm destructive changes. The complete interface and safety guide is published at `https://voteship.app/agent-instructions.md`. --- ## Table of Contents 1. Authentication 2. Rate Limits 3. Error Response Format 4. MCP Server 5. Webhook Events and Signatures 6. Endpoints - Discovery - Posts - Semantic Search - Natural Language Submission - Votes - Comments - Tags - Users - Roadmap - Releases - Analytics - Activity - AI Triage - AI Summary - AI Sprint Plan - Import - Integrations (Slack, GitHub, Linear, Discord) - Webhooks --- ## 1. Authentication All endpoints except the discovery endpoint (`GET /api/v1/`) require a Bearer token in the `Authorization` header. API keys are project-scoped and have the prefix `sk_`. You can find your API key in the VoteShip dashboard under **Settings > API**. **Header format:** ``` Authorization: Bearer sk_your_api_key_here ``` If the header is missing or malformed, the API returns: ```json { "error": "Missing or invalid Authorization header. Use: Bearer sk_...", "error_code": "UNAUTHORIZED", "message": "Missing or invalid Authorization header. Use: Bearer sk_...", "resolution_hint": "Send the project secret key in the Authorization header as 'Bearer sk_...'.", "suggestion": "Send the project secret key in the Authorization header as 'Bearer sk_...'.", "available_actions": ["read_api_documentation"], "retry": false, "docs_url": "https://voteship.app/docs" } ``` HTTP status: `401 Unauthorized` If the API key is invalid or does not match any project: ```json { "error": "Invalid API key", "error_code": "UNAUTHORIZED", "message": "Invalid API key", "resolution_hint": "Copy the current project secret key from Share & Embed and retry.", "suggestion": "Copy the current project secret key from Share & Embed and retry.", "available_actions": ["verify_api_key"], "retry": false, "docs_url": "https://voteship.app/docs" } ``` HTTP status: `401 Unauthorized` **Key characteristics:** - Each API key is bound to a single project. - The key determines which project's data is accessed -- there is no separate project ID parameter. - Rate limits and feature gates are determined by the billing plan of the account that owns the project. --- ## 2. Rate Limits Rate limits are enforced per project, using a sliding window algorithm. Both per-minute and per-day limits apply. The stricter of the two takes effect. ### Per-Minute Limits | Plan | Price | Requests / Minute | |---------|---------|-------------------| | Free | $0/mo | 100 | | Starter | $5/mo | 300 | | Growth | $15/mo | 600 | | Pro | $25/mo | 1,000 | ### Per-Day Limits | Plan | Requests / Day | |---------|----------------| | Free | 1,000 | | Starter | 5,000 | | Growth | 25,000 | | Pro | 100,000 | ### Rate Limit Response When rate limited, the API returns HTTP `429 Too Many Requests` with a `Retry-After` header (seconds until the limit resets): ```json { "error": "Too many requests. Please try again later.", "error_code": "RATE_LIMITED", "message": "Too many requests. Please try again later.", "resolution_hint": "Wait for the Retry-After interval before sending another request.", "suggestion": "Wait for the Retry-After interval before sending another request.", "available_actions": [], "retry": true, "docs_url": "https://voteship.app/docs" } ``` Response headers include: ``` Retry-After: 42 ``` --- ## 3. Error Response Format All error responses use a consistent JSON structure: ```json { "error_code": "VALIDATION_ERROR", "message": "title is required and must be <= 200 chars", "resolution_hint": "Provide a title string with max 200 characters", "suggestion": "Provide a title string with max 200 characters", "available_actions": ["list_posts", "search_similar"], "retry": false, "docs_url": "https://app.voteship.app/docs" } ``` ### Error Fields | Field | Type | Description | |---------------------|------------|--------------------------------------------------------------------| | `error_code` | string | Machine-readable error code (see list below) | | `message` | string | Human-readable error description | | `resolution_hint` | string | Actionable guidance for resolving the error | | `suggestion` | string? | Actionable hint for how to fix the error | | `available_actions` | string[]? | List of alternative API actions the caller can take | | `retry` | boolean | Whether the request might succeed if retried (e.g. transient error)| | `docs_url` | string | Link to VoteShip API documentation | ### Error Codes | Code | HTTP Status | Description | |----------------------|-------------|------------------------------------------------------| | `UNAUTHORIZED` | 401 | Missing or invalid API key | | `FORBIDDEN` | 403 | API key valid but insufficient permissions | | `NOT_FOUND` | 404 | Generic resource not found | | `POST_NOT_FOUND` | 404 | Post ID does not exist or does not belong to project | | `TAG_NOT_FOUND` | 404 | Tag ID does not exist or does not belong to project | | `WEBHOOK_NOT_FOUND` | 404 | Webhook endpoint not found | | `RELEASE_NOT_FOUND` | 404 | Release ID does not exist or does not belong to project | | `RATE_LIMITED` | 429 | Too many requests | | `VALIDATION_ERROR` | 400 | Request body or query parameter validation failure | | `INVALID_STATUS` | 400 | Invalid post status value | | `CONFLICT` | 409 | Resource conflict (e.g. duplicate) | | `DUPLICATE_VOTE` | 409 | User has already voted on this post | | `IMPORT_FAILED` | 500 | CSV import processing failed | | `AI_UNAVAILABLE` | 503 | AI service (Claude or Voyage) is not available | | `PLAN_REQUIRED` | 403 | Feature requires a higher billing plan | | `INTERNAL_ERROR` | 500 | Unexpected server error | --- ## 4. MCP Server VoteShip provides an MCP (Model Context Protocol) server for AI agent integration. It exposes 34 tools, 6 resources, and 5 workflow prompts with an API key. Public mode exposes 4 tools, 1 credential-free agent-guide resource, and 1 workflow prompt. ### Installation **npm package:** `@voteship/mcp-server` ```bash npx @voteship/mcp-server ``` **Environment variable required:** ``` VOTESHIP_API_KEY=sk_your_api_key_here ``` ### Claude Desktop Configuration Add to `claude_desktop_config.json`: ```json { "mcpServers": { "voteship": { "command": "npx", "args": ["-y", "@voteship/mcp-server"], "env": { "VOTESHIP_API_KEY": "sk_your_api_key_here" } } } } ``` ### Cursor / Windsurf Configuration Add to `.cursor/mcp.json` or equivalent: ```json { "mcpServers": { "voteship": { "command": "npx", "args": ["-y", "@voteship/mcp-server"], "env": { "VOTESHIP_API_KEY": "sk_your_api_key_here" } } } } ``` ### Plan Requirement The MCP server is available on Growth ($15/mo) and Pro ($25/mo) plans. Free and Starter plans can use the REST API directly. --- ## 5. Webhook Events and Signatures ### Event Types VoteShip dispatches the following webhook events: | Event | Trigger | |------------------------|----------------------------------------------| | `post.created` | A new post (feature request) is created | | `post.updated` | A post's title, description, or tags change | | `post.deleted` | A post is permanently deleted | | `post.status_changed` | A post's status transitions (e.g. APPROVED -> IN_PROGRESS) | | `post.merged` | A post is merged into another post | | `vote.created` | A new vote is cast on a post | | `vote.removed` | A vote is removed from a post | | `comment.created` | A new comment is posted | | `comment.deleted` | A comment is deleted | | `tag.created` | A new tag is created | | `tag.deleted` | A tag is deleted | | `release.published` | A changelog release is published | You can subscribe to individual events or use `"*"` to receive all events. ### Webhook Payload Every webhook delivery sends an HTTP POST with: **Headers:** | Header | Value | |-------------------------|----------------------------------------------| | `Content-Type` | `application/json` | | `X-VoteShip-Signature` | `sha256=` | | `X-Webhook-Event` | The event type (e.g. `post.created`) | | `X-Webhook-Id` | The webhook endpoint ID | **Body:** ```json { "event": "post.created", "data": { "post": { ... } }, "timestamp": "2026-02-13T12:00:00.000Z", "webhookId": "abc123def456" } ``` ### Signature Verification The `X-VoteShip-Signature` header contains an HMAC-SHA256 signature of the raw JSON body, signed with the webhook endpoint's secret. To verify in Node.js: ```javascript import { createHmac } from "crypto"; function verifyWebhookSignature(rawBody, secret, signatureHeader) { const expected = createHmac("sha256", secret) .update(rawBody) .digest("hex"); return signatureHeader === `sha256=${expected}`; } ``` ### Retry Policy Failed deliveries (non-2xx response or network error) are retried up to 3 total attempts: - Attempt 1: Immediate - Attempt 2: After 5 seconds - Attempt 3: After 15 seconds Each delivery is logged and visible via `GET /api/v1/integrations/webhooks/:webhookId`. --- ## 6. Endpoints --- ### Discovery #### GET /api/v1/ Returns API metadata, authentication instructions, rate limit tiers, and a listing of all available endpoints. No authentication required. **Authentication:** None **Parameters:** None **Response:** ```json { "name": "VoteShip API", "version": "1.0", "description": "Feature request management API — collect, prioritize, and ship features", "docs_url": "https://app.voteship.app/docs", "openapi_url": "https://app.voteship.app/api/v1/openapi.json", "mcp_package": "@voteship/mcp-server", "auth": { "type": "bearer", "header": "Authorization", "format": "Bearer sk_..." }, "rate_limits": { "free": "100/min", "starter": "300/min", "growth": "600/min", "pro": "1000/min" }, "endpoints": [ { "method": "GET", "path": "/posts", "description": "List posts with filters" }, { "method": "POST", "path": "/posts", "description": "Create a feature request" }, { "method": "GET", "path": "/posts/:postId", "description": "Get a single post" }, { "method": "PATCH", "path": "/posts/:postId", "description": "Update a post" }, { "method": "DELETE", "path": "/posts/:postId", "description": "Delete a post" }, { "method": "GET", "path": "/posts/similar", "description": "Semantic search for similar posts" }, { "method": "POST", "path": "/posts/from-text", "description": "Submit unstructured text as a feature request" }, { "method": "GET", "path": "/posts/:postId/votes", "description": "List voters on a post" }, { "method": "POST", "path": "/posts/:postId/votes", "description": "Add a vote" }, { "method": "GET", "path": "/posts/:postId/comments", "description": "List comments" }, { "method": "POST", "path": "/posts/:postId/comments", "description": "Add a comment" }, { "method": "GET", "path": "/tags", "description": "List tags" }, { "method": "POST", "path": "/tags", "description": "Create a tag" }, { "method": "PATCH", "path": "/tags/:tagId", "description": "Update a tag" }, { "method": "DELETE", "path": "/tags/:tagId", "description": "Delete a tag" }, { "method": "GET", "path": "/users", "description": "List board users" }, { "method": "GET", "path": "/roadmap", "description": "Get product roadmap" }, { "method": "GET", "path": "/releases", "description": "List changelog releases" }, { "method": "POST", "path": "/releases", "description": "Create a release" }, { "method": "GET", "path": "/releases/:releaseId", "description": "Get a release" }, { "method": "PATCH", "path": "/releases/:releaseId", "description": "Update a release" }, { "method": "DELETE", "path": "/releases/:releaseId", "description": "Delete a release" }, { "method": "GET", "path": "/analytics", "description": "Get analytics summary" }, { "method": "GET", "path": "/activity", "description": "Get activity log" }, { "method": "POST", "path": "/import", "description": "Import posts from CSV" }, { "method": "POST", "path": "/ai/triage", "description": "AI-powered inbox triage" }, { "method": "POST", "path": "/ai/summary", "description": "AI feedback summary" }, { "method": "POST", "path": "/ai/sprint-plan", "description": "AI sprint planning" }, { "method": "GET", "path": "/integrations/slack", "description": "Get Slack integration" }, { "method": "PUT", "path": "/integrations/slack", "description": "Configure Slack" }, { "method": "GET", "path": "/integrations/webhooks", "description": "List webhook endpoints" }, { "method": "POST", "path": "/integrations/webhooks", "description": "Create webhook endpoint" } ] } ``` **curl example:** ```bash curl https://app.voteship.app/api/v1/ ``` --- ### Posts #### GET /api/v1/posts List feature request posts for the project. Supports filtering by status, sorting, and pagination. **Authentication:** Bearer token required **Query Parameters:** | Parameter | Type | Default | Description | |-----------|--------|---------|--------------------------------------------------| | `status` | string | (all) | Filter by status: `PENDING`, `APPROVED`, `IN_PROGRESS`, `COMPLETE`, `CLOSED`, `ARCHIVED` | | `sort` | string | `votes` | Sort order: `votes` (by vote count desc) or `date` (by creation date desc) | | `page` | number | 1 | Page number (1-indexed) | | `limit` | number | 50 | Results per page (1-100) | **Response:** ```json { "data": [ { "id": "abc123def456ghi789jkl", "projectId": "proj_abc123", "title": "Add dark mode support", "description": "It would be great to have a dark mode option for the dashboard.", "status": "APPROVED", "voteCount": 42, "targetReleaseDate": null, "mergedIntoPostId": null, "releaseId": null, "metadata": null, "createdByBoardUserId": null, "createdAt": "2026-01-15T10:30:00.000Z", "updatedAt": "2026-02-01T14:20:00.000Z", "tags": [ { "id": "tag_abc123", "projectId": "proj_abc123", "label": "UI/UX", "theme": "#6366f1", "createdAt": "2026-01-01T00:00:00.000Z" } ] } ], "pagination": { "page": 1, "limit": 50, "total": 127, "totalPages": 3 } } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ "https://app.voteship.app/api/v1/posts?status=APPROVED&sort=votes&page=1&limit=20" ``` --- #### POST /api/v1/posts Create a new feature request post. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |---------------|----------|----------|-------------------------------------------------| | `title` | string | Yes | Post title (max 200 characters) | | `description` | string | No | Detailed description of the feature request | | `status` | string | No | Initial status. Default: `PENDING`. One of: `PENDING`, `APPROVED`, `IN_PROGRESS`, `COMPLETE`, `CLOSED`, `ARCHIVED` | | `tagIds` | string[] | No | Array of tag IDs to attach. All must belong to the project. | **Response (201 Created):** ```json { "data": { "id": "abc123def456ghi789jkl", "projectId": "proj_abc123", "title": "Export data as PDF", "description": "Allow users to export their feedback data as a PDF report.", "status": "PENDING", "voteCount": 0, "targetReleaseDate": null, "mergedIntoPostId": null, "releaseId": null, "metadata": null, "createdByBoardUserId": null, "createdAt": "2026-02-13T12:00:00.000Z", "updatedAt": "2026-02-13T12:00:00.000Z" } } ``` **Side effects:** - AI processing is triggered asynchronously (embedding generation, duplicate detection). - Activity log entry is created. - `post.created` webhook is dispatched. **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"title": "Export data as PDF", "description": "Allow users to export their feedback data as a PDF report.", "status": "APPROVED", "tagIds": ["tag_abc123"]}' \ https://app.voteship.app/api/v1/posts ``` --- #### GET /api/v1/posts/:postId Fetch a single post by ID, including its tags. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-----------|--------|----------------------| | `postId` | string | The post's unique ID | **Response:** ```json { "data": { "id": "abc123def456ghi789jkl", "projectId": "proj_abc123", "title": "Add dark mode support", "description": "It would be great to have a dark mode option for the dashboard.", "status": "APPROVED", "voteCount": 42, "targetReleaseDate": null, "mergedIntoPostId": null, "releaseId": null, "metadata": null, "createdByBoardUserId": null, "createdAt": "2026-01-15T10:30:00.000Z", "updatedAt": "2026-02-01T14:20:00.000Z", "tags": [ { "id": "tag_abc123", "projectId": "proj_abc123", "label": "UI/UX", "theme": "#6366f1", "createdAt": "2026-01-01T00:00:00.000Z" } ] } } ``` **Error (404):** ```json { "error_code": "POST_NOT_FOUND", "message": "Post not found", "suggestion": "Use GET /api/v1/posts to list valid post IDs", "available_actions": ["list_posts", "search_similar"], "retry": false, "docs_url": "https://app.voteship.app/docs" } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/posts/abc123def456ghi789jkl ``` --- #### PATCH /api/v1/posts/:postId Update an existing post. Only provided fields are updated; omitted fields are left unchanged. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-----------|--------|----------------------| | `postId` | string | The post's unique ID | **Request Body (JSON):** | Field | Type | Required | Description | |---------------|----------|----------|-------------------------------------------------| | `title` | string | No | New title (max 200 characters) | | `description` | string | No | New description | | `status` | string | No | New status: `PENDING`, `APPROVED`, `IN_PROGRESS`, `COMPLETE`, `CLOSED`, `ARCHIVED` | | `tagIds` | string[] | No | Replace all tags with this array. Pass `[]` to remove all tags. | **Response:** ```json { "data": { "id": "abc123def456ghi789jkl", "projectId": "proj_abc123", "title": "Add dark mode support", "description": "Updated description with more details.", "status": "IN_PROGRESS", "voteCount": 42, "targetReleaseDate": null, "mergedIntoPostId": null, "releaseId": null, "metadata": null, "createdByBoardUserId": null, "createdAt": "2026-01-15T10:30:00.000Z", "updatedAt": "2026-02-13T12:00:00.000Z" } } ``` **Side effects:** - Activity log entry is created for the update. - `post.updated` webhook is dispatched. - If status changed, an additional `post.status_changed` webhook is dispatched with `from` and `to` fields. **curl example:** ```bash curl -X PATCH \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"status": "IN_PROGRESS", "tagIds": ["tag_abc123", "tag_def456"]}' \ https://app.voteship.app/api/v1/posts/abc123def456ghi789jkl ``` --- #### DELETE /api/v1/posts/:postId Permanently delete a post and all associated votes, comments, and tags. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-----------|--------|----------------------| | `postId` | string | The post's unique ID | **Response:** ```json { "success": true } ``` **Side effects:** - Activity log entry is created. - `post.deleted` webhook is dispatched with `postId` and `title`. **curl example:** ```bash curl -X DELETE \ -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/posts/abc123def456ghi789jkl ``` --- ### Semantic Search #### GET /api/v1/posts/similar Search for posts similar to a given text query using AI-powered vector similarity (pgvector + Voyage AI embeddings). **Authentication:** Bearer token required **Query Parameters:** | Parameter | Type | Default | Description | |-------------|--------|---------|--------------------------------------------------| | `query` | string | (required) | Text to search for similar posts | | `threshold` | number | 0.7 | Minimum similarity score (0.0 to 1.0) | | `limit` | number | 5 | Maximum results to return (1-20) | **Response:** ```json { "data": [ { "id": "abc123def456ghi789jkl", "title": "Add dark mode support", "similarity": 0.89 }, { "id": "xyz789abc123def456ghi", "title": "Night theme for the app", "similarity": 0.82 } ] } ``` **Error (400) - Missing query:** ```json { "error_code": "VALIDATION_ERROR", "message": "query parameter is required", "suggestion": "Provide a text query to search for similar posts, e.g. ?query=export data as PDF", "retry": false, "docs_url": "https://app.voteship.app/docs" } ``` **Error (503) - AI unavailable:** ```json { "error_code": "AI_UNAVAILABLE", "message": "Embedding service is not available", "suggestion": "Ensure VOYAGE_API_KEY is configured. Try again later.", "retry": true, "docs_url": "https://app.voteship.app/docs" } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ "https://app.voteship.app/api/v1/posts/similar?query=dark%20mode&threshold=0.7&limit=5" ``` --- ### Natural Language Submission #### POST /api/v1/posts/from-text Submit unstructured text and let AI extract a structured feature request. The endpoint uses Claude to extract a title and description, checks for duplicate posts via vector similarity, and auto-categorizes with existing tags. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |--------------|--------|----------|------------------------------------------------------| | `text` | string | Yes | Unstructured text describing a feature request | | `source` | string | No | Source label (e.g. `"slack"`, `"support-ticket"`) | | `source_url` | string | No | URL where the feedback originated | **Response (201 Created) - New post created:** ```json { "data": { "action": "created", "post": { "id": "abc123def456ghi789jkl", "projectId": "proj_abc123", "title": "Export data as PDF", "description": "Users want to be able to export their feedback data and analytics as downloadable PDF reports.", "status": "PENDING", "voteCount": 0, "metadata": { "source": "slack", "source_url": "https://myteam.slack.com/archives/C123/p456" }, "createdAt": "2026-02-13T12:00:00.000Z", "updatedAt": "2026-02-13T12:00:00.000Z", "tags": [ { "id": "tag_abc123", "label": "Reporting" } ] }, "similar_posts": [ { "id": "xyz789", "title": "Download analytics report", "similarity": 0.72 } ] } } ``` **Response (200) - Duplicate detected (>90% similarity):** When a very high similarity match is found (>0.9), no new post is created. Instead, the matched post is returned: ```json { "data": { "action": "matched_duplicate", "matched_post": { "id": "existing_post_id", "title": "Export data as PDF report", "similarity": 0.95 }, "similar_posts": [ { "id": "another_post_id", "title": "Download reports", "similarity": 0.78 } ] } } ``` **Side effects (when post is created):** - Activity log entry is created. - `post.created` webhook is dispatched. **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"text": "Hey, a few customers have been asking for the ability to export their data as a PDF. Can we look into that?", "source": "slack", "source_url": "https://myteam.slack.com/archives/C123/p456"}' \ https://app.voteship.app/api/v1/posts/from-text ``` --- ### Votes #### GET /api/v1/posts/:postId/votes List all votes on a specific post, including voter information. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-----------|--------|----------------------| | `postId` | string | The post's unique ID | **Response:** ```json { "data": [ { "id": "vote_abc123", "boardUserId": "bu_xyz789", "anonymousId": null, "createdAt": "2026-01-20T08:00:00.000Z", "boardUserName": "Jane Smith", "boardUserEmail": "jane@example.com" }, { "id": "vote_def456", "boardUserId": null, "anonymousId": "anon_fingerprint_hash", "createdAt": "2026-01-21T14:30:00.000Z", "boardUserName": null, "boardUserEmail": null } ] } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/posts/abc123def456ghi789jkl/votes ``` --- #### POST /api/v1/posts/:postId/votes Add a vote to a post. Requires either a `boardUserId` (for identified users) or an `anonymousId` (for anonymous voters). Each user can only vote once per post. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-----------|--------|----------------------| | `postId` | string | The post's unique ID | **Request Body (JSON):** | Field | Type | Required | Description | |---------------|--------|----------|------------------------------------------------------------| | `boardUserId` | string | No* | ID of the board user casting the vote | | `anonymousId` | string | No* | Anonymous identifier (e.g. fingerprint hash) | *At least one of `boardUserId` or `anonymousId` is required. **Response (201 Created):** ```json { "data": { "id": "vote_newvote123", "postId": "abc123def456ghi789jkl", "boardUserId": "bu_xyz789", "anonymousId": null, "createdAt": "2026-02-13T12:00:00.000Z" } } ``` **Error (409) - Duplicate vote:** ```json { "error_code": "DUPLICATE_VOTE", "message": "This user has already voted on this post", "suggestion": "Each user can only vote once per post", "retry": false, "docs_url": "https://app.voteship.app/docs" } ``` **Side effects:** - The post's `voteCount` is incremented by 1. **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"boardUserId": "bu_xyz789"}' \ https://app.voteship.app/api/v1/posts/abc123def456ghi789jkl/votes ``` --- ### Comments #### GET /api/v1/posts/:postId/comments List all public comments on a post, ordered by creation date (newest first). Internal notes are excluded. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-----------|--------|----------------------| | `postId` | string | The post's unique ID | **Response:** ```json { "data": [ { "id": "cmt_abc123", "postId": "abc123def456ghi789jkl", "body": "This would be amazing! We really need this feature.", "authorName": "Jane Smith", "boardUserId": null, "userId": null, "isInternalNote": false, "createdAt": "2026-01-25T10:00:00.000Z" } ] } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/posts/abc123def456ghi789jkl/comments ``` --- #### POST /api/v1/posts/:postId/comments Add a public comment to a post. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-----------|--------|----------------------| | `postId` | string | The post's unique ID | **Request Body (JSON):** | Field | Type | Required | Description | |--------------|--------|----------|-------------------------------------| | `body` | string | Yes | Comment text (non-empty) | | `authorName` | string | No | Display name of the comment author | **Response (201 Created):** ```json { "data": { "id": "cmt_newcmt123", "postId": "abc123def456ghi789jkl", "body": "We are working on this feature now!", "authorName": "Product Team", "boardUserId": null, "userId": null, "isInternalNote": false, "createdAt": "2026-02-13T12:00:00.000Z" } } ``` **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"body": "We are working on this feature now!", "authorName": "Product Team"}' \ https://app.voteship.app/api/v1/posts/abc123def456ghi789jkl/comments ``` --- ### Tags #### GET /api/v1/tags List all tags for the project. **Authentication:** Bearer token required **Parameters:** None **Response:** ```json { "data": [ { "id": "tag_abc123", "projectId": "proj_abc123", "label": "UI/UX", "theme": "#6366f1", "createdAt": "2026-01-01T00:00:00.000Z" }, { "id": "tag_def456", "projectId": "proj_abc123", "label": "Performance", "theme": "#10b981", "createdAt": "2026-01-05T00:00:00.000Z" } ] } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/tags ``` --- #### POST /api/v1/tags Create a new tag. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |---------|--------|----------|--------------------------------------------------| | `label` | string | Yes | Tag label (non-empty string) | | `theme` | string | No | Hex color code for the tag. Default: `#6366f1` | **Response (201 Created):** ```json { "data": { "id": "tag_newtag123", "projectId": "proj_abc123", "label": "Mobile", "theme": "#f59e0b", "createdAt": "2026-02-13T12:00:00.000Z" } } ``` **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"label": "Mobile", "theme": "#f59e0b"}' \ https://app.voteship.app/api/v1/tags ``` --- #### PATCH /api/v1/tags/:tagId Update an existing tag's label or theme color. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-----------|--------|---------------------| | `tagId` | string | The tag's unique ID | **Request Body (JSON):** | Field | Type | Required | Description | |---------|--------|----------|--------------------------------------| | `label` | string | No | New label (non-empty string) | | `theme` | string | No | New hex color code | At least one of `label` or `theme` must be provided. **Response:** ```json { "data": { "id": "tag_abc123", "projectId": "proj_abc123", "label": "UI/UX Design", "theme": "#8b5cf6", "createdAt": "2026-01-01T00:00:00.000Z" } } ``` **curl example:** ```bash curl -X PATCH \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"label": "UI/UX Design", "theme": "#8b5cf6"}' \ https://app.voteship.app/api/v1/tags/tag_abc123 ``` --- #### DELETE /api/v1/tags/:tagId Delete a tag. The tag is removed from all posts it was attached to. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-----------|--------|---------------------| | `tagId` | string | The tag's unique ID | **Response:** ```json { "success": true } ``` **curl example:** ```bash curl -X DELETE \ -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/tags/tag_abc123 ``` --- ### Users #### GET /api/v1/users List board users (people who have interacted with the public board, voted, or were imported). Paginated. **Authentication:** Bearer token required **Query Parameters:** | Parameter | Type | Default | Description | |-----------|--------|---------|------------------------------| | `page` | number | 1 | Page number (1-indexed) | | `limit` | number | 50 | Results per page (1-100) | **Response:** ```json { "data": [ { "id": "bu_abc123", "projectId": "proj_abc123", "appUserId": "user_from_your_app", "email": "jane@example.com", "name": "Jane Smith", "avatarUrl": "https://example.com/avatar.jpg", "userSpend": "199.99", "createdAt": "2026-01-10T00:00:00.000Z" } ], "pagination": { "page": 1, "limit": 50, "total": 234, "totalPages": 5 } } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ "https://app.voteship.app/api/v1/users?page=1&limit=25" ``` --- ### Roadmap #### GET /api/v1/roadmap Get the product roadmap: posts grouped by status columns. Only posts with roadmap-visible statuses (`APPROVED`, `IN_PROGRESS`, `COMPLETE`) are included. Posts are sorted by vote count descending within each column. **Authentication:** Bearer token required **Parameters:** None **Response:** ```json { "data": { "approved": [ { "id": "abc123def456ghi789jkl", "title": "Add dark mode support", "description": "It would be great to have a dark mode option.", "voteCount": 42, "tags": [ { "id": "tag_abc123", "label": "UI/UX", "theme": "#6366f1" } ], "createdAt": "2026-01-15T10:30:00.000Z" } ], "in_progress": [ { "id": "xyz789abc123def456ghi", "title": "Slack integration improvements", "description": "Better notification controls for Slack.", "voteCount": 38, "tags": [ { "id": "tag_def456", "label": "Integrations", "theme": "#10b981" } ], "createdAt": "2026-01-20T08:00:00.000Z" } ], "complete": [ { "id": "def456ghi789jkl012mno", "title": "CSV export", "description": "Export posts as CSV.", "voteCount": 67, "tags": [], "createdAt": "2025-12-01T00:00:00.000Z" } ] } } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/roadmap ``` --- ### Releases #### GET /api/v1/releases List changelog releases, ordered by published date (newest first). By default, only published releases are returned. **Authentication:** Bearer token required **Query Parameters:** | Parameter | Type | Default | Description | |------------------|---------|---------|---------------------------------------| | `page` | number | 1 | Page number (1-indexed) | | `limit` | number | 20 | Results per page (1-100) | | `include_drafts` | string | `false` | Set to `"true"` to include drafts | **Response:** ```json { "data": [ { "id": "rel_abc123", "projectId": "proj_abc123", "title": "January 2026 Update", "content": "We shipped dark mode, improved Slack notifications, and fixed 12 bugs.", "publishedAt": "2026-01-31T12:00:00.000Z", "isDraft": false, "createdAt": "2026-01-28T10:00:00.000Z", "updatedAt": "2026-01-31T12:00:00.000Z" } ] } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ "https://app.voteship.app/api/v1/releases?page=1&limit=10&include_drafts=true" ``` --- #### POST /api/v1/releases Create a new changelog release. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |-----------|---------|----------|-------------------------------------------------------| | `title` | string | Yes | Release title (max 200 characters) | | `content` | string | No | Release content (markdown or plain text) | | `isDraft` | boolean | No | Whether the release is a draft. Default: `true` | If `isDraft` is `false`, the release is published immediately and `publishedAt` is set to the current time. **Response (201 Created):** ```json { "data": { "id": "rel_newrel123", "projectId": "proj_abc123", "title": "February 2026 Update", "content": "New features and improvements.", "publishedAt": null, "isDraft": true, "createdAt": "2026-02-13T12:00:00.000Z", "updatedAt": "2026-02-13T12:00:00.000Z" } } ``` **Side effects:** - Activity log entry is created. - If published (not draft), `release.published` webhook is dispatched. **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"title": "February 2026 Update", "content": "## What'\''s New\n\n- Dark mode\n- Slack improvements", "isDraft": false}' \ https://app.voteship.app/api/v1/releases ``` --- #### GET /api/v1/releases/:releaseId Fetch a single release by ID. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-------------|--------|-------------------------| | `releaseId` | string | The release's unique ID | **Response:** ```json { "data": { "id": "rel_abc123", "projectId": "proj_abc123", "title": "January 2026 Update", "content": "We shipped dark mode, improved Slack notifications, and fixed 12 bugs.", "publishedAt": "2026-01-31T12:00:00.000Z", "isDraft": false, "createdAt": "2026-01-28T10:00:00.000Z", "updatedAt": "2026-01-31T12:00:00.000Z" } } ``` **Error (404):** ```json { "error_code": "RELEASE_NOT_FOUND", "message": "Release not found", "suggestion": "Use GET /api/v1/releases to list valid release IDs", "available_actions": ["list_releases"], "retry": false, "docs_url": "https://app.voteship.app/docs" } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/releases/rel_abc123 ``` --- #### PATCH /api/v1/releases/:releaseId Update an existing release. Only provided fields are updated. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-------------|--------|-------------------------| | `releaseId` | string | The release's unique ID | **Request Body (JSON):** | Field | Type | Required | Description | |-----------|---------|----------|-------------------------------------------------------| | `title` | string | No | New title (max 200 characters) | | `content` | string | No | New content | | `isDraft` | boolean | No | Set to `false` to publish a draft. If publishing for the first time, `publishedAt` is set automatically. | **Response:** ```json { "data": { "id": "rel_abc123", "projectId": "proj_abc123", "title": "January 2026 Update (Revised)", "content": "Updated content with additional items.", "publishedAt": "2026-01-31T12:00:00.000Z", "isDraft": false, "createdAt": "2026-01-28T10:00:00.000Z", "updatedAt": "2026-02-13T12:00:00.000Z" } } ``` **Side effects:** - Activity log entry is created. - If the release transitions from draft to published, `release.published` webhook is dispatched. **curl example:** ```bash curl -X PATCH \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"isDraft": false}' \ https://app.voteship.app/api/v1/releases/rel_abc123 ``` --- #### DELETE /api/v1/releases/:releaseId Delete a release permanently. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-------------|--------|-------------------------| | `releaseId` | string | The release's unique ID | **Response:** ```json { "success": true } ``` **Side effects:** - Activity log entry is created. **curl example:** ```bash curl -X DELETE \ -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/releases/rel_abc123 ``` --- ### Analytics #### GET /api/v1/analytics Get an analytics summary for the project over a specified time period. Includes aggregate stats, top posts, and trending tags. **Authentication:** Bearer token required **Query Parameters:** | Parameter | Type | Default | Description | |-----------|--------|---------|-----------------------------------------------------| | `period` | string | `week` | Time period: `week` (7 days), `month` (30 days), or `quarter` (90 days) | **Response:** ```json { "data": { "period": "week", "stats": { "new_posts": 12, "total_votes": 87, "new_comments": 23, "page_views": 1542 }, "top_posts": [ { "id": "abc123def456ghi789jkl", "title": "Add dark mode support", "voteCount": 42, "status": "APPROVED" }, { "id": "xyz789abc123def456ghi", "title": "Export data as PDF", "voteCount": 31, "status": "PENDING" } ], "trending_tags": [ { "label": "UI/UX", "post_count": 5 }, { "label": "Performance", "post_count": 3 } ] } } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ "https://app.voteship.app/api/v1/analytics?period=month" ``` --- ### Activity #### GET /api/v1/activity List activity log entries for the project. Includes post creations, updates, status changes, deletions, and other tracked actions. **Authentication:** Bearer token required **Query Parameters:** | Parameter | Type | Default | Description | |--------------|--------|---------|------------------------------------------| | `page` | number | 1 | Page number (1-indexed) | | `limit` | number | 50 | Results per page (1-100) | | `entityType` | string | (all) | Filter by entity type (e.g. `post`, `release`, `tag`) | **Response:** ```json { "data": [ { "id": "act_abc123", "projectId": "proj_abc123", "action": "created", "entityType": "post", "entityId": "abc123def456ghi789jkl", "metadata": { "title": "Add dark mode support", "source": "api" }, "userId": null, "createdAt": "2026-02-13T12:00:00.000Z" }, { "id": "act_def456", "projectId": "proj_abc123", "action": "status_changed", "entityType": "post", "entityId": "xyz789abc123def456ghi", "metadata": { "title": "Slack integration", "from": "APPROVED", "to": "IN_PROGRESS", "source": "api" }, "userId": null, "createdAt": "2026-02-12T15:30:00.000Z" } ], "pagination": { "page": 1, "limit": 50, "total": 342, "totalPages": 7 } } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ "https://app.voteship.app/api/v1/activity?page=1&limit=20&entityType=post" ``` --- ### AI Triage #### POST /api/v1/ai/triage AI-powered triage of pending (unreviewed) posts. Claude analyzes each post and recommends a status, tags, priority level, and reason. Duplicate detection is also performed. **Authentication:** Bearer token required **Plan requirement:** Growth or Pro (returns `403 PLAN_REQUIRED` on lower plans) **Request Body (JSON):** | Field | Type | Required | Description | |---------|--------|----------|-------------------------------------------| | `limit` | number | No | Number of pending posts to analyze (1-50, default: 20) | **Response:** ```json { "data": { "total_pending": 8, "analyzed": 8, "recommendations": [ { "id": "abc123def456ghi789jkl", "suggested_status": "APPROVED", "suggested_tags": ["tag_abc123", "tag_def456"], "reason": "Clear, well-defined feature request with strong user demand.", "priority": "high", "potential_duplicates": [ { "id": "xyz789abc123def456ghi", "title": "Similar feature request", "similarity": 0.85 } ] }, { "id": "def456ghi789jkl012mno", "suggested_status": "CLOSED", "suggested_tags": [], "reason": "This appears to be spam content unrelated to the product.", "priority": "low", "potential_duplicates": [] } ] } } ``` **Response when no pending posts exist:** ```json { "data": { "message": "No pending posts to triage", "recommendations": [] } } ``` **Error (403) - Plan required:** ```json { "error_code": "PLAN_REQUIRED", "message": "AI triage requires the Growth plan or higher", "suggestion": "Upgrade to Growth or Pro plan at https://app.voteship.app/settings/billing", "available_actions": ["list_posts"], "retry": false, "docs_url": "https://app.voteship.app/docs" } ``` **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"limit": 10}' \ https://app.voteship.app/api/v1/ai/triage ``` --- ### AI Summary #### POST /api/v1/ai/summary Generate an AI-powered natural language summary of feedback trends over a time period. Claude analyzes recent posts, vote patterns, and engagement metrics to produce actionable insights. **Authentication:** Bearer token required **Plan requirement:** Growth or Pro (returns `403 PLAN_REQUIRED` on lower plans) **Request Body (JSON):** | Field | Type | Required | Description | |----------|--------|----------|-------------------------------------------------------| | `period` | string | No | Time period: `week`, `month`, or `quarter`. Default: `week` | **Response:** ```json { "data": { "period": "week", "summary": "This week saw a notable uptick in feedback around mobile experience improvements. 12 new feature requests were submitted, with dark mode and offline support emerging as the most requested features (42 and 31 votes respectively). Comment activity was strong at 23 new discussions, suggesting high user engagement.\n\nThe most in-demand features remain centered around UI customization and data export capabilities. Dark mode has been the top request for three consecutive weeks, signaling strong user desire for this feature.\n\nRecommended actions: (1) Prioritize dark mode implementation given consistent demand, (2) Consider bundling the top 3 export-related requests into a single initiative, and (3) Review the 4 new mobile-related posts for potential quick wins.", "stats": { "new_posts": 12, "total_votes": 87, "new_comments": 23, "page_views": 1542 }, "top_requested": [ { "title": "Add dark mode support", "votes": 42, "status": "APPROVED" }, { "title": "Offline mode", "votes": 31, "status": "PENDING" }, { "title": "Export data as PDF", "votes": 28, "status": "APPROVED" }, { "title": "Keyboard shortcuts", "votes": 22, "status": "IN_PROGRESS" }, { "title": "Multi-language support", "votes": 19, "status": "PENDING" } ] } } ``` **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"period": "month"}' \ https://app.voteship.app/api/v1/ai/summary ``` --- ### AI Sprint Plan #### POST /api/v1/ai/sprint-plan Generate an AI-powered sprint plan from your approved backlog. Claude selects the best features to build based on your chosen strategy, considering votes, tags, complexity, and impact. **Authentication:** Bearer token required **Plan requirement:** Growth or Pro (returns `403 PLAN_REQUIRED` on lower plans) **Request Body (JSON):** | Field | Type | Required | Description | |------------|--------|----------|------------------------------------------------------| | `capacity` | number | No | Number of features to include in the sprint (1-20, default: 5) | | `strategy` | string | No | Planning strategy. Default: `balanced`. One of: `balanced`, `revenue`, `popular`, `quick-wins` | **Strategy descriptions:** | Strategy | Description | |--------------|----------------------------------------------------------------| | `balanced` | Considers votes, variety of themes, and feasibility | | `revenue` | Prioritizes features requested by high-value/high-spend users | | `popular` | Strictly by vote count | | `quick-wins` | Features that are simple to implement with high impact | **Response:** ```json { "data": { "strategy": "balanced", "capacity": 5, "total_backlog": 23, "sprint": [ { "id": "abc123def456ghi789jkl", "title": "Add dark mode support", "reason": "Highest vote count and frequently requested across multiple tags.", "effort_estimate": "medium", "impact_estimate": "high", "vote_count": 42, "tags": ["UI/UX"] }, { "id": "def456ghi789jkl012mno", "title": "CSV export improvements", "reason": "Quick win with high demand; builds on existing export infrastructure.", "effort_estimate": "small", "impact_estimate": "medium", "vote_count": 28, "tags": ["Reporting"] }, { "id": "ghi789jkl012mno345pqr", "title": "Slack notification controls", "reason": "Addresses integration pain point raised by enterprise customers.", "effort_estimate": "small", "impact_estimate": "high", "vote_count": 38, "tags": ["Integrations"] }, { "id": "jkl012mno345pqr678stu", "title": "Keyboard shortcuts", "reason": "Low effort quality-of-life improvement for power users.", "effort_estimate": "small", "impact_estimate": "medium", "vote_count": 22, "tags": ["UI/UX", "Performance"] }, { "id": "mno345pqr678stu901vwx", "title": "Multi-language support", "reason": "Opens product to international markets; diversifies tag coverage.", "effort_estimate": "large", "impact_estimate": "high", "vote_count": 19, "tags": ["Internationalization"] } ] } } ``` **Response when backlog is empty:** ```json { "data": { "message": "No approved posts available for sprint planning", "sprint": [] } } ``` **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"capacity": 5, "strategy": "quick-wins"}' \ https://app.voteship.app/api/v1/ai/sprint-plan ``` --- ### Import #### POST /api/v1/import Import posts from CSV data. Supports standard CSV format and Canny export format. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |----------|--------|----------|--------------------------------------------------------| | `data` | string | Yes | CSV content as a string (must include header row) | | `format` | string | No | CSV format: `csv` (default) or `canny` | **CSV columns (standard format):** | Column | Required | Description | |---------------|----------|--------------------------------------| | `title` | Yes | Post title | | `description` | No | Post description | | `status` | No | Status (auto-mapped to VoteShip statuses) | | `votes` | No | Vote count (integer) | | `email` | No | Author email (creates board user and vote) | **CSV columns (Canny format):** | Column | Maps to | |----------------|---------------| | `Title` | title | | `Details` | description | | `Status` | status | | `Votes` | votes | | `Author Email` | email | **Status mapping:** External statuses are automatically mapped to VoteShip statuses: | External Status | VoteShip Status | |------------------|-----------------| | `Open` | `APPROVED` | | `Under Review` | `PENDING` | | `Planned` | `APPROVED` | | `In Development` | `IN_PROGRESS` | | `Developing` | `IN_PROGRESS` | | `Done` | `COMPLETE` | | `Completed` | `COMPLETE` | | `Shipped` | `COMPLETE` | | `Rejected` | `CLOSED` | | `Declined` | `CLOSED` | | `Archived` | `CLOSED` | **Response:** ```json { "imported": 45, "errors": [ "Row 12: missing title, skipped", "Row 23: unknown error" ] } ``` **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"data": "title,description,status,votes\nDark mode,Add a dark theme,Open,42\nExport PDF,Allow PDF exports,Planned,31", "format": "csv"}' \ https://app.voteship.app/api/v1/import ``` --- #### POST /api/v1/import/nolt Import posts from a Nolt CSV export. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |--------|--------|----------|------------------------------------------------| | `data` | string | Yes | Nolt CSV export content as a string | **Nolt CSV columns:** | Column | Maps to | |---------------|---------------| | `Title` | title | | `Description` | description | | `Status` | status | | `Upvotes` | votes | | `Created` | (recorded) | **Nolt status mapping:** | Nolt Status | VoteShip Status | |----------------|-----------------| | `Open` | `APPROVED` | | `In Progress` | `IN_PROGRESS` | | `Complete` | `COMPLETE` | | `Completed` | `COMPLETE` | | `Closed` | `CLOSED` | **Response:** ```json { "imported": 32, "errors": [] } ``` **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"data": "Title,Description,Status,Upvotes\nDark mode,Add dark theme,Open,42"}' \ https://app.voteship.app/api/v1/import/nolt ``` --- #### POST /api/v1/import/uservoice Import posts from a UserVoice CSV export. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |--------|--------|----------|----------------------------------------------------| | `data` | string | Yes | UserVoice CSV export content as a string | **UserVoice CSV columns:** | Column | Maps to | |--------------|---------------| | `Title` | title | | `Body` | description | | `Status` | status | | `Votes` | votes | | `Category` | (prepended to description as `[Category: ...]`) | | `Created At` | (recorded) | | `Updated At` | (recorded) | **UserVoice status mapping:** | UserVoice Status | VoteShip Status | |------------------|-----------------| | `Under Review` | `PENDING` | | `Planned` | `APPROVED` | | `Started` | `IN_PROGRESS` | | `Completed` | `COMPLETE` | | `Declined` | `CLOSED` | **Response:** ```json { "imported": 128, "errors": [ "Row 45: missing title, skipped" ] } ``` **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"data": "Title,Body,Status,Votes,Category\nDark mode,Add dark theme,Planned,42,UI"}' \ https://app.voteship.app/api/v1/import/uservoice ``` --- ### Integrations: Slack #### GET /api/v1/integrations/slack Get the current Slack integration configuration. Returns `null` data if no integration is configured. **Authentication:** Bearer token required **Parameters:** None **Response (configured):** ```json { "data": { "id": "slack_abc123", "webhookUrl": "••••••••••••••••ab1c", "enabled": true, "createdAt": "2026-01-10T00:00:00.000Z" } } ``` **Response (not configured):** ```json { "data": null } ``` Note: The `webhookUrl` is masked for security, showing only the last 4 characters. **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/integrations/slack ``` --- #### PUT /api/v1/integrations/slack Create or update the Slack integration. If an integration already exists, it is updated; otherwise a new one is created. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |--------------|---------|----------|-------------------------------------------------| | `webhookUrl` | string | Yes | Slack incoming webhook URL | | `enabled` | boolean | No | Whether the integration is active. Default: `true` | **Response (200 if updated, 201 if created):** ```json { "data": { "id": "slack_abc123", "webhookUrl": "••••••••••••••••ab1c", "enabled": true, "createdAt": "2026-01-10T00:00:00.000Z" } } ``` **curl example:** ```bash curl -X PUT \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"webhookUrl": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX", "enabled": true}' \ https://app.voteship.app/api/v1/integrations/slack ``` --- #### DELETE /api/v1/integrations/slack Remove the Slack integration. **Authentication:** Bearer token required **Parameters:** None **Response:** ```json { "success": true } ``` **curl example:** ```bash curl -X DELETE \ -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/integrations/slack ``` --- ### Integrations: GitHub #### GET /api/v1/integrations/github Get the current GitHub integration configuration. **Authentication:** Bearer token required **Parameters:** None **Response (configured):** ```json { "data": { "id": "gh_abc123", "repoOwner": "myorg", "repoName": "myproduct", "accessToken": "••••••••••••••••ab1c", "enabled": true, "syncLabels": true, "autoCreateIssues": false, "createdAt": "2026-01-10T00:00:00.000Z" } } ``` **Response (not configured):** ```json { "data": null } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/integrations/github ``` --- #### PUT /api/v1/integrations/github Create or update the GitHub integration. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |--------------------|---------|----------|----------------------------------------------------| | `repoOwner` | string | Yes | GitHub repository owner (username or organization) | | `repoName` | string | Yes | GitHub repository name | | `accessToken` | string | No | GitHub personal access token or app token | | `enabled` | boolean | No | Whether the integration is active. Default: `true` | | `syncLabels` | boolean | No | Sync VoteShip tags as GitHub labels. Default: `true` | | `autoCreateIssues` | boolean | No | Automatically create GitHub issues for new posts. Default: `false` | **Response (200 if updated, 201 if created):** ```json { "data": { "id": "gh_abc123", "repoOwner": "myorg", "repoName": "myproduct", "accessToken": "••••••••••••••••ab1c", "enabled": true, "syncLabels": true, "autoCreateIssues": true, "createdAt": "2026-01-10T00:00:00.000Z" } } ``` **curl example:** ```bash curl -X PUT \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"repoOwner": "myorg", "repoName": "myproduct", "accessToken": "ghp_xxxxxxxxxxxx", "autoCreateIssues": true}' \ https://app.voteship.app/api/v1/integrations/github ``` --- #### DELETE /api/v1/integrations/github Remove the GitHub integration. **Authentication:** Bearer token required **Parameters:** None **Response:** ```json { "success": true } ``` **curl example:** ```bash curl -X DELETE \ -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/integrations/github ``` --- ### Integrations: Linear #### GET /api/v1/integrations/linear Get the current Linear integration configuration. **Authentication:** Bearer token required **Parameters:** None **Response (configured):** ```json { "data": { "id": "lin_abc123", "apiKey": "••••••••••••••••ab1c", "teamId": "team_123", "enabled": true, "autoCreateIssues": false, "createdAt": "2026-01-10T00:00:00.000Z" } } ``` **Response (not configured):** ```json { "data": null } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/integrations/linear ``` --- #### PUT /api/v1/integrations/linear Create or update the Linear integration. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |--------------------|---------|----------|--------------------------------------------------| | `apiKey` | string | Yes | Linear API key | | `teamId` | string | No | Linear team ID (for scoping issue creation) | | `enabled` | boolean | No | Whether the integration is active. Default: `true` | | `autoCreateIssues` | boolean | No | Auto-create Linear issues for new posts. Default: `false` | **Response (200 if updated, 201 if created):** ```json { "data": { "id": "lin_abc123", "apiKey": "••••••••••••••••ab1c", "teamId": "team_123", "enabled": true, "autoCreateIssues": true, "createdAt": "2026-01-10T00:00:00.000Z" } } ``` **curl example:** ```bash curl -X PUT \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"apiKey": "lin_api_xxxxxxxxxxxx", "teamId": "team_123", "autoCreateIssues": true}' \ https://app.voteship.app/api/v1/integrations/linear ``` --- #### DELETE /api/v1/integrations/linear Remove the Linear integration. **Authentication:** Bearer token required **Parameters:** None **Response:** ```json { "success": true } ``` **curl example:** ```bash curl -X DELETE \ -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/integrations/linear ``` --- ### Integrations: Discord #### GET /api/v1/integrations/discord Get the current Discord integration configuration. **Authentication:** Bearer token required **Parameters:** None **Response (configured):** ```json { "data": { "id": "disc_abc123", "webhookUrl": "••••••••••••••••ab1c", "enabled": true, "createdAt": "2026-01-10T00:00:00.000Z" } } ``` **Response (not configured):** ```json { "data": null } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/integrations/discord ``` --- #### PUT /api/v1/integrations/discord Create or update the Discord integration. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |--------------|---------|----------|--------------------------------------------------| | `webhookUrl` | string | Yes | Discord webhook URL | | `enabled` | boolean | No | Whether the integration is active. Default: `true` | **Response (200 if updated, 201 if created):** ```json { "data": { "id": "disc_abc123", "webhookUrl": "••••••••••••••••ab1c", "enabled": true, "createdAt": "2026-01-10T00:00:00.000Z" } } ``` **curl example:** ```bash curl -X PUT \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"webhookUrl": "https://discord.com/api/webhooks/123456789/abcdefghijklmnop", "enabled": true}' \ https://app.voteship.app/api/v1/integrations/discord ``` --- #### DELETE /api/v1/integrations/discord Remove the Discord integration. **Authentication:** Bearer token required **Parameters:** None **Response:** ```json { "success": true } ``` **curl example:** ```bash curl -X DELETE \ -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/integrations/discord ``` --- ### Webhooks #### GET /api/v1/integrations/webhooks List all webhook endpoints configured for the project. **Authentication:** Bearer token required **Parameters:** None **Response:** ```json { "data": [ { "id": "wh_abc123", "url": "https://example.com/webhooks/voteship", "secret": "••••••••••••••••ab1c", "events": ["post.created", "post.status_changed"], "enabled": true, "description": "Production webhook", "createdAt": "2026-01-10T00:00:00.000Z" } ] } ``` Note: The `secret` is masked. The full secret is only returned once, at creation time. **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/integrations/webhooks ``` --- #### POST /api/v1/integrations/webhooks Create a new webhook endpoint. **Authentication:** Bearer token required **Request Body (JSON):** | Field | Type | Required | Description | |---------------|----------|----------|----------------------------------------------------------| | `url` | string | Yes | HTTPS endpoint URL that will receive webhook events | | `events` | string[] | Yes | Array of event types to subscribe to. Use `"*"` for all. | | `description` | string | No | Human-readable description of the endpoint | **Valid events:** `post.created`, `post.updated`, `post.deleted`, `post.status_changed`, `post.merged`, `vote.created`, `vote.removed`, `comment.created`, `comment.deleted`, `tag.created`, `tag.deleted`, `release.published`, `*` **Response (201 Created):** The full webhook secret is returned only on creation. Save it immediately. ```json { "data": { "id": "wh_newhook123", "url": "https://example.com/webhooks/voteship", "secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6", "events": ["post.created", "post.status_changed"], "enabled": true, "description": "Production webhook", "createdAt": "2026-02-13T12:00:00.000Z" } } ``` **curl example:** ```bash curl -X POST \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/webhooks/voteship", "events": ["post.created", "post.status_changed"], "description": "Production webhook"}' \ https://app.voteship.app/api/v1/integrations/webhooks ``` --- #### GET /api/v1/integrations/webhooks/:webhookId Get a single webhook endpoint with its recent delivery history (last 20 deliveries). **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-------------|--------|------------------------------| | `webhookId` | string | The webhook endpoint's ID | **Response:** ```json { "data": { "id": "wh_abc123", "url": "https://example.com/webhooks/voteship", "secret": "••••••••••••••••ab1c", "events": ["post.created", "post.status_changed"], "enabled": true, "description": "Production webhook", "createdAt": "2026-01-10T00:00:00.000Z", "recentDeliveries": [ { "id": "del_abc123", "event": "post.created", "success": true, "responseStatus": 200, "attempts": 1, "createdAt": "2026-02-13T11:30:00.000Z" }, { "id": "del_def456", "event": "post.status_changed", "success": false, "responseStatus": 500, "attempts": 3, "createdAt": "2026-02-13T10:00:00.000Z" } ] } } ``` **Error (404):** ```json { "error_code": "WEBHOOK_NOT_FOUND", "message": "Webhook endpoint not found", "suggestion": "Verify the webhookId exists and belongs to this project.", "available_actions": ["GET /api/v1/integrations/webhooks"], "retry": false, "docs_url": "https://app.voteship.app/docs" } ``` **curl example:** ```bash curl -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/integrations/webhooks/wh_abc123 ``` --- #### PATCH /api/v1/integrations/webhooks/:webhookId Update an existing webhook endpoint. Only provided fields are updated. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-------------|--------|------------------------------| | `webhookId` | string | The webhook endpoint's ID | **Request Body (JSON):** | Field | Type | Required | Description | |---------------|----------|----------|----------------------------------------------------------| | `url` | string | No | New endpoint URL | | `events` | string[] | No | New event subscriptions (non-empty array) | | `enabled` | boolean | No | Enable or disable the webhook | | `description` | string | No | Update description | **Response:** ```json { "data": { "id": "wh_abc123", "url": "https://example.com/webhooks/voteship-v2", "secret": "••••••••••••••••ab1c", "events": ["*"], "enabled": true, "description": "Updated webhook - all events", "createdAt": "2026-01-10T00:00:00.000Z" } } ``` **curl example:** ```bash curl -X PATCH \ -H "Authorization: Bearer sk_your_api_key" \ -H "Content-Type: application/json" \ -d '{"events": ["*"], "description": "Updated webhook - all events"}' \ https://app.voteship.app/api/v1/integrations/webhooks/wh_abc123 ``` --- #### DELETE /api/v1/integrations/webhooks/:webhookId Delete a webhook endpoint and all its delivery history. **Authentication:** Bearer token required **Path Parameters:** | Parameter | Type | Description | |-------------|--------|------------------------------| | `webhookId` | string | The webhook endpoint's ID | **Response:** ```json { "success": true } ``` **curl example:** ```bash curl -X DELETE \ -H "Authorization: Bearer sk_your_api_key" \ https://app.voteship.app/api/v1/integrations/webhooks/wh_abc123 ``` --- ## Appendix A: Post Status Values | Status | Description | |---------------|------------------------------------------------| | `PENDING` | Newly submitted, awaiting review | | `APPROVED` | Reviewed and accepted into the backlog | | `IN_PROGRESS` | Currently being worked on | | `COMPLETE` | Feature has been shipped | | `CLOSED` | Rejected, duplicate, or no longer relevant | --- ## Appendix B: ID Format All resource IDs are 21-character URL-safe strings generated by nanoid. They are NOT UUIDs. Example: `V1StGXR8_Z5jdHi6B-myT` --- ## Appendix C: Pagination Paginated endpoints return a `pagination` object: ```json { "pagination": { "page": 1, "limit": 50, "total": 127, "totalPages": 3 } } ``` - `page` starts at 1. - `limit` is clamped to 1-100. - Navigate pages by incrementing the `page` query parameter. --- ## Appendix D: CORS All API v1 endpoints include the following CORS headers on every response (including errors and OPTIONS preflight): ``` Access-Control-Allow-Origin: * Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS Access-Control-Allow-Headers: Content-Type, Authorization ``` This means the API can be called directly from browser-based applications. --- ## Appendix E: Plan Feature Gates Some endpoints require specific plans: | Feature | Minimum Plan | Endpoints | |----------------------|--------------|----------------------------------------------| | AI Triage | Growth | `POST /ai/triage` | | AI Summary | Growth | `POST /ai/summary` | | AI Sprint Plan | Growth | `POST /ai/sprint-plan` | | MCP Server | Growth | `@voteship/mcp-server` | | All other endpoints | Free | All CRUD, import, integration, analytics endpoints | Calling a plan-gated endpoint on a lower plan returns: ```json { "error_code": "PLAN_REQUIRED", "message": "AI triage requires the Growth plan or higher", "suggestion": "Upgrade to Growth or Pro plan at https://app.voteship.app/settings/billing", "retry": false, "docs_url": "https://app.voteship.app/docs" } ``` HTTP status: `403 Forbidden` --- ## Appendix F: Quick Reference Table | Method | Path | Description | Auth | |--------|-----------------------------------------|--------------------------------------|------| | GET | `/api/v1/` | API discovery (metadata + endpoints) | No | | GET | `/api/v1/posts` | List posts (filterable, paginated) | Yes | | POST | `/api/v1/posts` | Create a post | Yes | | GET | `/api/v1/posts/:postId` | Get a single post | Yes | | PATCH | `/api/v1/posts/:postId` | Update a post | Yes | | DELETE | `/api/v1/posts/:postId` | Delete a post | Yes | | GET | `/api/v1/posts/similar` | Semantic search for similar posts | Yes | | POST | `/api/v1/posts/from-text` | AI-powered natural language submission | Yes | | GET | `/api/v1/posts/:postId/votes` | List votes on a post | Yes | | POST | `/api/v1/posts/:postId/votes` | Cast a vote | Yes | | GET | `/api/v1/posts/:postId/comments` | List comments on a post | Yes | | POST | `/api/v1/posts/:postId/comments` | Add a comment | Yes | | GET | `/api/v1/tags` | List tags | Yes | | POST | `/api/v1/tags` | Create a tag | Yes | | PATCH | `/api/v1/tags/:tagId` | Update a tag | Yes | | DELETE | `/api/v1/tags/:tagId` | Delete a tag | Yes | | GET | `/api/v1/users` | List board users (paginated) | Yes | | GET | `/api/v1/roadmap` | Get roadmap (posts by status) | Yes | | GET | `/api/v1/releases` | List changelog releases | Yes | | POST | `/api/v1/releases` | Create a release | Yes | | GET | `/api/v1/releases/:releaseId` | Get a single release | Yes | | PATCH | `/api/v1/releases/:releaseId` | Update a release | Yes | | DELETE | `/api/v1/releases/:releaseId` | Delete a release | Yes | | GET | `/api/v1/analytics` | Analytics summary (stats, top posts) | Yes | | GET | `/api/v1/activity` | Activity log (paginated) | Yes | | POST | `/api/v1/ai/triage` | AI inbox triage (Growth+) | Yes | | POST | `/api/v1/ai/summary` | AI feedback summary (Growth+) | Yes | | POST | `/api/v1/ai/sprint-plan` | AI sprint planning (Growth+) | Yes | | POST | `/api/v1/import` | Import from CSV / Canny | Yes | | POST | `/api/v1/import/nolt` | Import from Nolt | Yes | | POST | `/api/v1/import/uservoice` | Import from UserVoice | Yes | | GET | `/api/v1/integrations/slack` | Get Slack integration | Yes | | PUT | `/api/v1/integrations/slack` | Configure Slack | Yes | | DELETE | `/api/v1/integrations/slack` | Remove Slack integration | Yes | | GET | `/api/v1/integrations/github` | Get GitHub integration | Yes | | PUT | `/api/v1/integrations/github` | Configure GitHub | Yes | | DELETE | `/api/v1/integrations/github` | Remove GitHub integration | Yes | | GET | `/api/v1/integrations/linear` | Get Linear integration | Yes | | PUT | `/api/v1/integrations/linear` | Configure Linear | Yes | | DELETE | `/api/v1/integrations/linear` | Remove Linear integration | Yes | | GET | `/api/v1/integrations/discord` | Get Discord integration | Yes | | PUT | `/api/v1/integrations/discord` | Configure Discord | Yes | | DELETE | `/api/v1/integrations/discord` | Remove Discord integration | Yes | | GET | `/api/v1/integrations/webhooks` | List webhook endpoints | Yes | | POST | `/api/v1/integrations/webhooks` | Create webhook endpoint | Yes | | GET | `/api/v1/integrations/webhooks/:id` | Get webhook + delivery history | Yes | | PATCH | `/api/v1/integrations/webhooks/:id` | Update webhook endpoint | Yes | | DELETE | `/api/v1/integrations/webhooks/:id` | Delete webhook endpoint | Yes |