API Documentation

The VoteShip REST API v1 lets you programmatically manage posts, votes, comments, tags, users, and more.

Base URL
text
https://app.voteship.app/api/v1

All API endpoints are relative to this base URL.

MCP Server

The VoteShip MCP (Model Context Protocol) server lets AI coding assistants interact with your feedback board directly. Install it in Claude Code, Cursor, or Windsurf to list posts, triage feedback, plan sprints, and more -- all from your IDE.

Installation
Add the following to your MCP configuration file (e.g. claude_desktop_config.json or .cursor/mcp.json). Replace sk_... with your project's API secret key from the Share & Embed settings page.
json
{
  "mcpServers": {
    "voteship": {
      "command": "npx",
      "args": ["@voteship/mcp-server"],
      "env": { "VOTESHIP_API_KEY": "sk_..." }
    }
  }
}
Available Tools
The MCP server exposes the following tools that AI assistants can call on your behalf.
NameDescription
list_postsList and filter feature request posts
get_postGet a single post by ID with full details
create_postCreate a new feature request post
update_postUpdate an existing post (title, description, status, tags)
delete_postPermanently delete a post
search_similarFind semantically similar posts using AI embeddings
add_voteAdd a vote to a post on behalf of a user
get_votersList all voters for a given post
add_commentAdd a public comment or internal note to a post
get_commentsList comments on a post (public, internal, or both)
delete_commentDelete a comment from a post
list_tagsList all tags in the project
create_tagCreate a new tag with label and color
list_usersList board users who have interacted with the project
update_userUpdate a board user's spend/revenue, name, or email
get_roadmapGet the public roadmap grouped by status
get_analyticsRetrieve project analytics and daily stats
list_releasesList published changelog releases
create_releaseCreate a new changelog release
submit_feedbackSubmit feedback from natural language text
triage_inboxAI-powered triage of pending posts (tag, deduplicate, prioritise)
get_summaryGenerate an AI summary of recent feedback trends
plan_sprintAI-generated sprint plan based on votes, effort, and themes
sync_stripe_revenueSync customer MRR from connected Stripe account to board users
configure_webhookCreate or update a webhook endpoint
Resources
MCP resources provide read-only context that AI assistants can pull into their conversation window.
URIDescription
voteship://project/overviewProject name, description, plan, and key metrics
voteship://project/boardFull list of posts with vote counts and statuses
voteship://project/roadmapPublic roadmap items grouped by status column
voteship://project/changelogPublished changelog releases with linked posts
voteship://project/analyticsDaily stats, top posts, and trend data
Prompts
Pre-built prompt templates the AI assistant can invoke for common workflows.
NameDescription
triage_inboxReview pending posts, auto-tag, detect duplicates, and suggest statuses
sprint_planningGenerate a prioritised sprint plan based on votes, effort, and themes
generate_changelogDraft a changelog release from recently completed posts
feedback_summarySummarise recent feedback trends, sentiment, and top requests

Authentication

All API requests require a Bearer token. You can find your API secret key in your project's Share & Embed settings page.

bash
curl https://app.voteship.app/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

Include the header Authorization: Bearer { apiSecretKey } on every request.

Rate Limits

PlanRequests / minuteRequests / day
Free1001,000
Starter3005,000
Growth60025,000
Pro1,000100,000

Rate-limited responses return HTTP 429 Too Many Requests with a Retry-After header.

Posts

Create, read, update, and delete feature request posts.

GET/api/v1/posts
Retrieve a paginated list of posts. Supports filtering by status and sorting.

Query Parameters

NameTypeDescription
statusstringFilter by status: PENDING, APPROVED, IN_PROGRESS, COMPLETE, CLOSED, ARCHIVED
sortstringSort order: votes (default), date
pagenumberPage number (default: 1)
limitnumberNumber of results per page (default: 50, max: 100)

Response

json
{
  "data": [
    {
      "id": "post_abc123",
      "title": "Dark mode support",
      "description": "Add a dark theme option.",
      "status": "APPROVED",
      "voteCount": 42,
      "createdAt": "2025-01-15T08:30:00Z",
      "updatedAt": "2025-01-20T10:00:00Z",
      "tags": [
        { "id": "tag_1", "label": "UI", "theme": "#6366f1" }
      ]
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 128,
    "totalPages": 3
  }
}

Example

bash
curl "https://app.voteship.app/api/v1/posts?status=APPROVED&sort=votes&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
POST/api/v1/posts
Create a new feature request post. Title is required (max 200 characters). If no status is provided, defaults to PENDING.

Request Body

json
{
  "title": "Dark mode support",
  "description": "It would be great to have a dark theme option.",
  "status": "APPROVED",
  "tagIds": ["tag_1", "tag_2"]
}

Response

json
{
  "data": {
    "id": "post_abc123",
    "projectId": "proj_1",
    "title": "Dark mode support",
    "description": "It would be great to have a dark theme option.",
    "status": "APPROVED",
    "voteCount": 0,
    "targetReleaseDate": null,
    "mergedIntoPostId": null,
    "releaseId": null,
    "metadata": null,
    "createdByBoardUserId": null,
    "createdAt": "2025-01-15T08:30:00Z",
    "updatedAt": "2025-01-15T08:30:00Z"
  }
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/posts" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Dark mode support",
    "description": "It would be great to have a dark theme option.",
    "status": "APPROVED"
  }'
GET/api/v1/posts/:postId
Retrieve a single post by its ID.

Response

json
{
  "data": {
    "id": "post_abc123",
    "projectId": "proj_1",
    "title": "Dark mode support",
    "description": "It would be great to have a dark theme option.",
    "status": "APPROVED",
    "voteCount": 42,
    "targetReleaseDate": null,
    "mergedIntoPostId": null,
    "releaseId": null,
    "metadata": null,
    "createdByBoardUserId": null,
    "tags": [
      { "id": "tag_1", "label": "UI", "theme": "#6366f1" }
    ],
    "createdAt": "2025-01-15T08:30:00Z",
    "updatedAt": "2025-01-20T10:00:00Z"
  }
}

Example

bash
curl "https://app.voteship.app/api/v1/posts/post_abc123" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
PATCH/api/v1/posts/:postId
Update an existing post. Only include fields you want to change. Valid statuses: PENDING, APPROVED, IN_PROGRESS, COMPLETE, CLOSED, ARCHIVED.

Request Body

json
{
  "title": "Dark mode support (updated)",
  "status": "IN_PROGRESS",
  "tagIds": ["tag_1"]
}

Response

json
{
  "data": {
    "id": "post_abc123",
    "title": "Dark mode support (updated)",
    "description": "It would be great to have a dark theme option.",
    "status": "IN_PROGRESS",
    "voteCount": 42,
    "createdAt": "2025-01-15T08:30:00Z",
    "updatedAt": "2025-02-01T14:00:00Z"
  }
}

Example

bash
curl -X PATCH "https://app.voteship.app/api/v1/posts/post_abc123" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "IN_PROGRESS" }'
DELETE/api/v1/posts/:postId
Permanently delete a post and all associated votes and comments.

Response

json
{
  "success": true
}

Example

bash
curl -X DELETE "https://app.voteship.app/api/v1/posts/post_abc123" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

Votes

List voters and add votes on posts.

GET/api/v1/posts/:postId/votes
List all votes for a given post, including the associated board user details.

Response

json
{
  "data": [
    {
      "id": "vote_1",
      "boardUserId": "bu_123",
      "anonymousId": null,
      "createdAt": "2025-01-16T09:00:00Z",
      "boardUserName": "Jane Doe",
      "boardUserEmail": "jane@example.com"
    }
  ]
}

Example

bash
curl "https://app.voteship.app/api/v1/posts/post_abc123/votes" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
POST/api/v1/posts/:postId/votes
Add a vote to a post. Provide either a boardUserId or an anonymousId. Returns 409 Conflict if the user has already voted on this post.

Request Body

json
// Option A: identified board user
{
  "boardUserId": "bu_123"
}

// Option B: anonymous voter
{
  "anonymousId": "anon_abc123"
}

Response

json
{
  "data": {
    "id": "vote_1",
    "postId": "post_abc123",
    "boardUserId": "bu_123",
    "anonymousId": null,
    "createdAt": "2025-01-16T09:00:00Z"
  }
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/posts/post_abc123/votes" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "boardUserId": "bu_123" }'

Comments

List and create comments on posts. Comments can be public (visible to everyone) or internal notes (visible only to team members).

GET/api/v1/posts/:postId/comments
Retrieve comments for a given post. By default only public comments are returned. Use the internal query parameter to include internal notes.

Query Parameters

NameTypeDescription
internalstringFilter by visibility: 'true' for internal notes only, 'all' for both public and internal. Omit for public comments only.

Response

json
{
  "data": [
    {
      "id": "comment_1",
      "postId": "post_abc123",
      "body": "Great idea! Would love to see this.",
      "authorName": "Jane Doe",
      "isInternalNote": false,
      "boardUserId": null,
      "userId": "user_1",
      "createdAt": "2025-01-16T09:00:00Z"
    }
  ]
}

Example

bash
# Public comments only (default)
curl "https://app.voteship.app/api/v1/posts/post_abc123/comments" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

# Internal notes only
curl "https://app.voteship.app/api/v1/posts/post_abc123/comments?internal=true" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

# Both public and internal
curl "https://app.voteship.app/api/v1/posts/post_abc123/comments?internal=all" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
POST/api/v1/posts/:postId/comments
Add a new comment or internal note to a post. Set isInternalNote to true to create a team-only note that is hidden from public view.

Request Body

json
// Public comment (default)
{
  "body": "This is now on our roadmap for Q2!",
  "authorName": "Support Team"
}

// Internal note (team-only)
{
  "body": "Customer on Enterprise plan requested this urgently.",
  "authorName": "Support Team",
  "isInternalNote": true
}

Response

json
{
  "data": {
    "id": "comment_2",
    "postId": "post_abc123",
    "body": "This is now on our roadmap for Q2!",
    "authorName": "Support Team",
    "isInternalNote": false,
    "boardUserId": null,
    "userId": null,
    "createdAt": "2025-02-01T12:00:00Z"
  }
}

Example

bash
# Public comment
curl -X POST "https://app.voteship.app/api/v1/posts/post_abc123/comments" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "This is now on our roadmap for Q2!",
    "authorName": "Support Team"
  }'

# Internal note
curl -X POST "https://app.voteship.app/api/v1/posts/post_abc123/comments" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Enterprise customer requested this urgently.",
    "isInternalNote": true
  }'
DELETE/api/v1/posts/:postId/comments/:commentId
Delete a comment from a post. The comment must belong to a post in your project.

Response

json
// 204 No Content (empty response body on success)

Example

bash
curl -X DELETE "https://app.voteship.app/api/v1/posts/post_abc123/comments/comment_456" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

Tags

Manage tags to categorize and organise feature requests.

GET/api/v1/tags
Retrieve all tags for the project.

Response

json
{
  "data": [
    {
      "id": "tag_1",
      "label": "UI",
      "theme": "#6366f1",
      "projectId": "proj_1",
      "createdAt": "2025-01-10T00:00:00Z"
    },
    {
      "id": "tag_2",
      "label": "Performance",
      "theme": "#f59e0b",
      "projectId": "proj_1",
      "createdAt": "2025-01-10T00:00:00Z"
    }
  ]
}

Example

bash
curl "https://app.voteship.app/api/v1/tags" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
POST/api/v1/tags
Create a new tag.

Request Body

json
{
  "label": "Mobile",
  "theme": "#10b981"
}

Response

json
{
  "data": {
    "id": "tag_3",
    "label": "Mobile",
    "theme": "#10b981",
    "projectId": "proj_1",
    "createdAt": "2025-02-01T00:00:00Z"
  }
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/tags" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "label": "Mobile", "theme": "#10b981" }'
PATCH/api/v1/tags/:tagId
Update an existing tag's label or theme.

Request Body

json
{
  "label": "Mobile App",
  "theme": "#059669"
}

Response

json
{
  "data": {
    "id": "tag_3",
    "label": "Mobile App",
    "theme": "#059669",
    "projectId": "proj_1",
    "createdAt": "2025-02-01T00:00:00Z"
  }
}

Example

bash
curl -X PATCH "https://app.voteship.app/api/v1/tags/tag_3" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "label": "Mobile App" }'
DELETE/api/v1/tags/:tagId
Delete a tag. Posts with this tag will have it removed.

Response

json
{
  "success": true
}

Example

bash
curl -X DELETE "https://app.voteship.app/api/v1/tags/tag_3" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

Users

Manage board users who have interacted with your project. Update user spend/revenue values to prioritize feedback from your most valuable customers.

GET/api/v1/users
List all board users for this project. Supports pagination.

Query Parameters

NameTypeDescription
pagenumberPage number (default: 1)
limitnumberResults per page (default: 50, max: 100)

Response

json
{
  "data": [
    {
      "id": "bu_1",
      "projectId": "proj_1",
      "appUserId": "app_user_123",
      "email": "jane@example.com",
      "name": "Jane Doe",
      "avatarUrl": null,
      "userSpend": "0",
      "createdAt": "2024-11-01T00:00:00Z"
    },
    {
      "id": "bu_2",
      "projectId": "proj_1",
      "appUserId": null,
      "email": "john@example.com",
      "name": "John Smith",
      "avatarUrl": null,
      "userSpend": "49.99",
      "createdAt": "2025-01-05T10:30:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 245,
    "totalPages": 5
  }
}

Example

bash
curl "https://app.voteship.app/api/v1/users?page=1&limit=50" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
PATCH/api/v1/users/:userId
Update a board user's spend/revenue value, name, or email. Only include fields you want to change. The userSpend field is used for revenue-weighted prioritization in analytics and AI sprint planning.

Request Body

json
{
  "userSpend": 149.99,
  "name": "Jane Doe",
  "email": "jane@example.com"
}

Response

json
{
  "data": {
    "id": "bu_1",
    "projectId": "proj_1",
    "appUserId": "app_user_123",
    "email": "jane@example.com",
    "name": "Jane Doe",
    "avatarUrl": null,
    "userSpend": "149.99",
    "createdAt": "2024-11-01T00:00:00Z"
  }
}

Example

bash
curl -X PATCH "https://app.voteship.app/api/v1/users/bu_1" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "userSpend": 149.99 }'

Roadmap

Retrieve your public product roadmap grouped by status columns.

GET/api/v1/roadmap
Get the product roadmap with posts grouped by status: approved, in_progress, and complete. Posts with status PENDING are excluded. Each column is sorted by vote count.

Response

json
{
  "data": {
    "approved": [
      {
        "id": "post_abc123",
        "title": "Dark mode support",
        "description": "Add a dark theme option.",
        "voteCount": 42,
        "tags": [{ "id": "tag_1", "label": "UI", "theme": "#6366f1" }],
        "createdAt": "2025-01-15T08:30:00Z"
      }
    ],
    "in_progress": [],
    "complete": []
  }
}

Example

bash
curl "https://app.voteship.app/api/v1/roadmap" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

Releases

Manage changelog releases to keep users informed about shipped features.

GET/api/v1/releases
List published changelog releases, sorted by publish date descending. Pass include_drafts=true to also include draft releases.

Query Parameters

NameTypeDescription
pagenumberPage number (default: 1)
limitnumberResults per page (default: 20, max: 100)
include_draftsbooleanInclude draft releases (default: false)

Response

json
{
  "data": [
    {
      "id": "rel_abc123",
      "projectId": "proj_1",
      "title": "January 2026 Update",
      "content": "<p>We shipped dark mode and CSV export.</p>",
      "isDraft": false,
      "publishedAt": "2026-01-15T12:00:00Z",
      "createdAt": "2026-01-14T08:30:00Z"
    }
  ]
}

Example

bash
curl "https://app.voteship.app/api/v1/releases?limit=10" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
POST/api/v1/releases
Create a new changelog release. Defaults to draft. Set isDraft: false to publish immediately.

Request Body

json
{
  "title": "February 2026 Update",
  "content": "<p>New features shipped this month.</p>",
  "isDraft": false
}

Response

json
{
  "data": {
    "id": "rel_def456",
    "projectId": "proj_1",
    "title": "February 2026 Update",
    "content": "<p>New features shipped this month.</p>",
    "isDraft": false,
    "publishedAt": "2026-02-14T12:00:00Z",
    "createdAt": "2026-02-14T12:00:00Z"
  }
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/releases" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "February 2026 Update",
    "content": "<p>New features shipped this month.</p>",
    "isDraft": false
  }'

Analytics

Get aggregated analytics for your project including stats, top posts, and trending tags.

GET/api/v1/analytics
Get analytics summary for a time period. Returns new posts, votes, comments, page views, top posts by vote count, and trending tags.

Query Parameters

NameTypeDescription
periodstringTime period: week (default), month, or quarter

Response

json
{
  "data": {
    "period": "week",
    "stats": {
      "new_posts": 12,
      "total_votes": 87,
      "new_comments": 34,
      "page_views": 1250
    },
    "top_posts": [
      {
        "id": "post_abc123",
        "title": "Dark mode support",
        "voteCount": 42,
        "status": "APPROVED"
      }
    ],
    "trending_tags": [
      { "label": "UI", "post_count": 8 },
      { "label": "Performance", "post_count": 5 }
    ]
  }
}

Example

bash
curl "https://app.voteship.app/api/v1/analytics?period=month" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

Integrations

Configure third-party integrations (Slack, GitHub, Linear, Discord) via the API. This enables AI agents and CI/CD pipelines to manage VoteShip integrations without a browser session.

GET/api/v1/integrations/slack
Get the current Slack integration configuration. The webhook URL is masked.

Response

json
{
  "data": {
    "id": "int_abc123",
    "webhookUrl": "••••••••••••abcd",
    "enabled": true,
    "createdAt": "2025-01-15T08:30:00Z"
  }
}

Example

bash
curl "https://app.voteship.app/api/v1/integrations/slack" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
PUT/api/v1/integrations/slack
Create or update the Slack integration. When a new post is submitted via the widget, a notification is automatically sent to the configured Slack channel.

Request Body

json
{
  "webhookUrl": "https://hooks.slack.com/services/T.../B.../xxx",
  "enabled": true
}

Response

json
{
  "data": {
    "id": "int_abc123",
    "webhookUrl": "••••••••••••/xxx",
    "enabled": true,
    "createdAt": "2025-01-15T08:30:00Z"
  }
}

Example

bash
curl -X PUT "https://app.voteship.app/api/v1/integrations/slack" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "webhookUrl": "https://hooks.slack.com/services/T.../B.../xxx",
    "enabled": true
  }'
DELETE/api/v1/integrations/slack
Remove the Slack integration.

Response

json
{
  "success": true
}

Example

bash
curl -X DELETE "https://app.voteship.app/api/v1/integrations/slack" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
GET/api/v1/integrations/github
Get the current GitHub integration configuration. The access token is masked.

Response

json
{
  "data": {
    "id": "int_def456",
    "repoOwner": "acme-corp",
    "repoName": "product-feedback",
    "accessToken": "••••••••abcd",
    "enabled": true,
    "syncLabels": true,
    "autoCreateIssues": true,
    "createdAt": "2025-01-15T08:30:00Z"
  }
}

Example

bash
curl "https://app.voteship.app/api/v1/integrations/github" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
PUT/api/v1/integrations/github
Create or update the GitHub integration. When autoCreateIssues is true, a GitHub issue is automatically created for each new post submitted via the widget.

Request Body

json
{
  "repoOwner": "acme-corp",
  "repoName": "product-feedback",
  "accessToken": "ghp_xxxxxxxxxxxx",
  "enabled": true,
  "syncLabels": true,
  "autoCreateIssues": true
}

Response

json
{
  "data": {
    "id": "int_def456",
    "repoOwner": "acme-corp",
    "repoName": "product-feedback",
    "accessToken": "••••••••xxxx",
    "enabled": true,
    "syncLabels": true,
    "autoCreateIssues": true,
    "createdAt": "2025-01-15T08:30:00Z"
  }
}

Example

bash
curl -X PUT "https://app.voteship.app/api/v1/integrations/github" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "repoOwner": "acme-corp",
    "repoName": "product-feedback",
    "accessToken": "ghp_xxxxxxxxxxxx",
    "enabled": true,
    "syncLabels": true,
    "autoCreateIssues": true
  }'
DELETE/api/v1/integrations/github
Remove the GitHub integration.

Response

json
{
  "success": true
}

Example

bash
curl -X DELETE "https://app.voteship.app/api/v1/integrations/github" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
GET/api/v1/integrations/linear
Get the current Linear integration configuration. The API key is masked.

Response

json
{
  "data": {
    "id": "int_ghi789",
    "apiKey": "••••••••abcd",
    "teamId": "team_123",
    "enabled": true,
    "autoCreateIssues": true,
    "createdAt": "2025-01-15T08:30:00Z"
  }
}

Example

bash
curl "https://app.voteship.app/api/v1/integrations/linear" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
PUT/api/v1/integrations/linear
Create or update the Linear integration. When autoCreateIssues is true, a Linear issue is automatically created for each new post submitted via the widget.

Request Body

json
{
  "apiKey": "lin_api_xxxxxxxxxxxx",
  "teamId": "team_123",
  "enabled": true,
  "autoCreateIssues": true
}

Response

json
{
  "data": {
    "id": "int_ghi789",
    "apiKey": "••••••••xxxx",
    "teamId": "team_123",
    "enabled": true,
    "autoCreateIssues": true,
    "createdAt": "2025-01-15T08:30:00Z"
  }
}

Example

bash
curl -X PUT "https://app.voteship.app/api/v1/integrations/linear" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "lin_api_xxxxxxxxxxxx",
    "teamId": "team_123",
    "enabled": true,
    "autoCreateIssues": true
  }'
DELETE/api/v1/integrations/linear
Remove the Linear integration.

Response

json
{
  "success": true
}

Example

bash
curl -X DELETE "https://app.voteship.app/api/v1/integrations/linear" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
GET/api/v1/integrations/discord
Get the current Discord integration configuration. The webhook URL is masked.

Response

json
{
  "data": {
    "id": "int_jkl012",
    "webhookUrl": "••••••••••••abcd",
    "enabled": true,
    "createdAt": "2025-01-15T08:30:00Z"
  }
}

Example

bash
curl "https://app.voteship.app/api/v1/integrations/discord" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
PUT/api/v1/integrations/discord
Create or update the Discord integration. When enabled, a notification is automatically sent to the configured Discord channel for each new post.

Request Body

json
{
  "webhookUrl": "https://discord.com/api/webhooks/1234/abcd",
  "enabled": true
}

Response

json
{
  "data": {
    "id": "int_jkl012",
    "webhookUrl": "••••••••••••abcd",
    "enabled": true,
    "createdAt": "2025-01-15T08:30:00Z"
  }
}

Example

bash
curl -X PUT "https://app.voteship.app/api/v1/integrations/discord" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "webhookUrl": "https://discord.com/api/webhooks/1234/abcd",
    "enabled": true
  }'
DELETE/api/v1/integrations/discord
Remove the Discord integration.

Response

json
{
  "success": true
}

Example

bash
curl -X DELETE "https://app.voteship.app/api/v1/integrations/discord" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
POST/api/v1/integrations/stripe/sync
Trigger a sync of customer MRR from the connected Stripe account into board user spend values. Matches Stripe customers to board users by email. Requires Growth plan or higher.

Response

json
{
  "data": {
    "status": "success",
    "message": "Matched 42 of 156 Stripe customers, created 8 new users",
    "totalCustomers": 156,
    "matchedUsers": 42,
    "createdUsers": 8,
    "skippedNoEmail": 3,
    "errors": []
  }
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/integrations/stripe/sync" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

Import

Bulk-import posts from CSV files or migrate from other platforms.

POST/api/v1/import
Import posts from CSV data. Send the CSV content as a JSON string in the request body. The CSV must include a 'title' column. Optional columns: description, status, votes, email. You can also pass format: 'canny' for Canny-style exports.

Request Body

json
{
  "data": "title,description,status,votes\n\"Dark mode\",\"Add dark theme\",\"APPROVED\",12\n\"API access\",\"REST API for integrations\",\"APPROVED\",45",
  "format": "csv"
}

Response

json
{
  "imported": 24,
  "errors": [
    "Row 15: missing title, skipped"
  ]
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/import" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data": "title,description\n\"Dark mode\",\"Add dark theme\"" }'
POST/api/v1/import/nolt
Import data from a Nolt CSV export. Send the CSV content as a JSON string in the request body.

Request Body

json
{
  "data": "title,description,status,upvotes\n\"Feature A\",\"Description\",\"open\",10"
}

Response

json
{
  "imported": 56,
  "errors": []
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/import/nolt" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data": "title,description\n\"Feature A\",\"Description\"" }'
POST/api/v1/import/uservoice
Import data from a UserVoice CSV export. Send the CSV content as a JSON string in the request body.

Request Body

json
{
  "data": "title,body,status,votes\n\"Feature A\",\"Description\",\"planned\",25"
}

Response

json
{
  "imported": 130,
  "errors": []
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/import/uservoice" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "data": "title,body\n\"Feature A\",\"Description\"" }'

Activity

Retrieve the activity log for your project.

GET/api/v1/activity
Get a chronological log of all activity events (post created, status changed, votes, comments, etc.).

Query Parameters

NameTypeDescription
pagenumberPage number (default: 1)
limitnumberNumber of events per page (default: 50, max: 100)
entityTypestringFilter by entity type (e.g. post, vote, comment)

Response

json
{
  "data": [
    {
      "id": "evt_1",
      "projectId": "proj_1",
      "action": "created",
      "entityType": "post",
      "entityId": "post_abc123",
      "metadata": {},
      "userId": "user_1",
      "createdAt": "2025-01-15T08:30:00Z"
    },
    {
      "id": "evt_2",
      "projectId": "proj_1",
      "action": "status_changed",
      "entityType": "post",
      "entityId": "post_abc123",
      "metadata": {
        "oldStatus": "APPROVED",
        "newStatus": "IN_PROGRESS"
      },
      "userId": "user_1",
      "createdAt": "2025-01-20T10:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 1024,
    "totalPages": 21
  }
}

Example

bash
curl "https://app.voteship.app/api/v1/activity?page=1&limit=50" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

Webhooks

Receive real-time notifications when events happen in your project. Configure webhook URLs in your project settings.

How Webhooks Work
When an event occurs, VoteShip sends a POST request to your configured webhook URL with the event payload.

Event Types

EventDescription
post.createdA new post was created
post.updatedA post was updated (title, description, or status)
post.deletedA post was deleted
post.status_changedA post's status was changed
post.mergedA post was merged into another
vote.createdA user voted on a post
vote.removedA user removed their vote
comment.createdA new comment was added
comment.deletedA comment was deleted
tag.createdA new tag was created
tag.deletedA tag was deleted
release.publishedA changelog release was published

Payload Format

json
{
  "event": "post.created",
  "data": {
    "id": "post_abc123",
    "title": "Dark mode support",
    "description": "Add a dark theme option.",
    "status": "APPROVED"
  },
  "timestamp": "2025-01-15T08:30:00Z",
  "webhookId": "whk_abc123"
}

Signature Verification

Every webhook request includes a X-VoteShip-Signature header. This is an HMAC-SHA256 signature of the raw request body using your webhook secret as the key. Always verify this signature to ensure the request is authentic.

javascript
const crypto = require('crypto');

function verifyWebhookSignature(body, signature, secret) {
  // Strip the "sha256=" prefix before comparing
  const sig = signature.startsWith('sha256=')
    ? signature.slice(7)
    : signature;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(body, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(sig),
    Buffer.from(expected)
  );
}

// In your webhook handler:
app.post('/webhook', (req, res) => {
  const signature = req.headers['x-voteship-signature'];
  const isValid = verifyWebhookSignature(
    req.rawBody,
    signature,
    process.env.WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }

  const event = req.body;
  console.log('Received event:', event.event);
  // Process the event...

  res.status(200).send('OK');
});

Retry Policy

If your endpoint returns a non-2xx status code or times out (after 30 seconds), VoteShip will retry the delivery up to 2 additional times (3 total attempts):

  • 1st retry: after ~5 seconds
  • 2nd retry: after ~15 seconds

Failed deliveries are logged and can be manually retried from the webhook settings in your project dashboard.

Webhook Endpoints

Manage webhook endpoints programmatically via the API. This allows AI agents and automation tools to configure webhooks without a browser session.

GET/api/v1/integrations/webhooks
List all webhook endpoints for the project.

Response

json
{
  "data": [
    {
      "id": "whk_abc123",
      "url": "https://your-app.com/webhook",
      "secret": "••••••••abcd",
      "events": ["post.created", "post.status_changed"],
      "enabled": true,
      "description": "Production webhook",
      "createdAt": "2025-01-15T08:30:00Z"
    }
  ]
}

Example

bash
curl "https://app.voteship.app/api/v1/integrations/webhooks" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
POST/api/v1/integrations/webhooks
Create a new webhook endpoint. The secret is returned in full only on creation. Use '*' to subscribe to all events.

Request Body

json
{
  "url": "https://your-app.com/webhook",
  "events": ["post.created", "vote.created"],
  "description": "My webhook"
}

Response

json
{
  "data": {
    "id": "whk_abc123",
    "url": "https://your-app.com/webhook",
    "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "events": ["post.created", "vote.created"],
    "enabled": true,
    "description": "My webhook",
    "createdAt": "2025-01-15T08:30:00Z"
  }
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/integrations/webhooks" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhook",
    "events": ["*"],
    "description": "All events"
  }'
GET/api/v1/integrations/webhooks/:webhookId
Get a single webhook endpoint with its 20 most recent deliveries.

Response

json
{
  "data": {
    "id": "whk_abc123",
    "url": "https://your-app.com/webhook",
    "secret": "••••••••abcd",
    "events": ["*"],
    "enabled": true,
    "description": "All events",
    "createdAt": "2025-01-15T08:30:00Z",
    "recentDeliveries": [
      {
        "id": "del_1",
        "event": "post.created",
        "success": true,
        "responseStatus": 200,
        "attempts": 1,
        "createdAt": "2025-01-20T10:00:00Z"
      }
    ]
  }
}

Example

bash
curl "https://app.voteship.app/api/v1/integrations/webhooks/whk_abc123" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
PATCH/api/v1/integrations/webhooks/:webhookId
Update an existing webhook endpoint. Only include fields you want to change.

Request Body

json
{
  "enabled": false,
  "events": ["post.created"]
}

Response

json
{
  "data": {
    "id": "whk_abc123",
    "url": "https://your-app.com/webhook",
    "secret": "••••••••abcd",
    "events": ["post.created"],
    "enabled": false,
    "description": "All events",
    "createdAt": "2025-01-15T08:30:00Z"
  }
}

Example

bash
curl -X PATCH "https://app.voteship.app/api/v1/integrations/webhooks/whk_abc123" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'
DELETE/api/v1/integrations/webhooks/:webhookId
Delete a webhook endpoint and all its delivery logs.

Response

json
{
  "success": true
}

Example

bash
curl -X DELETE "https://app.voteship.app/api/v1/integrations/webhooks/whk_abc123" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

AI Workflows

AI-powered endpoints for processing, triaging, and summarizing feature requests. Requires Growth plan or higher.

POST/api/v1/posts/from-text
Submit a feature request from free-form natural language text. The AI extracts a structured title, description, and suggested tags automatically.

Request Body

json
{
  "text": "It would be really nice if I could export my board data to CSV so I can do analysis in Excel. Maybe include vote counts and status too.",
  "authorEmail": "jane@example.com",
  "authorName": "Jane Doe"
}

Response

json
{
  "data": {
    "id": "post_xyz789",
    "title": "CSV export for board data",
    "description": "Allow exporting board data to CSV including vote counts and statuses for external analysis in tools like Excel.",
    "status": "PENDING",
    "voteCount": 1,
    "tags": [
      { "id": "tag_2", "label": "Analytics", "theme": "#f59e0b" }
    ],
    "createdAt": "2025-02-10T14:30:00Z",
    "updatedAt": "2025-02-10T14:30:00Z",
    "ai": {
      "extractedTitle": "CSV export for board data",
      "suggestedTags": ["Analytics"],
      "duplicateOf": null,
      "confidence": 0.92
    }
  }
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/posts/from-text" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "It would be really nice if I could export my board data to CSV so I can do analysis in Excel.",
    "authorEmail": "jane@example.com",
    "authorName": "Jane Doe"
  }'
POST/api/v1/ai/triage
AI-powered triage of pending posts. Analyses unreviewed feedback and returns suggested statuses, tags, duplicate matches, and priority scores. Optionally pass autoApply: true to automatically apply the suggestions.

Request Body

json
{
  "limit": 20,
  "autoApply": false
}

Response

json
{
  "data": {
    "triaged": [
      {
        "postId": "post_abc123",
        "title": "Dark mode support",
        "suggestedStatus": "APPROVED",
        "suggestedTags": ["UI", "Design"],
        "duplicateOf": null,
        "priorityScore": 0.87,
        "reasoning": "High vote count (42), clear user need, aligns with existing UI tag."
      },
      {
        "postId": "post_def456",
        "title": "Add night theme",
        "suggestedStatus": "CLOSED",
        "suggestedTags": ["UI"],
        "duplicateOf": "post_abc123",
        "priorityScore": 0.34,
        "reasoning": "Duplicate of 'Dark mode support'. Merging recommended."
      }
    ],
    "totalProcessed": 2,
    "applied": false
  }
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/ai/triage" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "limit": 20, "autoApply": false }'
POST/api/v1/ai/summary
Generate an AI summary of recent feedback trends, including top themes, sentiment breakdown, and actionable insights. Defaults to the last 30 days.

Request Body

json
{
  "days": 30,
  "includeComments": true
}

Response

json
{
  "data": {
    "period": {
      "from": "2025-01-11T00:00:00Z",
      "to": "2025-02-10T23:59:59Z"
    },
    "overview": "128 new posts received with 1,024 total votes. Feature requests are up 15% from the previous period.",
    "topThemes": [
      { "theme": "Integrations", "postCount": 32, "totalVotes": 256 },
      { "theme": "UI/UX improvements", "postCount": 28, "totalVotes": 198 },
      { "theme": "Performance", "postCount": 18, "totalVotes": 145 }
    ],
    "sentiment": {
      "positive": 0.62,
      "neutral": 0.29,
      "negative": 0.09
    },
    "insights": [
      "Integration requests (especially Slack and Linear) are the strongest trend this period.",
      "Several users are requesting CSV export -- consider bundling with the analytics theme.",
      "Negative sentiment is concentrated around load times on the public board."
    ],
    "topPosts": [
      { "id": "post_abc123", "title": "Slack two-way sync", "votes": 67 },
      { "id": "post_def456", "title": "Linear auto-create issues", "votes": 54 },
      { "id": "post_ghi789", "title": "Board performance on mobile", "votes": 48 }
    ]
  }
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/ai/summary" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "days": 30, "includeComments": true }'
POST/api/v1/ai/sprint-plan
Generate an AI-powered sprint plan based on vote counts, estimated effort, thematic grouping, and business impact. Returns a prioritised list of posts to include in the next sprint. The 'revenue' and 'balanced' strategies include real MRR data from connected Stripe accounts when available.

Request Body

json
{
  "sprintDurationDays": 14,
  "maxItems": 10,
  "focus": "high-impact"
}

Response

json
{
  "data": {
    "sprintName": "Sprint 2025-02-10",
    "duration": "14 days",
    "items": [
      {
        "postId": "post_abc123",
        "title": "Slack two-way sync",
        "votes": 67,
        "effort": "medium",
        "impact": "high",
        "theme": "Integrations",
        "rationale": "Highest-voted request; builds on existing Slack integration."
      },
      {
        "postId": "post_def456",
        "title": "Linear auto-create issues",
        "votes": 54,
        "effort": "low",
        "impact": "high",
        "theme": "Integrations",
        "rationale": "Low effort since Linear API is already integrated. Pairs well with Slack sync."
      },
      {
        "postId": "post_ghi789",
        "title": "Board performance on mobile",
        "votes": 48,
        "effort": "high",
        "impact": "high",
        "theme": "Performance",
        "rationale": "Source of most negative sentiment. Addresses mobile usability gap."
      }
    ],
    "summary": "This sprint focuses on high-impact integration features and a critical performance fix. Estimated to address 169 user votes across 3 themes.",
    "totalVotesAddressed": 169,
    "themes": ["Integrations", "Performance"]
  }
}

Example

bash
curl -X POST "https://app.voteship.app/api/v1/ai/sprint-plan" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sprintDurationDays": 14,
    "maxItems": 10,
    "focus": "high-impact"
  }'

Widget SDK

Embed a feedback widget directly in your application so users can submit and vote on feature requests without leaving your app.

Quick Start
Add a single script tag to your HTML to get started. The widget will appear as a floating button in the corner of your page.

Floating Button (default)

Add this snippet before the closing </body> tag. A floating feedback button will appear in the corner of your page.

html
<script
  src="https://app.voteship.app/widget/widget.js"
  data-project="YOUR_PUBLIC_KEY"
  data-position="bottom-right"
  data-theme="auto"
  defer
></script>

Inline Board

Embed the full voting board directly into your page by adding a data-container attribute pointing to a container element:

html
<div id="voteship-board"></div>
<script
  src="https://app.voteship.app/widget/widget.js"
  data-project="YOUR_PUBLIC_KEY"
  data-container="voteship-board"
  data-theme="auto"
  defer
></script>

Configuration Options

AttributeRequiredDescription
data-projectYesYour project's public API key (found in Settings → General)
data-positionNoButton position: bottom-right (default), bottom-left, top-right, top-left
data-containerNoElement ID for inline board mode — omit for floating button
data-themeNoTheme: auto (default — detects from page), light, dark
data-localeNoLocale code (e.g. en, es, fr). Defaults to browser language
data-user-idNoPre-identify the current user by their app ID (triggers auto-identify on load)
data-user-emailNoEmail for the identified user (optional — enhances user profile)
data-user-nameNoDisplay name for the identified user

Next.js Integration

Use the built-in <Script> component for optimal loading:

jsx
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Script
          src="https://app.voteship.app/widget/widget.js"
          data-project="YOUR_PUBLIC_KEY"
          data-position="bottom-right"
          strategy="lazyOnload"
        />
      </body>
    </html>
  );
}

Next.js with User Identification

Pass your logged-in user's info so their feedback is linked to their account:

jsx
import Script from 'next/script';

// In your layout or page component:
<Script
  src="https://app.voteship.app/widget/widget.js"
  data-project="YOUR_PUBLIC_KEY"
  data-user-id={currentUser.id}
  data-user-email={currentUser.email}
  data-user-name={currentUser.name}
  strategy="lazyOnload"
/>

React (non-Next.js)

jsx
import { useEffect } from 'react';

export function VoteShipWidget({ userId, userEmail, userName }) {
  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://app.voteship.app/widget/widget.js';
    script.dataset.project = 'YOUR_PUBLIC_KEY';
    script.dataset.position = 'bottom-right';
    script.defer = true;
    if (userId) script.dataset.userId = userId;
    if (userEmail) script.dataset.userEmail = userEmail;
    if (userName) script.dataset.userName = userName;
    document.body.appendChild(script);

    return () => {
      document.body.removeChild(script);
    };
  }, []);

  return null;
}

Programmatic Control

After the widget loads, a global window.VoteShip object becomes available. Listen for the voteship:ready event to know when it's safe to call:

javascript
// Wait for the widget to be ready
window.addEventListener('voteship:ready', () => {
  // Now safe to call VoteShip methods
  console.log('VoteShip widget loaded');
});

// Open the floating popup
window.VoteShip.openPopup();

// Close the floating popup
window.VoteShip.closePopup();

// Identify a user (e.g. after login)
window.VoteShip.identify({
  id: 'user_123',           // required — your app's user ID
  email: 'jane@example.com', // optional
  name: 'Jane Doe',          // optional
  userSpend: 99.00,          // optional — monthly spend for prioritization
});

// Clear user identity (e.g. after logout)
window.VoteShip.reset();

// Mount an inline voting board into a container
window.VoteShip.loadVotingBoard('my-container-id');

// Mount an inline roadmap
window.VoteShip.loadRoadmap('roadmap-container');

// Mount an inline changelog
window.VoteShip.loadChangelog('changelog-container');