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_ME
X-API-Key: sw_live_REPLACE_ME

Endpoints

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

List boards
curl https://api.signalwish.com/api/v1/boards \
  -H "Authorization: Bearer sw_live_REPLACE_ME"
Board list response
{
  "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
  }]
}
Board response
{
  "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
}
Post list response
{
  "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
}
Create a post
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}'
Update a post
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}'
Post response
{
  "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

Get board
curl https://api.signalwish.com/api/v1/boards/acme \
  -H "Authorization: Bearer sw_live_REPLACE_ME"
List posts with filters
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"
Read one post
curl https://api.signalwish.com/api/v1/boards/acme/posts/42 \
  -H "Authorization: Bearer sw_live_REPLACE_ME"
Votes
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}
Comments
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": []
  }]
}
Delete post
curl -X DELETE https://api.signalwish.com/api/v1/boards/acme/posts/42 \
  -H "Authorization: Bearer sw_live_REPLACE_ME"

# 204 No Content

Roadmap, 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.

Roadmap
curl https://api.signalwish.com/api/v1/boards/acme/roadmap \
  -H "Authorization: Bearer sw_live_REPLACE_ME"
Changelog
curl "https://api.signalwish.com/api/v1/boards/acme/changelog?limit=25&offset=0" \
  -H "Authorization: Bearer sw_live_REPLACE_ME"
Roadmap response
{
  "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
}
Changelog response
{
  "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
}
Ship stats
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": []
}
Analytics
curl "https://api.signalwish.com/api/v1/boards/acme/analytics?period=30d" \
  -H "Authorization: Bearer sw_live_REPLACE_ME"
Analytics response
{
  "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"
  }
}