API reference
Public API
The public developer API is mounted at https://api.featurejet.com/api/v1. Every endpoint is scoped to the API key organization; unknown and cross-org boards or posts return 404. Use it to connect customer feedback to the tools where your team already decides and ships.
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 fj_live_REPLACE_MEX-API-Key: fj_live_REPLACE_MEEndpoints
GET
/api/v1/boards
Params: none
Returns: ApiBoardListResponse
GET
/api/v1/boards/{slug}
Params: slug
Returns: BoardAdmin
POST
/api/v1/boards
Params: slug, name, description
Returns: BoardAdmin, 201
POST
/api/v1/boards/{slug}/publish
Params: slug
Returns: BoardAdmin — off by default (403 publish_disabled); publishing is a dashboard step
POST
/api/v1/boards/{slug}/unpublish
Params: slug
Returns: BoardAdmin — back to draft, nothing deleted
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, queue_rank, implementation_evidence, notify_voters
Returns: PostPublic
DELETE
/api/v1/boards/{slug}/posts/{id}
Params: slug, id
Returns: 204
GET
/api/v1/boards/{slug}/queue
Params: limit=1..500 (default 10)
Returns: PostListResponse — top unclaimed posts, owner-ranked first
POST
/api/v1/boards/{slug}/posts/{id}/claim
Params: ttl_seconds=1..86400 (default 3600), agent_label
Returns: claim {post_id, agent_label, claimed_at, expires_at}; 409 if claimed by another key
DELETE
/api/v1/boards/{slug}/posts/{id}/claim
Params: slug, id
Returns: 204; 409 if another key holds the claim
POST
/api/v1/boards/{slug}/posts/{id}/evidence
Params: kind=branch|commit|pr|transcript|url, ref, label
Returns: PostPublic, 201 — evidence renders on the public post page
POST
/api/v1/boards/{slug}/posts/{id}/propose-status
Params: status=open|planned|in_progress|shipped|declined, note
Returns: PostPublic — records a PENDING proposal only; nothing changes and no voter is emailed until a human confirms
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
POST
/api/v1/boards/{slug}/posts/{id}/comments
Params: body, parent_comment_id
Returns: CommentPublic, 201 — posted as the board team
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
GET
/api/v1/webhooks
Params: none
Returns: WebhookListResponse — read-only; endpoints are created in the dashboard
Boards and posts
curl https://api.featurejet.com/api/v1/boards \
-H "Authorization: Bearer fj_live_REPLACE_ME"{
"boards": [{
"id": 1,
"slug": "acme",
"name": "Acme Feedback",
"description": "Feature requests from customers",
"welcome_message": "Tell us what we should ship 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 we should ship 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.featurejet.com/api/v1/boards/acme/posts \
-H "Authorization: Bearer fj_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.featurejet.com/api/v1/boards/acme/posts/42 \
-H "Authorization: Bearer fj_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",
"queue_rank": 1,
"implementation_evidence": [],
"claim": null,
"attachments": [],
"tags": [{"id": 7, "name": "UI", "slug": "ui", "color": null}]
}Endpoint examples
curl https://api.featurejet.com/api/v1/boards/acme \
-H "Authorization: Bearer fj_live_REPLACE_ME"curl "https://api.featurejet.com/api/v1/boards/acme/posts?status=planned&tag=ui&sort=new&limit=25&offset=0" \
-H "Authorization: Bearer fj_live_REPLACE_ME"curl https://api.featurejet.com/api/v1/boards/acme/posts/42 \
-H "Authorization: Bearer fj_live_REPLACE_ME"curl https://api.featurejet.com/api/v1/boards/acme/posts/42/votes \
-H "Authorization: Bearer fj_live_REPLACE_ME"
{"post_id":42,"vote_count":12}curl https://api.featurejet.com/api/v1/boards/acme/posts/42/comments \
-H "Authorization: Bearer fj_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 https://api.featurejet.com/api/v1/boards/acme/posts/42/comments \
-H "Authorization: Bearer fj_live_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{"body":"Shipping this week — thanks for flagging it.","parent_comment_id":9}'
{
"id": 12,
"body": "Shipping this week — thanks for flagging it.",
"is_admin": true,
"hidden": false,
"vote_count": 0,
"voter_has_voted": false,
"reaction_counts": {},
"own_reactions": [],
"voter_blocked": false,
"parent_comment_id": 9,
"created_at": "2026-07-18T09:00:00Z",
"attachments": [],
"replies": []
}curl -X DELETE https://api.featurejet.com/api/v1/boards/acme/posts/42 \
-H "Authorization: Bearer fj_live_REPLACE_ME"
# 204 No ContentAgent workflow: queue, claims, evidence, and proposals
The queue returns the top unclaimed posts in the board owner's curated order — ranked posts first (queue_rank, lower is sooner), then unranked planned posts by votes. Claim a post before working it so two agents never build the same thing: claims are atomic, carry a TTL (default one hour, max one day), extend on re-claim by the same key, and lapse on their own if the agent dies. Evidence entries are shown to voters on the public post page, so the work is verifiable, not just a status flip. Claims are never shown on public board pages.
When you want a human gate on the last step, propose-status is the agent-honest alternative to a status PATCH: it records a pending proposal, leaves the public board untouched, and emails nobody until the board owner confirms it from the dashboard. A propose-scoped key can propose but can never flip a status directly.
curl "https://api.featurejet.com/api/v1/boards/acme/queue?limit=1" \
-H "Authorization: Bearer fj_live_REPLACE_ME"
{
"posts": [{
"id": 42,
"title": "Dark mode",
"status": "planned",
"queue_rank": 1,
"vote_count": 12,
"claim": null
}],
"total": 6
}curl https://api.featurejet.com/api/v1/boards/acme/posts/42/claim \
-H "Authorization: Bearer fj_live_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{"ttl_seconds":7200,"agent_label":"overnight-agent"}'
{
"post_id": 42,
"agent_label": "overnight-agent",
"claimed_at": "2026-07-18T09:00:00Z",
"expires_at": "2026-07-18T11:00:00Z"
}
# 409 conflict if another API key holds a live claim;
# re-claiming with the SAME key extends the lease.curl -X DELETE https://api.featurejet.com/api/v1/boards/acme/posts/42/claim \
-H "Authorization: Bearer fj_live_REPLACE_ME"
# 204 No Content — claims also lapse on their own when the TTL expirescurl https://api.featurejet.com/api/v1/boards/acme/posts/42/evidence \
-H "Authorization: Bearer fj_live_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{"kind":"pr","ref":"https://github.com/acme/app/pull/12","label":"PR #12"}'
# 201 — returns the post; evidence shows in the public post page's
# "Implementation" section:
{
"implementation_evidence": [{
"kind": "pr",
"ref": "https://github.com/acme/app/pull/12",
"label": "PR #12",
"added_at": "2026-07-18T09:12:00Z"
}]
}curl https://api.featurejet.com/api/v1/boards/acme/posts/42/propose-status \
-H "Authorization: Bearer fj_live_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{"status":"shipped","note":"Shipped in PR #12 — please confirm."}'
# 200 — NOTHING changed on the public board and no voter was emailed.
# The pending proposal is visible to the org only, until a human confirms
# it from the dashboard:
{
"id": 42,
"status": "planned",
"proposal": {
"status": "shipped",
"note": "Shipped in PR #12 — please confirm.",
"proposed_at": "2026-07-18T09:20:00Z",
"proposed_by_api_key_id": 3
}
}
# 422 if the post already has the proposed status.
# A propose-scoped key can call this but can NEVER flip a status directly.Roadmap, changelog, stats, and ship velocity
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, while analytics shows whether requests are becoming launched features.
curl https://api.featurejet.com/api/v1/boards/acme/roadmap \
-H "Authorization: Bearer fj_live_REPLACE_ME"curl "https://api.featurejet.com/api/v1/boards/acme/changelog?limit=25&offset=0" \
-H "Authorization: Bearer fj_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.featurejet.com/api/v1/boards/acme/ship-stats?period=month" \
-H "Authorization: Bearer fj_live_REPLACE_ME"
{
"period": "month",
"period_label": "this month",
"shipped_count": 4,
"voter_count": 91,
"posts": []
}curl "https://api.featurejet.com/api/v1/boards/acme/analytics?period=30d" \
-H "Authorization: Bearer fj_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"
}
}Prefer a typed client? Explore the official Python and Node.js SDKs, or connect through MCP.