Authentication
All API requests require authentication via the X-API-Key header. You can find your project API key in the FeedbackKit dashboard under Project Settings.
X-API-Key: your-project-api-key
X-User-Id: optional-user-id # For personalized has_voted state
Content-Type: application/json
Optional: X-User-Id Header
Include the X-User-Id header to get personalized has_voted state in feedback responses. Without it, has_voted will always be false.
Field naming
Response bodies are snake_case — every field below is spelled exactly as it arrives on the wire.
Request bodies accept either casing. Both {"userId": …} and {"user_id": …} decode to the same field, so the camelCase request examples below are correct and so is the snake_case every official SDK sends.
Query parameters are read verbatim — neither convention applies, so includeMerged is spelled exactly as documented.
Endpoints Overview
All endpoints are prefixed with /api/v1. The base URL for production is https://api.feedbackkit.app.
| Method | Path | Description |
|---|---|---|
| GET | /feedbacks | List all feedback |
| POST | /feedbacks | Submit new feedback |
| GET | /feedbacks/:id | Get feedback by ID |
| POST | /feedbacks/:id/votes | Vote for feedback |
| DELETE | /feedbacks/:id/votes | Remove vote |
| GET | /feedbacks/:id/comments | List comments |
| POST | /feedbacks/:id/comments | Add comment |
| POST | /users/register | Register SDK user |
| POST | /events/track | Track analytics event |
| GET | /metrics/summary | Aggregate metrics for the key’s project |
| GET | /metrics/timeseries | Bucketed metrics for the key’s project |
| GET | /metrics/breakdown | Metrics grouped by one dimension |
| GET | /metrics/events | Raw event feed (Pro) |
| GET | /projects/:id/metrics/summary | Aggregate metrics, incl. revenue measures (admin token) |
| GET | /projects/:id/metrics/timeseries | Bucketed metrics (admin token) |
| GET | /projects/:id/metrics/breakdown | Grouped metrics (admin token) |
| GET | /projects/:id/metrics/events | Raw event feed (admin token, Pro) |
Feedback
/feedbacks
Retrieves all feedback for the project, sorted by vote count (descending). Merged feedback items are excluded by default.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| status | string | Filter by status: pending, approved, in_progress, testflight, completed, rejected |
| category | string | Filter by category: feature_request, bug_report, improvement, other |
| includeMerged | boolean | Include merged feedback items. Default: false |
| sort | string | Sort order: votes, newest, oldest, comments. Default: votes |
curl -X GET "https://api.feedbackkit.app/api/v1/feedbacks?status=approved" \
-H "X-API-Key: your-api-key" \
-H "X-User-Id: user_12345"
Response
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Add dark mode support",
"description": "It would be great to have a dark mode option.",
"status": "approved",
"category": "feature_request",
"user_id": "user_12345",
"vote_count": 42,
"has_voted": false,
"comment_count": 5,
"created_at": "2026-02-01T10:30:00Z",
"updated_at": "2026-02-05T14:20:00Z"
}
]
/feedbacks
Creates a new feedback item. The creator automatically receives a vote (vote_count starts at 1). Triggers notifications to project members and configured integrations.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Feedback title (1-200 chars) |
| description | string | Yes | Detailed description (1-5000 chars) |
| category | string | Yes | feature_request, bug_report, improvement, other |
| userId | string | Yes | Unique identifier of the submitting user |
| userEmail | string | No | Email for status update notifications |
curl -X POST "https://api.feedbackkit.app/api/v1/feedbacks" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"title": "Add dark mode support",
"description": "It would be great to have a dark mode option.",
"category": "feature_request",
"userId": "user_12345",
"userEmail": "user@example.com"
}'
/feedbacks/:id
Retrieves a single feedback item by its UUID.
curl -X GET "https://api.feedbackkit.app/api/v1/feedbacks/550e8400-e29b-41d4-a716-446655440000" \
-H "X-API-Key: your-api-key" \
-H "X-User-Id: user_12345"
Votes
/feedbacks/:id/votes
Adds a vote to a feedback item. Each user can only vote once per feedback.
Restrictions
- Cannot vote on archived projects (403)
- Cannot vote on completed or rejected feedback (403)
- Cannot vote twice on the same feedback (409)
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| userId | string | Yes | Unique identifier of the voting user |
| string | No | Email for status change notifications | |
| notifyStatusChange | boolean | No | Opt-in to email notifications on status changes. Default: false |
curl -X POST "https://api.feedbackkit.app/api/v1/feedbacks/550e8400-.../votes" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"userId": "user_12345",
"email": "user@example.com",
"notifyStatusChange": true
}'
Response
{
"feedback_id": "550e8400-e29b-41d4-a716-446655440000",
"vote_count": 43,
"has_voted": true
}
/feedbacks/:id/votes
Removes the user's vote from a feedback item.
curl -X DELETE "https://api.feedbackkit.app/api/v1/feedbacks/550e8400-.../votes" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"userId": "user_12345"}'
Users
/users/register
Registers or updates an SDK user. Use this to track user activity and associate MRR (Monthly Recurring Revenue). If the user already exists, their last_seen_at timestamp and MRR will be updated.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| userId | string | Yes | Unique identifier of the SDK user |
| mrr | number | No | Monthly Recurring Revenue for this user |
curl -X POST "https://api.feedbackkit.app/api/v1/users/register" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"userId": "user_12345",
"mrr": 9.99
}'
Response
{
"id": "770e8400-e29b-41d4-a716-446655440000",
"user_id": "user_12345",
"mrr": 9.99,
"first_seen_at": "2026-01-15T08:00:00Z",
"last_seen_at": "2026-02-08T12:30:00Z"
}
Events
/events/track
Tracks a custom event for analytics. Use this to measure user engagement with the feedback system.
Common Events
| Event Name | Description |
|---|---|
| feedback_list | User viewed the feedback list |
| feedback_detail | User viewed feedback details |
| submit_feedback | User opened the submit form |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| eventName | string | Yes | Name of the event (1-100 chars) |
| userId | string | Yes | Unique identifier of the user |
| properties | object | No | Key-value properties for the event |
curl -X POST "https://api.feedbackkit.app/api/v1/events/track" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"eventName": "feedback_list",
"userId": "user_12345",
"properties": {
"filter": "feature_request",
"sort": "votes"
}
}'
Metrics
Eight read-only operations return aggregated project metrics and the raw event feed. Four answer for the project behind an X-API-Key; four are nested under /projects/:id/metrics, take an admin bearer token, and additionally answer the SDK-user and revenue measures.
The machine-readable document is the authority for field names
This page deliberately does not restate the metrics response schemas. Every property name, type and example lives in the OpenAPI document at /openapi/openapi.yaml, which is generated from a single source and checked against the server’s own encoder by an automated drift test. A second, hand-maintained copy of those names on this page is exactly how the published contract came to declare field names the server had never sent.
A narrowed answer is a 200, not an error
Two subscription axes can narrow a metrics answer, and neither makes the feature unavailable. Every metrics response echoes what the server actually ran, so a low figure always arrives with a machine-readable reason attached — read the value, never infer it from an absence.
| Axis | Free | Pro | Team |
|---|---|---|---|
| Custom event names — events your app names itself | not readable | readable | readable |
| Metrics window — how far back a query may reach | 30 days | 30 days | 365 days |
FeedbackKit’s own three predefined events — feedback_list, feedback_detail and submit_feedback — are readable on every tier. Below Pro your own event names are still ingested and retained; they simply are not counted, and they become readable on upgrade with no re-tracking.
event_scope—all, orpredefined_onlywhen custom event names were excluded. It narrows only the two event measures,event_countandunique_event_users; no other measure is ever affected by it.retention_clamped— true when your plan narrowed the window you asked for. The request still succeeds.retention_window_days— your plan’s allowance. Together with the applieddate_fromanddate_tothis makes “showing 30 of the 180 days you asked for” computable from the response alone.
The one 402
METRICS_CUSTOM_EVENTS_REQUIRE_PRO is this family’s only payment-required refusal. It is raised on exactly three requests, each of which is exclusively about custom event names and so has no narrower honest answer: a breakdown by event name, a filter naming an event outside the three predefined ones, and any request to the raw event feed. Everything else a plan affects comes back as a narrower 200.
Two different error bodies
The metrics family is not uniformly enveloped, and a client that assumes an error code is always present will fail on a 401. Failures raised by the metrics API itself — its 400s, the 402, and the API-key surface’s measure 403 — carry error as an object with a stable code. Failures raised by the shared authentication, authorization and rate-limiting layers — 401, the suspended-project and no-access 403s, 404, 429 and 500 — use the server-wide shape below, where error is a boolean and there is no code. Same property name, incompatible types; the OpenAPI document declares both for every status that can carry either.
Models
FeedbackResponse
| Field | Type | Description |
|---|---|---|
| id | UUID | Unique identifier |
| title | string | Feedback title |
| description | string | Detailed description |
| status | FeedbackStatus | Current status of the feedback |
| category | FeedbackCategory | Feedback category |
| user_id | string | ID of the submitting user |
| user_email | string? | Email of the submitter (if provided) |
| vote_count | integer | Total number of votes |
| has_voted | boolean | Whether the current user has voted |
| comment_count | integer | Total number of comments |
| total_mrr | number? | Combined MRR of all voters |
| created_at | ISO 8601 | Creation timestamp |
| updated_at | ISO 8601 | Last update timestamp |
| rejection_reason | string? | Explanation for rejection (max 500 chars) |
| merged_into_id | UUID? | ID of feedback this was merged into |
| merged_at | ISO 8601? | When this feedback was merged |
| merged_feedback_ids | UUID[]? | IDs of feedback merged into this one |
FeedbackStatus
| Value | Description | Voting |
|---|---|---|
| pending | New, awaiting review | Allowed |
| approved | Accepted for consideration | Allowed |
| in_progress | Currently being worked on | Allowed |
| testflight | Available in beta/TestFlight | Allowed |
| completed | Shipped and available | Blocked |
| rejected | Won't be implemented | Blocked |
FeedbackCategory
| Value | Description |
|---|---|
| feature_request | New functionality request |
| bug_report | Issue or problem report |
| improvement | Enhancement to existing feature |
| other | General feedback |
VoteResponse
| Field | Type | Description |
|---|---|---|
| feedback_id | UUID | ID of the feedback item |
| vote_count | integer | Updated vote count |
| has_voted | boolean | Whether the user has voted after this action |
CommentResponse
| Field | Type | Description |
|---|---|---|
| id | UUID | Unique identifier |
| content | string | Comment text |
| user_id | string | ID of the commenting user |
| is_admin | boolean | Whether this is an admin comment |
| created_at | ISO 8601 | Creation timestamp |
Error Handling
All errors follow a consistent format with an error boolean and a human-readable reason string.
{
"error": true,
"reason": "Invalid API key"
}
HTTP Status Codes
| Code | Name | Description |
|---|---|---|
| 200 | OK | Request succeeded |
| 400 | Bad Request | Validation error (missing or invalid fields) |
| 401 | Unauthorized | Missing or invalid API key |
| 402 | Payment Required | Subscription tier limit exceeded |
| 403 | Forbidden | Action not allowed (archived project, completed feedback, etc.) |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Duplicate action (e.g., already voted) |
| 429 | Too Many Requests | Rate limit exceeded |
Subscription Tiers
Some API features are limited by subscription tier. Exceeding limits returns a 402 Payment Required response.
| Feature | Free | Pro | Team |
|---|---|---|---|
| Projects | 1 | 2 | Unlimited |
| Feedback per project | 10 | Unlimited | Unlimited |
| Team members | - | - | Unlimited |
| Integrations | - | All | All |
| Voter notifications | - | - | Yes |
Comments
/feedbacks/:id/commentsRetrieves all comments for a feedback item, sorted by creation time (ascending).
Response
/feedbacks/:id/commentsAdds a comment to a feedback item. Cannot comment on archived projects.
Request Body
false