API reference
Public API
The public developer API is mounted at https://api.signalwish.com/api/v1. Every endpoint is scoped to the API key organization; unknown and cross-org boards or posts return 404.
Authentication
Create a key in the dashboard, then send it as a bearer token or X-API-Key. Raw keys are shown once and stored only as hashed keys with display prefixes. Keep real keys out of committed files, public terminals, and shared shell history.
Authorization: Bearer sw_live_REPLACE_MEX-API-Key: sw_live_REPLACE_MEEndpoints
GET
/api/v1/boards
Params: none
Returns: ApiBoardListResponse
GET
/api/v1/boards/{slug}
Params: slug
Returns: BoardAdmin
GET
/api/v1/boards/{slug}/posts
Params: status, q, tag, sort=top|new, limit=1..500, offset>=0
Returns: PostListResponse
POST
/api/v1/boards/{slug}/posts
Params: title, body, status, tags, pinned
Returns: PostPublic, 201
GET
/api/v1/boards/{slug}/posts/{id}
Params: slug, id
Returns: PostPublic
PATCH
/api/v1/boards/{slug}/posts/{id}
Params: title, body, status, tags, pinned, notify_voters
Returns: PostPublic
DELETE
/api/v1/boards/{slug}/posts/{id}
Params: slug, id
Returns: 204
GET
/api/v1/boards/{slug}/posts/{id}/votes
Params: slug, id
Returns: ApiVoteListResponse
GET
/api/v1/boards/{slug}/posts/{id}/comments
Params: slug, id
Returns: CommentListResponse with reaction_counts and own_reactions
GET
/api/v1/boards/{slug}/roadmap
Params: slug
Returns: PostListResponse for open, planned, in_progress, shipped
GET
/api/v1/boards/{slug}/changelog
Params: limit=1..500, offset>=0
Returns: PostListResponse for shipped posts
GET
/api/v1/boards/{slug}/ship-stats
Params: period=month|week
Returns: ShipStatsResponse
GET
/api/v1/boards/{slug}/analytics
Params: period=30d|90d
Returns: BoardAnalyticsResponse
Boards and posts
curl https://api.signalwish.com/api/v1/boards \
-H "Authorization: Bearer sw_live_REPLACE_ME"{
"boards": [{
"id": 1,
"slug": "acme",
"name": "Acme Feedback",
"description": "Feature requests from customers",
"welcome_message": "Tell us what to build next",
"published": true,
"created_at": "2026-07-09T14:00:00Z",
"logo_url": null
}]
}{
"id": 1,
"slug": "acme",
"name": "Acme Feedback",
"description": "Feature requests from customers",
"welcome_message": "Tell us what to build next",
"published": true,
"created_at": "2026-07-09T14:00:00Z",
"logo_url": null
}{
"posts": [{
"id": 42,
"title": "Dark mode",
"body": "Please add a system theme",
"status": "planned",
"vote_count": 12,
"voter_has_voted": false,
"following": false,
"is_admin": true,
"hidden": false,
"pinned": false,
"roadmap_estimate": null,
"shipped_at": null,
"created_at": "2026-07-09T14:30:00Z",
"attachments": [],
"tags": []
}],
"total": 1
}curl https://api.signalwish.com/api/v1/boards/acme/posts \
-H "Authorization: Bearer sw_live_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{"title":"Dark mode","body":"Please add a system theme","status":"planned","tags":["UI"],"pinned":false}'curl -X PATCH https://api.signalwish.com/api/v1/boards/acme/posts/42 \
-H "Authorization: Bearer sw_live_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{"status":"shipped","notify_voters":true}'{
"id": 42,
"title": "Dark mode",
"body": "Please add a system theme",
"status": "planned",
"vote_count": 12,
"voter_has_voted": false,
"following": false,
"is_admin": true,
"hidden": false,
"pinned": false,
"roadmap_estimate": null,
"shipped_at": null,
"created_at": "2026-07-09T14:30:00Z",
"attachments": [],
"tags": [{"id": 7, "name": "UI", "slug": "ui", "color": null}]
}Endpoint examples
curl https://api.signalwish.com/api/v1/boards/acme \
-H "Authorization: Bearer sw_live_REPLACE_ME"curl "https://api.signalwish.com/api/v1/boards/acme/posts?status=planned&tag=ui&sort=new&limit=25&offset=0" \
-H "Authorization: Bearer sw_live_REPLACE_ME"curl https://api.signalwish.com/api/v1/boards/acme/posts/42 \
-H "Authorization: Bearer sw_live_REPLACE_ME"curl https://api.signalwish.com/api/v1/boards/acme/posts/42/votes \
-H "Authorization: Bearer sw_live_REPLACE_ME"
{"post_id":42,"vote_count":12}curl https://api.signalwish.com/api/v1/boards/acme/posts/42/comments \
-H "Authorization: Bearer sw_live_REPLACE_ME"
{
"comments": [{
"id": 9,
"body": "We need this too",
"is_admin": false,
"hidden": false,
"vote_count": 3,
"voter_has_voted": false,
"reaction_counts": {"👍": 2},
"own_reactions": [],
"voter_blocked": false,
"parent_comment_id": null,
"created_at": "2026-07-09T14:45:00Z",
"attachments": [],
"replies": []
}]
}curl -X DELETE https://api.signalwish.com/api/v1/boards/acme/posts/42 \
-H "Authorization: Bearer sw_live_REPLACE_ME"
# 204 No ContentRoadmap, changelog, stats, and analytics
Status values are open, planned, in_progress, shipped, and declined. List endpoints default to 100 rows and cap at 500 with limit and offset. Roadmap returns visible open, planned, in_progress, and shipped posts. Changelog returns shipped posts.
curl https://api.signalwish.com/api/v1/boards/acme/roadmap \
-H "Authorization: Bearer sw_live_REPLACE_ME"curl "https://api.signalwish.com/api/v1/boards/acme/changelog?limit=25&offset=0" \
-H "Authorization: Bearer sw_live_REPLACE_ME"{
"posts": [{
"id": 42,
"title": "Dark mode",
"body": "Please add a system theme",
"status": "planned",
"vote_count": 12,
"voter_has_voted": false,
"following": false,
"is_admin": true,
"hidden": false,
"pinned": false,
"roadmap_estimate": null,
"shipped_at": null,
"created_at": "2026-07-09T14:30:00Z",
"attachments": [],
"tags": []
}],
"total": 1
}{
"posts": [{
"id": 43,
"title": "Export requests",
"body": "CSV export is now live",
"status": "shipped",
"vote_count": 18,
"voter_has_voted": false,
"following": false,
"is_admin": true,
"hidden": false,
"pinned": false,
"roadmap_estimate": null,
"shipped_at": "2026-07-09T15:00:00Z",
"created_at": "2026-07-01T12:00:00Z",
"attachments": [],
"tags": []
}],
"total": 1
}curl "https://api.signalwish.com/api/v1/boards/acme/ship-stats?period=month" \
-H "Authorization: Bearer sw_live_REPLACE_ME"
{
"period": "month",
"period_label": "this month",
"shipped_count": 4,
"voter_count": 91,
"posts": []
}curl "https://api.signalwish.com/api/v1/boards/acme/analytics?period=30d" \
-H "Authorization: Bearer sw_live_REPLACE_ME"{
"period": "30d",
"totals": {"posts": 48, "votes": 312, "comments": 86, "voters": 124},
"status_breakdown": {"open": 20, "planned": 9, "in_progress": 4, "shipped": 12, "declined": 3},
"posts_over_time": [{"date": "2026-07-09", "post_count": 3}],
"votes_over_time": [{"date": "2026-07-09", "vote_count": 18}],
"top_requests": [{"id": 42, "title": "Dark mode", "status": "planned", "vote_count": 12, "comment_count": 4, "created_at": "2026-07-09T14:30:00Z", "shipped_at": null}],
"ship_velocity": {"shipped_total": 6, "shipped_per_week": 1.4, "shipped_per_month": 6},
"engagement": {"comments_per_post": 1.79, "posts_with_votes_percent": 72.9}
}Errors and rate limits
Errors are returned as an envelope. Rate limiting is per API key, defaults to 120 requests per minute, and 429 responses include Retry-After.
{
"error": {
"code": "validation_error",
"message": "request validation failed"
}
}