Skip to content
VoteFirst Docs
Dashboard

API

API reference

Every endpoint and every object, generated from the same description a client generator reads. Using the API covers the parts that are the same on all of them: keys, identities, the response shape and the limits.

Base URL
https://app.votefirst.app/api/v3

Reading a board

The project key is all these need. Sending a voter identity as well is optional and only decides whether has_voted and is_own come back true. Every one answers with an ETag you can send back as If-None-Match.

GET /board key The board this embed key belongs to
curl "https://app.votefirst.app/api/v3/board" \
  -H "X-API-Key: $VOTEFIRST_KEY"

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.

Responses

StatusDataWhen
200 BoardDocument Success.
304 Your ETag still matches. No body.
401 The API key is missing or invalid.
403 The key belongs to another project, or the project's settings hide what this route returns.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
GET /projects/{slug} key Board structure, display settings and theme
curl "https://app.votefirst.app/api/v3/projects/your-project-slug" \
  -H "X-API-Key: $VOTEFIRST_KEY"

Path

NameTypeDescription
slugstringThe project's public slug.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.

Responses

StatusDataWhen
200 BoardDocument Success.
304 Your ETag still matches. No body.
401 The API key is missing or invalid.
403 The key belongs to another project, or the project's settings hide what this route returns.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
GET /projects/{slug}/features key List features with filters, sorting and pagination
curl "https://app.votefirst.app/api/v3/projects/your-project-slug/features" \
  -H "X-API-Key: $VOTEFIRST_KEY"

Path

NameTypeDescription
slugstringThe project's public slug.

Query

NameTypeDescription
sortstringOrder of the results. board is the order the owner arranged, which is what the hosted board draws. top follows the project's own score, which is the weighted total on a project that weights votes; votes is the plain headcount whatever the project weights by, which is what a share of voters has to be measured in. Pinned features lead every order. One of board, top, votes, trending, new, oldest, comments. Defaults to top.
boardstringA custom board slug. Omit, or send roadmap, for the roadmap itself.
columnintegerA column id from GET /projects/{slug}. Use -1 for the archived column.
scopestringSend project to list every public feature regardless of which board it is filed on. Cannot be combined with board or column. One of project.
taginteger[]A tag id. Repeat the parameter to match any of several tags.
statusstring[]Repeatable. Subject to the project's show_denied setting.
qstringFree text matched against the heading and the description.
pageintegerDefaults to 1. Minimum 1.
per_pageintegerDefaults to 20. Maximum 100.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.

Responses

StatusDataWhen
200 FeatureObject[] Success.
304 Your ETag still matches. No body.
401 The API key is missing or invalid.
403 The key belongs to another project, or the project's settings hide what this route returns.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
GET /projects/{slug}/features/{id} key One feature in full
curl "https://app.votefirst.app/api/v3/projects/your-project-slug/features/42" \
  -H "X-API-Key: $VOTEFIRST_KEY"

Path

NameTypeDescription
slugstringThe project's public slug.
idintegerThe numeric id of the feature, release or message.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.

Responses

StatusDataWhen
200 FeatureObject Success.
304 Your ETag still matches. No body.
401 The API key is missing or invalid.
403 The key belongs to another project, or the project's settings hide what this route returns.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
GET /projects/{slug}/features/{id}/comments key The comment thread on one feature
curl "https://app.votefirst.app/api/v3/projects/your-project-slug/features/42/comments" \
  -H "X-API-Key: $VOTEFIRST_KEY"

Path

NameTypeDescription
slugstringThe project's public slug.
idintegerThe numeric id of the feature, release or message.

Query

NameTypeDescription
pageintegerDefaults to 1. Minimum 1.
per_pageintegerCounts top level comments. Every reply to a returned comment is returned with it. Defaults to 20. Maximum 100.
sortstringOrders the top level comments. Replies are always oldest first. One of newest, oldest, top. Defaults to newest.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.

Responses

StatusDataWhen
200 CommentObject[] Success.
304 Your ETag still matches. No body.
401 The API key is missing or invalid.
403 The key belongs to another project, or the project's settings hide what this route returns.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
GET /projects/{slug}/changelog key Published releases, featured first, then most recent
curl "https://app.votefirst.app/api/v3/projects/your-project-slug/changelog" \
  -H "X-API-Key: $VOTEFIRST_KEY"

Path

NameTypeDescription
slugstringThe project's public slug.

Query

NameTypeDescription
pageintegerDefaults to 1. Minimum 1.
per_pageintegerDefaults to 20. Maximum 100.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.

Responses

StatusDataWhen
200 ReleaseObject[] Success.
304 Your ETag still matches. No body.
401 The API key is missing or invalid.
403 The key belongs to another project, or the project's settings hide what this route returns.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
GET /projects/{slug}/changelog/{id} key One release with its body and features
curl "https://app.votefirst.app/api/v3/projects/your-project-slug/changelog/42" \
  -H "X-API-Key: $VOTEFIRST_KEY"

Path

NameTypeDescription
slugstringThe project's public slug.
idintegerThe numeric id of the feature, release or message.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.

Responses

StatusDataWhen
200 ReleaseObject Success.
304 Your ETag still matches. No body.
401 The API key is missing or invalid.
403 The key belongs to another project, or the project's settings hide what this route returns.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
GET /projects/{slug}/messages key The messages this caller should be shown right now
curl "https://app.votefirst.app/api/v3/projects/your-project-slug/messages" \
  -H "X-API-Key: $VOTEFIRST_KEY"

Path

NameTypeDescription
slugstringThe project's public slug.

Query

NameTypeDescription
platformstringWhich platform is asking. A message aimed at one platform is only sent to that platform, and sending nothing here returns only the messages aimed at everybody. One of ios, macos, web.
app_versionstringThe version of your app that is asking, such as 2.1.0. A message with a version range is only sent to a build inside it, and sending nothing here excludes every message that names one.
pageintegerDefaults to 1. Minimum 1.
per_pageintegerDefaults to 20. Maximum 100.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.

Responses

StatusDataWhen
200 MessageObject[] Success.
304 Your ETag still matches. No body.
401 The API key is missing or invalid.
403 The key belongs to another project, or the project's settings hide what this route returns, or this project's owner is not on a PRO plan.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.

Taking part

Each of these needs a voter identity as well as the key, and each accepts an Idempotency-Key so that a retry cannot double the effect.

POST /projects/{slug}/identity key Mint or exchange a voter identity
curl -X POST "https://app.votefirst.app/api/v3/projects/your-project-slug/identity" \
  -H "X-API-Key: $VOTEFIRST_KEY" \
  -H "X-VF-Anon: $VOTEFIRST_VOTER"

Path

NameTypeDescription
slugstringThe project's public slug.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.
Idempotency-KeystringA value of your choosing, unique per intended write. Replaying a write with the same key returns the original response and creates nothing new. Kept for 24 hours. Up to 200 characters.

Body

Optional. Send an SSO token to exchange it, or an anonymous token to renew it.

FieldTypeRequiredDescription
anon_token string No An anonymous token you already hold. Presenting it renews it and keeps that voter's history, where sending nothing mints a new voter. May be sent as X-VF-Anon instead.
sso_token string No A token your own server signed with this project's SSO secret. Exchanged for the voter it names. May be sent as X-VF-Token instead.

Responses

StatusDataWhen
200 IdentityObject The identity you presented is still valid, or your SSO token resolved to a voter that already exists.
201 IdentityObject Success.
401 The API key is missing or invalid, or no voter identity was sent.
403 The key belongs to another project, or the project's settings forbid this action.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
409 The write conflicts with what is already recorded, such as voting twice, or withdrawing a vote you never cast. The second carries code not_found, which is the one place that code does not mean 404.
413 The request body is larger than this API accepts.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
POST /projects/{slug}/features key Suggest a feature
curl -X POST "https://app.votefirst.app/api/v3/projects/your-project-slug/features" \
  -H "X-API-Key: $VOTEFIRST_KEY" \
  -H "X-VF-Anon: $VOTEFIRST_VOTER" \
  -H "Content-Type: application/json" \
  -d '{"description": "It would help when I work at night.", "heading": "Dark mode"}'

Path

NameTypeDescription
slugstringThe project's public slug.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.
Idempotency-KeystringA value of your choosing, unique per intended write. Replaying a write with the same key returns the original response and creates nothing new. Kept for 24 hours. Up to 200 characters.

Body

The suggestion.

FieldTypeRequiredDescription
description string Yes The body. Plain text; it is not rendered as markdown. Up to 2000 characters.
heading string Yes The one line title, as the person suggesting it would write it. Up to 254 characters.

Responses

StatusDataWhen
201 FeatureObject Success.
401 The API key is missing or invalid, or no voter identity was sent.
403 The key belongs to another project, or the project's settings forbid this action.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
409 The write conflicts with what is already recorded, such as voting twice, or withdrawing a vote you never cast. The second carries code not_found, which is the one place that code does not mean 404.
413 The request body is larger than this API accepts.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
POST /projects/{slug}/features/{id}/vote key Vote for a feature
curl -X POST "https://app.votefirst.app/api/v3/projects/your-project-slug/features/42/vote" \
  -H "X-API-Key: $VOTEFIRST_KEY" \
  -H "X-VF-Anon: $VOTEFIRST_VOTER"

Path

NameTypeDescription
slugstringThe project's public slug.
idintegerThe numeric id of the feature, release or message.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.
Idempotency-KeystringA value of your choosing, unique per intended write. Replaying a write with the same key returns the original response and creates nothing new. Kept for 24 hours. Up to 200 characters.

Responses

StatusDataWhen
200 VoteObject Success.
401 The API key is missing or invalid, or no voter identity was sent.
403 The key belongs to another project, or the project's settings forbid this action.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
409 The write conflicts with what is already recorded, such as voting twice, or withdrawing a vote you never cast. The second carries code not_found, which is the one place that code does not mean 404.
413 The request body is larger than this API accepts.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
DELETE /projects/{slug}/features/{id}/vote key Withdraw your vote
curl -X DELETE "https://app.votefirst.app/api/v3/projects/your-project-slug/features/42/vote" \
  -H "X-API-Key: $VOTEFIRST_KEY" \
  -H "X-VF-Anon: $VOTEFIRST_VOTER"

Path

NameTypeDescription
slugstringThe project's public slug.
idintegerThe numeric id of the feature, release or message.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.
Idempotency-KeystringA value of your choosing, unique per intended write. Replaying a write with the same key returns the original response and creates nothing new. Kept for 24 hours. Up to 200 characters.

Responses

StatusDataWhen
200 VoteObject Success.
401 The API key is missing or invalid, or no voter identity was sent.
403 The key belongs to another project, or the project's settings forbid this action.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
409 The write conflicts with what is already recorded, such as voting twice, or withdrawing a vote you never cast. The second carries code not_found, which is the one place that code does not mean 404.
413 The request body is larger than this API accepts.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
POST /projects/{slug}/features/{id}/comments key Comment on a feature
curl -X POST "https://app.votefirst.app/api/v3/projects/your-project-slug/features/42/comments" \
  -H "X-API-Key: $VOTEFIRST_KEY" \
  -H "X-VF-Anon: $VOTEFIRST_VOTER" \
  -H "Content-Type: application/json" \
  -d '{"content": "Agreed, this is the one I keep wanting."}'

Path

NameTypeDescription
slugstringThe project's public slug.
idintegerThe numeric id of the feature, release or message.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.
Idempotency-KeystringA value of your choosing, unique per intended write. Replaying a write with the same key returns the original response and creates nothing new. Kept for 24 hours. Up to 200 characters.

Body

The comment.

FieldTypeRequiredDescription
content string Yes What to say. Plain text. Up to 2000 characters.
parent_id integer No Reply to this top level comment. Replies are one level deep.

Responses

StatusDataWhen
201 CommentObject Success.
401 The API key is missing or invalid, or no voter identity was sent.
403 The key belongs to another project, or the project's settings forbid this action.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
409 The write conflicts with what is already recorded, such as voting twice, or withdrawing a vote you never cast. The second carries code not_found, which is the one place that code does not mean 404.
413 The request body is larger than this API accepts.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
DELETE /projects/{slug}/comments/{cid} key Delete your own comment
curl -X DELETE "https://app.votefirst.app/api/v3/projects/your-project-slug/comments/7" \
  -H "X-API-Key: $VOTEFIRST_KEY" \
  -H "X-VF-Anon: $VOTEFIRST_VOTER"

Path

NameTypeDescription
slugstringThe project's public slug.
cidintegerThe numeric id of the comment.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.
Idempotency-KeystringA value of your choosing, unique per intended write. Replaying a write with the same key returns the original response and creates nothing new. Kept for 24 hours. Up to 200 characters.

Responses

StatusDataWhen
200 CommentDeletedObject Success.
401 The API key is missing or invalid, or no voter identity was sent.
403 The key belongs to another project, or the project's settings forbid this action.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
409 The write conflicts with what is already recorded, such as voting twice, or withdrawing a vote you never cast. The second carries code not_found, which is the one place that code does not mean 404.
413 The request body is larger than this API accepts.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
POST /projects/{slug}/comments/{cid}/like key Like or unlike a comment
curl -X POST "https://app.votefirst.app/api/v3/projects/your-project-slug/comments/7/like" \
  -H "X-API-Key: $VOTEFIRST_KEY" \
  -H "X-VF-Anon: $VOTEFIRST_VOTER"

Path

NameTypeDescription
slugstringThe project's public slug.
cidintegerThe numeric id of the comment.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.
Idempotency-KeystringA value of your choosing, unique per intended write. Replaying a write with the same key returns the original response and creates nothing new. Kept for 24 hours. Up to 200 characters.

Responses

StatusDataWhen
200 LikeObject Success.
401 The API key is missing or invalid, or no voter identity was sent.
403 The key belongs to another project, or the project's settings forbid this action.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
409 The write conflicts with what is already recorded, such as voting twice, or withdrawing a vote you never cast. The second carries code not_found, which is the one place that code does not mean 404.
413 The request body is larger than this API accepts.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
POST /projects/{slug}/messages/{id}/dismiss key Put a message away for this voter
curl -X POST "https://app.votefirst.app/api/v3/projects/your-project-slug/messages/42/dismiss" \
  -H "X-API-Key: $VOTEFIRST_KEY" \
  -H "X-VF-Anon: $VOTEFIRST_VOTER"

Path

NameTypeDescription
slugstringThe project's public slug.
idintegerThe numeric id of the feature, release or message.

Headers

NameTypeDescription
X-VF-AnonstringThe anonymous voter token from POST /projects/{slug}/identity. Required on every write.
X-VF-TokenstringA voter SSO token signed with the project's SSO secret. Must carry an exp claim. Outranks X-VF-Anon.
Idempotency-KeystringA value of your choosing, unique per intended write. Replaying a write with the same key returns the original response and creates nothing new. Kept for 24 hours. Up to 200 characters.

Responses

StatusDataWhen
200 MessageDismissedObject Success.
401 The API key is missing or invalid, or no voter identity was sent.
403 The key belongs to another project, or the project's settings forbid this action.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
409 The write conflicts with what is already recorded, such as voting twice, or withdrawing a vote you never cast. The second carries code not_found, which is the one place that code does not mean 404.
413 The request body is larger than this API accepts.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.

Service

Open routes. No key, no identity, and no rate limit tied to a key.

GET /health open Liveness probe
curl "https://app.votefirst.app/api/v3/health"

Responses

StatusDataWhen
200 HealthObject Success.
304 Your ETag still matches. No body.
405 This path does not take that method.
422 A parameter or field failed validation. The error names it.
GET /openapi.json open The OpenAPI 3.1 description of this API
curl "https://app.votefirst.app/api/v3/openapi.json"

Responses

StatusDataWhen
200 This document.
304 Your ETag still matches. No body.
500 The document could not be generated.
GET /docs open The documentation for this API and the embed SDK
curl "https://app.votefirst.app/api/v3/docs"

Responses

StatusDataWhen
301 The reference page moved into the documentation site. Follow Location.

Revenue

The money behind the votes. These two take your secret key only: the embed key is refused, because it is published in your own page source, and the project's owner has to be on a PRO plan. Every amount is in cents.

GET /projects/{slug}/voters key The voters this project has billing on, what they pay and what they voted for
curl "https://app.votefirst.app/api/v3/projects/your-project-slug/voters" \
  -H "X-API-Key: $VOTEFIRST_KEY"

Path

NameTypeDescription
slugstringThe project's public slug.

Query

NameTypeDescription
customer_idstringA billing id, matched against both the Stripe and the Paddle column. An id nobody on this board carries returns an empty list rather than a 404, which is the ordinary case: most of your customers have never voted.
churnedbooleanSend true for the voters who have left, false for the ones who stayed. Omit for both.
feature_idintegerOnly the voters who voted for that feature. Combined with churned=true this is how many of the people asking for it have already left.
pageintegerDefaults to 1. Minimum 1.
per_pageintegerDefaults to 20. Maximum 100.

Responses

StatusDataWhen
200 RevenueVoterObject[] Success.
304 Your ETag still matches. No body.
401 The API key is missing or invalid.
403 The key belongs to another project, or you sent the embed key, which this route refuses because it is published in your own page source, or this project's owner is not on a PRO plan.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.
GET /projects/{slug}/revenue/features key Every voted feature with the revenue behind it
curl "https://app.votefirst.app/api/v3/projects/your-project-slug/revenue/features" \
  -H "X-API-Key: $VOTEFIRST_KEY"

Path

NameTypeDescription
slugstringThe project's public slug.

Query

NameTypeDescription
sortstringOrder of the results. mrr is the revenue behind each feature, votes the plain headcount, weighted the project's own weighted total, priority that total against the effort the owner recorded. One of mrr, votes, weighted, priority. Defaults to mrr.
pageintegerDefaults to 1. Minimum 1.
per_pageintegerDefaults to 20. Maximum 100.

Responses

StatusDataWhen
200 RevenueFeatureObject[] Success.
304 Your ETag still matches. No body.
401 The API key is missing or invalid.
403 The key belongs to another project, or you sent the embed key, which this route refuses because it is published in your own page source, or this project's owner is not on a PRO plan.
404 No such project, feature, comment or release. A private project answers this way too.
405 This path does not take that method.
422 A parameter or field failed validation. The error names it.
429 Rate limit reached. See Retry-After.
500 Something on our side failed. The code is internal and the request id in X-Request-Id identifies it.

Objects

What arrives in data. Every key listed is always present; a value that does not apply is null rather than absent, so nothing here is optional in the decoding sense. A nested object is listed under its dotted path rather than given a name of its own.

BoardDocument 29
FieldTypeDescription
boards BoardEntry[] Every public board, the roadmap first.
capabilities.can_comment boolean Whether to draw a comment box.
capabilities.can_like boolean Whether to draw a like control on a comment.
capabilities.can_suggest boolean Whether to offer the suggestion form. Takes both the board's setting and the suggestion form's own.
capabilities.can_vote boolean Whether to draw a vote control anywhere. The project's own setting, before any column's.
display.allow_comments boolean Whether the project accepts comments.
display.allow_suggestions boolean Whether the project accepts new suggestions.
display.allow_voting boolean Whether the project accepts votes at all. A column can still refuse one on its own.
display.default_mode string Which view the hosted board opens on, for a client that would rather agree with it than choose.
display.score_is_weighted boolean Whether score is the weighted total rather than the plain count of votes.
display.show_archived boolean Whether the archived column exists at all. It is column -1 when it does.
display.show_comments boolean Whether comments are public. False sends comment_count as null and closes the comment routes.
display.show_denied boolean Whether declined suggestions are public. False removes the denied column and its features.
display.show_progress boolean Whether the progress figure is public. False sends progress as null.
display.show_views boolean Whether view counts are public. False sends view_count as null.
display.show_votes boolean Whether vote counts are public. False sends vote_count and score as null on every feature.
project.banner_url string or null An absolute address of the project's banner image, or null.
project.currency_symbol string The symbol to put in front of a money figure this project shows.
project.description string or null The line under the project's name on its public board, if it has one.
project.logo_url string or null An absolute address of the project's logo, or null.
project.name string The project's name.
project.show_branding boolean Whether the hosted board shows the VoteFirst line at the bottom. A client rendering its own interface may honour it or not.
project.slug string The name in every path of this API, and in the address of the public board.
project.voter_count integer How many distinct people have voted on a public suggestion in this project. The denominator behind a share of voters, which no single page of features can give you.
tags Tag[] Every tag in the project, by name.
theme.dark ThemeColors The palette for a dark ground.
theme.font string The family the hosted board loads. A page that has not loaded it should inherit its own instead.
theme.light ThemeColors The palette for a light ground.
theme.radius string The corner radius the project chose, as a CSS length.
BoardEntry 5
FieldTypeDescription
columns Column[] This board's columns, in the order they are drawn.
id integer 0 for the roadmap, which is not a row of its own.
kind string roadmap or custom. Every project has exactly one roadmap.
name string The board's name, for a heading.
slug string Send this as the board parameter to list this board alone.
BoardRef 4
FieldTypeDescription
id integer 0 for the roadmap, which is not a row of its own.
kind string Which of the two this is. Every project has exactly one roadmap. One of roadmap, custom.
name string The board's name, for a heading.
slug string Send this as the board parameter to list this board alone.
Column 6
FieldTypeDescription
color string or null A hex colour the owner chose for the column, or null if they chose none.
id integer Send this as the column parameter to list this column alone. -1 is the archived column.
is_votable boolean Whether a vote on a feature in this column would be accepted. Draw the vote control from this rather than by trying the vote and reading the refusal.
name string The column's name, as the owner named it.
position integer Where the column sits, left to right. The columns arrive in this order already.
status string or null The status a feature in this column reports. Null on a custom board column, which has no status of its own.
ColumnRef 3
FieldTypeDescription
color string or null A hex colour the owner chose for the column, or null if they chose none.
id integer Send this as the column parameter to list this column alone. -1 is the archived column.
name string The column's name, as the owner named it.
CommentDeletedObject 3
FieldTypeDescription
comment_count integer or null The feature's new comment count. Null when the project does not show comments.
deleted boolean Always true. The failure cases arrive as an error instead.
feature_id integer The feature the comment was on.
CommentObject 11
FieldTypeDescription
author_name string Who wrote it: their account name, the name their SSO token carried, or a generated handle.
author_role string or null owner or editor, set when the author speaks for the project, so a client can mark an official reply. Null for everybody else, project members who can only read included.
content string What was written. Plain text.
created_at string When it was written, RFC 3339 in UTC.
feature_id integer The feature the thread belongs to.
has_liked boolean Whether the voter identity on this request has liked it.
id integer This comment's id, which the like and delete routes take.
is_own boolean Whether the voter identity on this request wrote it, which is what decides whether to offer a delete.
like_count integer How many likes it has.
parent_id integer or null The comment this one replies to, or null if it is top level. Replies are one level deep.
replies CommentObject[] The replies to this comment, in the order they were written. Every reply to a returned comment is returned with it and none of them is counted against per_page.
Error 3
FieldTypeDescription
code string The stable half of the failure, and the only half worth branching on. One of not_found, missing_key, invalid_key, key_wrong_project, identity_required, identity_invalid, not_allowed, validation_failed, already_exists, rate_limited, too_large, internal, voter_banned, origin_not_allowed, idempotency_in_progress, idempotency_mismatch.
field string or null The field at fault when the code is validation_failed. Null otherwise.
message string Written for a person to read. It may change between releases; the code will not.
FeatureObject 25
FieldTypeDescription
board BoardRef Which board this is filed on.
changelog_type string or null How it was filed in the changelog: new, improvement, fix, security or breaking_change. Null outside a release.
column ColumnRef Which column of that board it sits in.
comment_count integer or null How many comments it has, replies included. Null when the project does not show comments.
created_at string When it was suggested, RFC 3339 in UTC.
description string or null The body, as the author wrote it. Plain text, and null when there is none.
developer_response string or null The project owner's own answer to this feature, as they wrote it. Plain text. Null when there is none.
has_voted boolean Whether the voter identity on this request has a vote on it. False when no identity was sent.
heading string The one line title.
id integer This feature's id, which every other route takes in its path.
is_approved boolean Whether it has been through review. It only means anything in the suggestions column, which is where review happens: a feature of any other type carries whatever value it was created with, and a listing gates on this flag for suggestions alone. The feature POST /features answers with is a suggestion waiting for the owner, so it is false.
is_pinned boolean Whether the owner pinned it. Pinned features lead every ordering.
linked_feature FeatureRef or null Another feature the owner marked this one a duplicate of, or null.
progress integer or null How far along, 0 to 100. Null when the project does not show progress, or when nobody has set it.
release ReleaseRef or null The release that shipped it, or null if it has not shipped.
response_is_denial boolean Whether that answer is a refusal, so a client can call it a denial reason rather than a response. Read this rather than the status: archiving a declined feature reports it as archived and the refusal would be lost.
score integer or null The number to rank on. Carries the weighted total when the project uses weighted demand, the plain count otherwise.
score_is_weighted boolean Whether the score above is weighted. The same for every feature in a project; repeated here so a single feature carries its own answer.
start_date string or null When work on it began, RFC 3339 in UTC, or null.
status string Treat this as extensible. A client should tolerate a member added later rather than fail to decode. One of open, planned, in_progress, done, denied, archived.
tags Tag[] Every tag on this feature.
target_date string or null When the owner expects it, RFC 3339 in UTC, or null.
updated_at string When it last changed, RFC 3339 in UTC.
view_count integer or null How many times it has been opened. Null when the project does not show views.
vote_count integer or null How many people voted. Null when the project does not show votes.
FeatureRef 2
FieldTypeDescription
heading string Its one line title, so you can name it without a second request.
id integer The other feature's id.
HealthObject 2
FieldTypeDescription
status string ok when this installation is answering.
version string Which API version this is. 3.
IdentityObject 6
FieldTypeDescription
anon_token string or null Store this and send it as X-VF-Anon on every later request. Null for an SSO identity, where the SSO token is the identity.
banned boolean Whether this project has banned this voter from writing. True means comments and suggestions will be refused with voter_banned, while reading, voting and liking still work, so a client can hide the controls rather than fail at the end.
display_name string What this voter is called on their comments: their real name if a token carried one, a generated handle otherwise.
kind string Whether this identity was minted here or came from a token your own server signed. One of anonymous, sso.
voted_feature_ids integer[] Every feature this voter has a vote on, so a client can draw its vote controls in the right state without asking per feature.
voter_id string Who this is, as this project knows them. Stable for as long as the identity is.
LikeObject 3
FieldTypeDescription
comment_id integer The comment that was liked.
has_liked boolean Whether this voter now likes it. The route toggles, so this is how you know which way it went.
like_count integer The new count.
MessageDismissedObject 2
FieldTypeDescription
dismissed boolean Always true. It will not be sent to this voter again on any device.
message_id integer The message that was put away.
MessageObject 13
FieldTypeDescription
action_label string or null The label of the single action, or null when there is none. Never shown without action_url.
action_url string or null Where the action goes, or null. One action per message, never a list.
body string What it says. Plain text: it is rendered by your app, so no markup is ever sent.
created_at string When it was written, RFC 3339 in UTC.
ends_at string or null When it stops, RFC 3339 in UTC, or null if it runs until the owner ends it. It is already gone from this list once it passes.
id integer The message's id, which the dismiss route takes.
is_dismissible boolean Whether to offer a way to put it away. False means the owner wants it seen until they end it, so draw no dismiss control.
kind string How serious it is. Your app decides what each one looks like; no colours are sent. Treat this as extensible. One of info, warning, critical.
presentation string A hint, not a command. banner sits at the top of a screen and can be ignored, alert interrupts. Render either one your own way. One of banner, alert.
priority integer Higher comes first. The list is already in this order, so it only matters if you re-sort.
starts_at string or null When it began, RFC 3339 in UTC, or null if it always applied.
title string The one line heading, as the owner wrote it.
updated_at string When it last changed, RFC 3339 in UTC.
Meta 5
FieldTypeDescription
has_more boolean Whether asking for the next page would return anything. Cheaper to branch on than comparing the two numbers above.
page integer Which page this is. One based.
per_page integer How many items were asked for, which is how many are here unless this is the last page.
total integer How many items match, across every page.
total_pages integer How many pages that comes to.
ReleaseObject 9
FieldTypeDescription
body string or null The release note as markdown, or null.
body_html string or null The same note rendered to HTML on the server, with raw HTML refused and dangerous link schemes blanked. Null when there is no note.
features object[] What shipped in this release.
id integer The release's id.
is_featured boolean Whether the owner marked it a headline release.
name string The release's name.
published_at string or null When it was published, RFC 3339 in UTC.
slug string The name in its address on the public changelog.
view_count integer or null How many times it has been opened. Null when the project does not show views.
ReleaseRef 2
FieldTypeDescription
id integer The release's id, which GET /changelog/{id} takes.
name string The release's name.
RevenueFeatureObject 10
FieldTypeDescription
churned_voters integer How many of those have since left. This is the number behind the sentence nineteen of the forty seven people asking for this already left.
currency_symbol string The symbol the owner set for this project. No ISO currency code is stored, so none is sent.
heading string Its one line title. This route answers to your secret key alone, so it lists features the public board does not, including ones still awaiting approval.
id integer The feature's id.
mrr_cents integer The monthly revenue behind it, in cents: every voter with a tier, counted once, at their own amount or their tier's.
paying_voters integer How many of the voters behind it you have billing on. The rest voted without a tier and are worth nothing here.
status string Which column it sits in. One of open, planned, in_progress, done, denied, archived.
top_tier string or null The tier that cast the most votes for it, or null when no voter with a tier has.
vote_count integer How many people asked for it.
weighted_votes integer The same votes counted by tier weight.
RevenueTierRefObject 4
FieldTypeDescription
id integer The tier's id, as the owner's tier settings know it.
mrr_cents integer What the tier itself is worth per month, in cents. A voter with an override is worth their own amount instead.
multiplier integer How many plain votes one vote from this tier counts as, where the project weights votes.
name string What the owner calls this tier.
RevenueVoteRefObject 3
FieldTypeDescription
feature_id integer The feature they voted for.
heading string Its one line title.
voted_at string When the vote was cast, RFC 3339 in UTC.
RevenueVoterObject 20
FieldTypeDescription
canceled_at string or null When they cancelled, RFC 3339 in UTC, or null.
churn_source string or null What recorded the departure, such as stripe or manual, or null.
churned boolean Whether this voter has left. This is what lets you say how many of the people asking for something are already gone.
churned_at string or null When they left, RFC 3339 in UTC, or null.
company string or null The company the owner recorded against them, or null.
currency_symbol string The symbol the owner set for this project, such as $. This product stores a symbol and no ISO currency code, so none is sent.
customer_id string or null Their id in that billing system, or null. This is the value the customer_id filter takes.
display_name string or null What they are called on the board, or null if nobody named them.
last_synced_at string or null When this row was last refreshed from the billing system, RFC 3339 in UTC, or null.
matched_on string or null Which billing system this voter's id came from, or null if they carry neither. One of stripe, paddle, <nil>.
mrr_cents integer What this voter is worth per month, in cents. Never a decimal and never a formatted string.
mrr_source string Where that number came from: an amount set on this voter, the tier they are in, or nothing, which is how a real zero is told apart from a voter with no tier. One of override, tier, none.
subscribed_at string or null When they started paying, RFC 3339 in UTC, or null.
subscription_id string or null Their subscription in that billing system, or null.
subscription_status string or null The status the billing system last reported, such as active or past_due, or null. It is passed through, not interpreted.
tier RevenueTierRefObject or null
trial_ends_at string or null When their trial ends, RFC 3339 in UTC, or null.
vote_count integer How many features they have voted for in total, which may be more than the list below holds.
voter_id string Who this is, as this project knows them.
votes RevenueVoteRefObject[] What they voted for, newest first, at most 100. Compare its length with vote_count to see whether it was cut.
Tag 2
FieldTypeDescription
id integer Send this as the tag parameter to filter by it.
name string The tag as the project's owner wrote it.
ThemeColors 6
FieldTypeDescription
accent string The one colour the project chose. Buttons, links and the vote control wear it.
background string The page behind everything, as a hex colour.
border string The hairline between one thing and the next.
card_background string The surface a feature card sits on.
text string The colour of ordinary text.
text_secondary string The colour of the quieter text: dates, counts, metadata.
VoteObject 4
FieldTypeDescription
feature_id integer The feature that was voted on.
has_voted boolean Whether this voter now has a vote on it. True after a vote, false after a withdrawal.
score integer or null The new score. Null when the project does not show votes.
vote_count integer or null The new count. Null when the project does not show votes.