API Documentation

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

Base URL
text
https://your-domain.com/api/v1

Replace your-domain.com with your actual VoteShip domain.

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://your-domain.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

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

Rate Limits

PlanRequests / minuteRequests / day
Free601,000
Pro30010,000
Enterprise1,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 (e.g. open, planned, in_progress, closed)
sortstringSort order: newest, oldest, most_votes, trending
limitnumberNumber of results to return (default: 20, max: 100)
offsetnumberNumber of results to skip for pagination

Response

json
{
  "data": [
    {
      "id": "post_abc123",
      "title": "Dark mode support",
      "description": "Add a dark theme option.",
      "status": "planned",
      "voteCount": 42,
      "commentCount": 5,
      "createdAt": "2025-01-15T08:30:00Z",
      "updatedAt": "2025-01-20T10:00:00Z"
    }
  ],
  "total": 128,
  "limit": 20,
  "offset": 0
}

Example

bash
curl "https://your-domain.com/api/v1/posts?status=planned&sort=most_votes&limit=10" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
POST/api/v1/posts
Create a new feature request post.

Request Body

json
{
  "title": "Dark mode support",
  "description": "It would be great to have a dark theme option.",
  "status": "open"
}

Response

json
{
  "id": "post_abc123",
  "title": "Dark mode support",
  "description": "It would be great to have a dark theme option.",
  "status": "open",
  "voteCount": 0,
  "commentCount": 0,
  "createdAt": "2025-01-15T08:30:00Z",
  "updatedAt": "2025-01-15T08:30:00Z"
}

Example

bash
curl -X POST "https://your-domain.com/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": "open"
  }'
GET/api/v1/posts/:postId
Retrieve a single post by its ID.

Response

json
{
  "id": "post_abc123",
  "title": "Dark mode support",
  "description": "It would be great to have a dark theme option.",
  "status": "planned",
  "voteCount": 42,
  "commentCount": 5,
  "tags": [
    { "id": "tag_1", "name": "UI", "color": "#6366f1" }
  ],
  "createdAt": "2025-01-15T08:30:00Z",
  "updatedAt": "2025-01-20T10:00:00Z"
}

Example

bash
curl "https://your-domain.com/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.

Request Body

json
{
  "title": "Dark mode support (updated)",
  "status": "in_progress"
}

Response

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

Example

bash
curl -X PATCH "https://your-domain.com/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,
  "message": "Post deleted successfully."
}

Example

bash
curl -X DELETE "https://your-domain.com/api/v1/posts/post_abc123" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

Votes

Cast or remove a vote on a post.

POST/api/v1/posts/:postId/votes
Toggle a vote on a post. If the user has already voted, the vote is removed. Otherwise, a new upvote is created.

Request Body

json
{
  "userId": "user_xyz"
}

Response

json
{
  "voted": true,
  "voteCount": 43
}

Example

bash
curl -X POST "https://your-domain.com/api/v1/posts/post_abc123/votes" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "userId": "user_xyz" }'

Comments

List and create comments on posts.

GET/api/v1/posts/:postId/comments
Retrieve all comments for a given post.

Response

json
{
  "data": [
    {
      "id": "comment_1",
      "body": "Great idea! Would love to see this.",
      "authorName": "Jane Doe",
      "authorEmail": "jane@example.com",
      "createdAt": "2025-01-16T09:00:00Z"
    }
  ],
  "total": 5
}

Example

bash
curl "https://your-domain.com/api/v1/posts/post_abc123/comments" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"
POST/api/v1/posts/:postId/comments
Add a new comment to a post.

Request Body

json
{
  "body": "This is now on our roadmap for Q2!",
  "authorName": "Support Team",
  "authorEmail": "support@example.com",
  "isInternal": false
}

Response

json
{
  "id": "comment_2",
  "body": "This is now on our roadmap for Q2!",
  "authorName": "Support Team",
  "authorEmail": "support@example.com",
  "isInternal": false,
  "createdAt": "2025-02-01T12:00:00Z"
}

Example

bash
curl -X POST "https://your-domain.com/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",
    "authorEmail": "support@example.com"
  }'

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",
      "name": "UI",
      "color": "#6366f1",
      "postCount": 12
    },
    {
      "id": "tag_2",
      "name": "Performance",
      "color": "#f59e0b",
      "postCount": 8
    }
  ]
}

Example

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

Request Body

json
{
  "name": "Mobile",
  "color": "#10b981"
}

Response

json
{
  "id": "tag_3",
  "name": "Mobile",
  "color": "#10b981",
  "postCount": 0
}

Example

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

Request Body

json
{
  "name": "Mobile App",
  "color": "#059669"
}

Response

json
{
  "id": "tag_3",
  "name": "Mobile App",
  "color": "#059669",
  "postCount": 0
}

Example

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

Response

json
{
  "success": true,
  "message": "Tag deleted successfully."
}

Example

bash
curl -X DELETE "https://your-domain.com/api/v1/tags/tag_3" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY"

Users

Retrieve users who have interacted with your board.

GET/api/v1/users
List all users associated with this project. Includes voters, commenters, and team members.

Response

json
{
  "data": [
    {
      "id": "user_1",
      "name": "Jane Doe",
      "email": "jane@example.com",
      "role": "admin",
      "createdAt": "2024-11-01T00:00:00Z"
    },
    {
      "id": "user_2",
      "name": "John Smith",
      "email": "john@example.com",
      "role": "voter",
      "createdAt": "2025-01-05T10:30:00Z"
    }
  ],
  "total": 245
}

Example

bash
curl "https://your-domain.com/api/v1/users" \
  -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 a CSV file. The CSV must include 'title' and 'description' columns. Optional columns: status, votes, tags.

Request Body

json
// Send as multipart/form-data
// Field: "file" - the CSV file

// CSV format:
// title,description,status,votes
// "Dark mode","Add dark theme","open",12
// "API access","REST API for integrations","planned",45

Response

json
{
  "success": true,
  "imported": 24,
  "skipped": 2,
  "errors": [
    { "row": 15, "message": "Missing required field: title" }
  ]
}

Example

bash
curl -X POST "https://your-domain.com/api/v1/import" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -F "file=@posts.csv"
POST/api/v1/import/nolt
Import data from a Nolt export file. Upload the JSON export file from Nolt.

Request Body

json
// Send as multipart/form-data
// Field: "file" - the Nolt JSON export file

Response

json
{
  "success": true,
  "imported": {
    "posts": 56,
    "comments": 120,
    "votes": 340
  }
}

Example

bash
curl -X POST "https://your-domain.com/api/v1/import/nolt" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -F "file=@nolt-export.json"
POST/api/v1/import/uservoice
Import data from a UserVoice export. Upload the CSV export from UserVoice.

Request Body

json
// Send as multipart/form-data
// Field: "file" - the UserVoice CSV export file

Response

json
{
  "success": true,
  "imported": {
    "posts": 130,
    "comments": 450,
    "votes": 1200
  }
}

Example

bash
curl -X POST "https://your-domain.com/api/v1/import/uservoice" \
  -H "Authorization: Bearer YOUR_API_SECRET_KEY" \
  -F "file=@uservoice-export.csv"

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
limitnumberNumber of events to return (default: 50, max: 200)
offsetnumberNumber of events to skip

Response

json
{
  "data": [
    {
      "id": "evt_1",
      "type": "post.created",
      "postId": "post_abc123",
      "actorName": "Jane Doe",
      "metadata": {},
      "createdAt": "2025-01-15T08:30:00Z"
    },
    {
      "id": "evt_2",
      "type": "post.status_changed",
      "postId": "post_abc123",
      "actorName": "Admin",
      "metadata": {
        "oldStatus": "open",
        "newStatus": "planned"
      },
      "createdAt": "2025-01-20T10:00:00Z"
    }
  ],
  "total": 1024
}

Example

bash
curl "https://your-domain.com/api/v1/activity?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
vote.createdA user voted on a post
vote.removedA user removed their vote
comment.createdA new comment was added
tag.createdA new tag was created
tag.deletedA tag was deleted

Payload Format

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

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) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(body, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    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 with exponential backoff:

  • 1st retry: after 1 minute
  • 2nd retry: after 5 minutes
  • 3rd retry: after 30 minutes
  • 4th retry: after 2 hours
  • 5th retry: after 24 hours

After 5 failed attempts, the webhook will be marked as failing and you will receive an email notification. Failed events are retained for 30 days and can be manually retried from the dashboard.

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.

JavaScript / HTML

Add this snippet before the closing </body> tag:

html
<script
  src="https://your-domain.com/widget/widget.js"
  data-project="YOUR_PUBLIC_KEY"
  data-position="bottom-right"
  data-theme="auto"
  async
></script>

Configuration Options

AttributeRequiredDescription
data-projectYesYour project's public API key
data-positionNoWidget position: bottom-right (default), bottom-left, top-right, top-left
data-themeNoTheme: auto (default), light, dark
data-localeNoLocale code (e.g. en, es, fr). Defaults to browser language
data-user-idNoPre-identify the current user by their ID
data-user-emailNoPre-identify the current user by email
data-user-nameNoDisplay name for the identified user

React Integration

jsx
import { useEffect } from 'react';

export function VoteShipWidget() {
  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://your-domain.com/widget/widget.js';
    script.dataset.project = 'YOUR_PUBLIC_KEY';
    script.dataset.position = 'bottom-right';
    script.async = true;
    document.body.appendChild(script);

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

  return null;
}

// Usage in your app:
// <VoteShipWidget />

Programmatic Control

After the widget script loads, a global window.VoteShip object becomes available:

javascript
// Open the widget
window.VoteShip.open();

// Close the widget
window.VoteShip.close();

// Identify a user (e.g. after login)
window.VoteShip.identify({
  id: 'user_123',
  email: 'jane@example.com',
  name: 'Jane Doe',
});

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