API Documentation
The VoteShip REST API v1 lets you programmatically manage posts, votes, comments, tags, users, and more.
https://your-domain.com/api/v1Replace 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.
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
| Plan | Requests / minute | Requests / day |
|---|---|---|
| Free | 60 | 1,000 |
| Pro | 300 | 10,000 |
| Enterprise | 1,000 | 100,000 |
Rate-limited responses return HTTP 429 Too Many Requests with a Retry-After header.
Posts
Create, read, update, and delete feature request posts.
/api/v1/postsQuery Parameters
| Name | Type | Description |
|---|---|---|
| status | string | Filter by status (e.g. open, planned, in_progress, closed) |
| sort | string | Sort order: newest, oldest, most_votes, trending |
| limit | number | Number of results to return (default: 20, max: 100) |
| offset | number | Number of results to skip for pagination |
Response
{
"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
curl "https://your-domain.com/api/v1/posts?status=planned&sort=most_votes&limit=10" \
-H "Authorization: Bearer YOUR_API_SECRET_KEY"/api/v1/postsRequest Body
{
"title": "Dark mode support",
"description": "It would be great to have a dark theme option.",
"status": "open"
}Response
{
"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
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"
}'/api/v1/posts/:postIdResponse
{
"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
curl "https://your-domain.com/api/v1/posts/post_abc123" \
-H "Authorization: Bearer YOUR_API_SECRET_KEY"/api/v1/posts/:postIdRequest Body
{
"title": "Dark mode support (updated)",
"status": "in_progress"
}Response
{
"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
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" }'/api/v1/posts/:postIdResponse
{
"success": true,
"message": "Post deleted successfully."
}Example
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.
/api/v1/posts/:postId/votesRequest Body
{
"userId": "user_xyz"
}Response
{
"voted": true,
"voteCount": 43
}Example
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" }'Users
Retrieve users who have interacted with your board.
/api/v1/usersResponse
{
"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
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.
/api/v1/importRequest Body
// 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",45Response
{
"success": true,
"imported": 24,
"skipped": 2,
"errors": [
{ "row": 15, "message": "Missing required field: title" }
]
}Example
curl -X POST "https://your-domain.com/api/v1/import" \
-H "Authorization: Bearer YOUR_API_SECRET_KEY" \
-F "file=@posts.csv"/api/v1/import/noltRequest Body
// Send as multipart/form-data
// Field: "file" - the Nolt JSON export fileResponse
{
"success": true,
"imported": {
"posts": 56,
"comments": 120,
"votes": 340
}
}Example
curl -X POST "https://your-domain.com/api/v1/import/nolt" \
-H "Authorization: Bearer YOUR_API_SECRET_KEY" \
-F "file=@nolt-export.json"/api/v1/import/uservoiceRequest Body
// Send as multipart/form-data
// Field: "file" - the UserVoice CSV export fileResponse
{
"success": true,
"imported": {
"posts": 130,
"comments": 450,
"votes": 1200
}
}Example
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.
/api/v1/activityQuery Parameters
| Name | Type | Description |
|---|---|---|
| limit | number | Number of events to return (default: 50, max: 200) |
| offset | number | Number of events to skip |
Response
{
"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
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.
Event Types
| Event | Description |
|---|---|
| post.created | A new post was created |
| post.updated | A post was updated (title, description, or status) |
| post.deleted | A post was deleted |
| post.status_changed | A post's status was changed |
| vote.created | A user voted on a post |
| vote.removed | A user removed their vote |
| comment.created | A new comment was added |
| tag.created | A new tag was created |
| tag.deleted | A tag was deleted |
Payload Format
{
"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.
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.
JavaScript / HTML
Add this snippet before the closing </body> tag:
<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
| Attribute | Required | Description |
|---|---|---|
| data-project | Yes | Your project's public API key |
| data-position | No | Widget position: bottom-right (default), bottom-left, top-right, top-left |
| data-theme | No | Theme: auto (default), light, dark |
| data-locale | No | Locale code (e.g. en, es, fr). Defaults to browser language |
| data-user-id | No | Pre-identify the current user by their ID |
| data-user-email | No | Pre-identify the current user by email |
| data-user-name | No | Display name for the identified user |
React Integration
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:
// 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();
Comments
List and create comments on posts.
/api/v1/posts/:postId/commentsResponse
Example
/api/v1/posts/:postId/commentsRequest Body
Response
Example