# Upvoty Public API Last updated: 2026-05-28 The Upvoty Public API provides programmatic access to feedback boards, roadmaps, changelogs, and user management. ## Authentication All requests must include an API key in the `X-Upvoty-Key` header: ``` X-Upvoty-Key: upvoty_sk_your-secret-key ``` Two key types are available: - **Secret key** (`upvoty_sk_...`) — Full server-side access to all API operations. Never expose in client-side code. - **Publishable key** (`upvoty_pk_...`) — Restricted permissions for client-side use (e.g., voting, SSO). Safe to embed in frontend code. Manage your API keys in **Settings > Public API** in the Upvoty dashboard. ### User context with publishable keys Publishable keys support an optional `Authorization: Bearer ` header to identify the end user making the request. This is required for user-specific actions like voting, commenting, and submitting feedback. To obtain a session token, call `POST /auth/sso` with a JWT signed using your project's Custom SSO private key: ``` X-Upvoty-Key: upvoty_pk_your-publishable-key Authorization: Bearer ``` ### Publishable key permissions Publishable keys have per-action permissions configured in the dashboard. Each action can be individually enabled/disabled, and optionally allow guest (unauthenticated) users. | Action | Controls | |--------|----------| | Submit Feedback | Creating feedback items | | Vote Feedback | Voting on feedback items | | Comment Feedback | Commenting on feedback items | | React Feedback | Reacting to feedback items and comments | | Vote Roadmap | Voting on roadmap items | | Comment Changelog | Commenting on changelog entries | | React Changelog | Reacting to changelog entries and comments | | Subscribe Changelog | Subscribing/unsubscribing to the changelog | When an action is disabled, the API returns `403` with a message indicating which action is not enabled. When **Allow Guests** is off and no Bearer token is provided, the API returns `401`. ## Rate Limiting All responses include rate limit headers: - `X-RateLimit-Limit` — Maximum requests per minute - `X-RateLimit-Remaining` — Requests remaining in the current window - `X-RateLimit-Reset` — Unix timestamp when the window resets When the limit is exceeded, the API returns `429 Too Many Requests`. ## For AI Agents and LLMs This API ships with multiple documentation surfaces optimized for programmatic consumption. Prefer these over crawling the interactive `/docs` page (which is a JavaScript-rendered SPA): - **[`llms.txt`](https://api.upvotyfeedback.com/llms.txt)** — index of every endpoint with markdown links to per-endpoint files. Start here. - **`/llms/.md`** — one self-contained markdown file per endpoint with parameters, request body, and responses. Example: [`llms/list-feedback-items.md`](https://api.upvotyfeedback.com/llms/list-feedback-items.md). Fetch only the operations you need. - **[`llms-full.txt`](https://api.upvotyfeedback.com/llms-full.txt)** — complete reference inlined into a single document. Use when you want everything in one shot. Base URL: https://api.upvotyfeedback.com/v1 ## Pagination The API uses two pagination styles: ### Offset pagination Used by most list endpoints. Pass `pagination[page]` and `pagination[perPage]` as query parameters. Responses include a `meta` object: ```json { "data": [...], "meta": { "total": 150, "page": 1, "perPage": 25, "lastPage": 6 } } ``` ### Cursor pagination Used by comment endpoints. Pass `pagination[cursor]` and `pagination[limit]` as query parameters. Responses include a `meta` object: ```json { "data": [...], "meta": { "hasMore": true, "cursor": "eyJpZCI6MTAwfQ==" } } ``` When `hasMore` is `false`, there are no more pages. Pass the `cursor` value to fetch the next page. ## Error Responses All error responses follow this structure: ```json { "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "reason": "FIELD_REQUIRED", "fields": { "title": ["Title is required"] } } } ``` - `error.code` (string) — Machine-readable error code - `error.message` (string) — Human-readable error message - `error.reason` (string, optional) — Specific reason for the error - `error.fields` (object, optional) — Per-field validation errors, keyed by field name with an array of messages ## Instructions for LLM Agents When building integrations with the Upvoty API, follow these guidelines for best results: - **Use the `expand` parameter** to include related objects (category, status, author, tags) inline instead of making separate requests. - **Use batch endpoints** (`POST /feedback-items/batch`, `PATCH /feedback-items/batch`, etc.) for bulk operations instead of looping single-item calls. - **Publishable keys require SSO session tokens** for write operations. Call `POST /auth/sso` first to obtain a Bearer token. - **The `filter[parent]` parameter** controls merged feedback visibility. By default, merged items are excluded. Set `filter[parent][null]=false` to find merged items. - **Offset pagination** is the default. Always check `meta.lastPage` to know when to stop paginating. - **Comment endpoints use cursor pagination**. Keep fetching while `meta.hasMore` is `true`. - **Do not include `message` in successful response handling** — the Upvoty API never returns `message` in success responses. If a response contains `message`, treat it as an error. - **Secret keys** (`upvoty_sk_*`) are for server-side only. Never expose them in client-side code. Use publishable keys (`upvoty_pk_*`) for frontend integrations. ## Auth Authentication and session management ### POST /auth/sso Exchange SSO token for session Exchanges a JWT token signed with the project's Custom SSO private key for a session token. The returned session token can be used as a Bearer token in subsequent requests with a publishable API key. The JWT must be signed using HS256 with the project's Custom SSO private key (configured in project settings). No audience or issuer validation is performed. **Required JWT payload fields** (at least one of `id` or `email` must be present): - `id` (string, optional) — External user identifier - `email` (string, optional) — User email address - `name` (string, optional) — Display name - `avatar` (string, optional) — Avatar URL (must start with `http`, max 2048 characters, must be a permanent non-expiring URL) - `extra_data` (object, optional) — Additional user metadata This endpoint requires a **publishable API key**. Secret keys are rejected with a 403 error. **Request body:** - `token` (string, required) — JWT signed with the project's Custom SSO private key (HS256) **Responses:** - `200`: SSO authentication successful - `data` (object, required) - `401`: Invalid or expired JWT token - `error` (object, required) - `403`: Secret key used (only publishable keys are allowed) - `error` (object, required) - `422`: Missing token or Custom SSO not configured - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ## Feedback Feedback board operations ### GET /feedback-items List feedback items Retrieves a paginated list of feedback items for the project. By default, archived and merged items are excluded. Use `filter[archived]=true` to include archived items, and `filter[parent]` to find merged children. Private items are only visible when using a secret API key. Publishable keys automatically filter out private items. Supports full-text search across title and content. Use `#tagname` in the search query to filter by tag. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. - `search` (string, query, optional) — Full-text search query - `expand` (string, query, optional) — Comma-separated list of relations to expand inline. Supported fields: `category`, `status`, `author`, `parent`, `tags`. - `filter[category]` (string, query, optional) — Filter by category ID. Use `filter[category][in]` for multiple values (comma-separated). - `filter[status]` (string, query, optional) — Filter by status ID. Use `filter[status][in]` for multiple values (comma-separated). Use `filter[status][null]=true` to find items without a status. - `filter[priority]` (string, query, optional) — Filter by priority level. Use `filter[priority][in]` for multiple values (comma-separated). - `filter[archived]` (string, query, optional) — Filter by archived status. Defaults to `false` (non-archived items only). - `filter[private]` (string, query, optional) — Filter by privacy status (secret key only). Publishable keys always see only public items. - `filter[pinned]` (string, query, optional) — Filter by pinned status - `filter[tag][in]` (string, query, optional) — Filter by tag IDs (comma-separated). Returns items that have at least one of the specified tags. - `filter[assignee]` (string, query, optional) — Filter by assignee ID. Use `filter[assignee][null]=true` for unassigned items, `filter[assignee][null]=false` for assigned items. - `filter[author]` (string, query, optional) — Filter by author account ID - `filter[created_at][gte]` (string, query, optional) — Filter items created on or after this timestamp - `filter[created_at][lte]` (string, query, optional) — Filter items created on or before this timestamp - `filter[created_at][between]` (string, query, optional) — Filter items created between two timestamps (comma-separated) - `filter[updated_at][gte]` (string, query, optional) — Filter items updated on or after this timestamp - `filter[updated_at][lte]` (string, query, optional) — Filter items updated on or before this timestamp - `filter[updated_at][between]` (string, query, optional) — Filter items updated between two timestamps (comma-separated) - `filter[parent]` (string, query, optional) — Filter by merge parent ID to find child items merged into a parent. Use `filter[parent][null]=true` to find only un-merged items (default behavior), or `filter[parent][null]=false` to find only merged items. **Responses:** - `200`: A paginated list of feedback items - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items Create a feedback item Creates a new feedback item. A `category_id` is required. If no `status_id` is provided, the project's default status is assigned automatically. With a **publishable key**, the authenticated portal user is the author. Only public fields can be set (`title`, `content`, `category_id`, `status_id`, `launch_date`, `custom_fields`, `tags`). With a **secret key**, all fields are available including `author_id`, `private`, `pinned`, `archived`, `priority`, `email`, `source`, `votes_offset`, and `assignee_id`. Tags can be attached by passing an array of tag IDs. **Auto-vote on creation:** when the feedback item is created with an author (either the authenticated portal user via publishable key, or `author_id` via secret key), an upvote from that author is automatically added and counted toward `votes_count`. Items created without an author get no auto-vote. **Request body:** - `title` (string, required) — Title of the feedback item - `content` (string, optional) — Detailed description, may contain HTML - `priority` (integer, optional) — Priority level (0 = none, 1-5 stars) - `category_id` (string, required) — ID of the category to assign - `status_id` (string, optional) — ID of the status to assign. If omitted, the project default status is used. - `private` (boolean, optional) — Whether the item should be private (team-only). Defaults to the project setting. - `pinned` (boolean, optional) — Whether the item should be pinned to the top of lists - `archived` (boolean, optional) — Whether the item should be archived (hidden from default lists) - `source` (string, optional) — Source of the feedback — a page URL, app name, or any free text describing where it came from - `launch_date` (string, optional) — Target launch date or timeframe for the requested feature - `email` (string, optional) — Email of the feedback submitter - `external_user_id` (string, optional) — External user identifier (e.g. from your system) - `votes_offset` (integer, optional) — Manual vote count offset to add to the organic count - `image_private` (boolean, optional) — Whether the screenshot is restricted to team-only visibility - `custom_fields` (object, optional) — Custom field key-value pairs. Keys must match defined fields in the project — unknown keys are stripped. Values are validated against field type (text or number). Publishable keys can only set public fields; secret keys can set all fields. - `tags` (array of string, optional) — Array of tag IDs to attach - `assignee_id` (string, optional) — ID of a team member to assign - `author_id` (string, optional) — ID of the portal account to set as the author **Responses:** - `201`: Feedback item created successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `422`: Validation or business rule error (e.g. category not found) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-items/{id} Get a feedback item Retrieves a single feedback item by its ID. Private items are only accessible with a secret API key. Use the `expand` parameter to include related objects inline. **Parameters:** - `expand` (string, query, optional) — Comma-separated list of relations to expand inline. Supported fields: `category`, `status`, `author`, `parent`, `tags`. **Responses:** - `200`: The requested feedback item - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item not found (or private and using publishable key) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /feedback-items/{id} Update a feedback item Updates an existing feedback item. Only provided fields are changed. Changing the `status_id` automatically updates the `status_changed_at` timestamp. Requires a secret API key. **Request body:** - `title` (string, optional) — Title of the feedback item - `content` (string, optional) — Detailed description, may contain HTML. Set to null to clear. - `priority` (integer, optional) — Priority level (0 = none, 1-5 stars) - `category_id` (string, optional) — ID of the category to assign - `status_id` (string, optional) — ID of the status to assign, or null to clear - `private` (boolean, optional) — Whether the item is private - `source` (string, optional) — Source of the feedback — a page URL, app name, or any free text describing where it came from - `launch_date` (string, optional) — Target launch date or timeframe, or null to clear - `email` (string, optional) — Email of the feedback submitter - `external_user_id` (string, optional) — External user identifier - `votes_offset` (integer, optional) — Manual vote count offset - `image_private` (boolean, optional) — Whether the screenshot is restricted to team-only - `custom_fields` (object, optional) — Custom field key-value pairs. Keys must match defined fields in the project — unknown keys are stripped. Values are validated against field type (text or number). Publishable keys can only set public fields; secret keys can set all fields. - `tags` (array of string, optional) — Array of tag IDs (replaces all current tags) - `assignee_id` (string, optional) — ID of a team member to assign, or null to unassign - `author_id` (string, optional) — ID of a portal account to set as the author. Changes the author, updates email, and resets replied status. - `notify_status_change` (boolean, optional) — When true and status_id is changed, sends email notifications to all upvoters about the status change. Assignee notifications are always sent automatically. **Responses:** - `200`: Feedback item updated successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation or business rule error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/{id} Delete a feedback item Permanently deletes a feedback item and its merged children. This action cannot be undone. Use the archive endpoint instead if you want to hide the item without deleting it. Requires a secret API key. **Responses:** - `204`: Feedback item deleted successfully - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/archive Archive a feedback item Archives a feedback item. Archived items are excluded from default list queries. Requires a secret API key. **Responses:** - `201`: Feedback item archived successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/{id}/archive Unarchive a feedback item Restores an archived feedback item so it appears in default list queries again. Requires a secret API key. **Responses:** - `204`: Feedback item unarchived successfully - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/pin Pin a feedback item Pins a feedback item so it appears at the top of lists when sorted by default order. Requires a secret API key. **Responses:** - `201`: Feedback item pinned successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/{id}/pin Unpin a feedback item Removes the pin from a feedback item, restoring it to normal sort order. Requires a secret API key. **Responses:** - `204`: Feedback item unpinned successfully - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/merge Merge a feedback item into another Merges a feedback item into a parent item. The merged (child) item is hidden from default list queries. Its votes are consolidated with the parent. An item cannot be merged with itself. Requires a secret API key. **Request body:** - `parent_id` (string, required) — ID of the parent feedback item to merge into **Responses:** - `201`: Feedback item merged successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Business rule error (e.g. parent not found, cannot merge with itself) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/{id}/merge Unmerge a feedback item Removes a feedback item from its merge parent, restoring it as an independent item. Requires a secret API key. **Responses:** - `204`: Feedback item unmerged successfully - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/privacy Make a feedback item private Sets a feedback item to private so it is only visible to team members using a secret API key. Requires a secret API key. **Responses:** - `201`: Feedback item set to private - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/{id}/privacy Make a feedback item public Restores a private feedback item to public visibility on the portal. Requires a secret API key. **Responses:** - `204`: Feedback item set to public - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/reply Send a reply to the feedback author Marks the feedback item as replied and sends an email notification to the author (if `subject` and `message` are provided and the author has an email address). Sets the `replied` flag and `replied_at` timestamp on the item. Requires a secret API key. **Request body:** - `subject` (string, required) — Subject line for the reply email - `message` (string, required) — Reply message body **Responses:** - `201`: Reply sent successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/duplicate Duplicate a feedback item Creates a copy of the feedback item with the same category, status, content, tags, and custom fields. The duplicate starts with zero organic votes. Requires a secret API key. **Responses:** - `201`: Feedback item duplicated successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/screenshot Upload a screenshot for a feedback item Uploads an image file and attaches it as the feedback item's screenshot. If a screenshot already exists, it is replaced. Requires a secret API key. **Responses:** - `200`: Screenshot uploaded successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Business rule error (e.g. invalid file type) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/{id}/screenshot Delete a feedback item screenshot Removes the screenshot from a feedback item and deletes the file from storage. Requires a secret API key. **Responses:** - `204`: Screenshot deleted successfully - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-items/{id}/votes List votes on a feedback item Retrieves a cursor-paginated list of votes on a feedback item. Private items are only accessible with a secret API key. **Parameters:** - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return - `expand` (string, query, optional) — Comma-separated list of relations to expand inline. Supported fields: `voter`. **Responses:** - `200`: A cursor-paginated list of votes - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/votes Vote on a feedback item Casts a vote on a feedback item. With a **publishable key**, the authenticated portal user is the voter. With a **secret key**, you must provide a `voter_id` to vote on behalf of a user. Down-votes are only available if the project has downvotes enabled in portal settings. **Request body:** - `type` (enum: up | down, optional) — Direction of the vote - `voter_id` (string, optional) — ID of the portal account casting the vote (secret key only) **Responses:** - `201`: Vote cast successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Business rule error (e.g. voter not found, duplicate vote, downvotes disabled) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/{id}/votes Remove a vote from a feedback item Removes a vote from a feedback item. With a **publishable key**, the authenticated portal user's vote is removed. With a **secret key**, you must provide `voter_id` in the request body. **Request body:** - `voter_id` (string, optional) — ID of the portal account whose vote to remove (secret key only) **Responses:** - `204`: Vote removed successfully - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Business rule error (e.g. voter not identified, vote not found) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-items/{id}/reactions List reactions on a feedback item Retrieves a cursor-paginated list of emoji reactions on a feedback item. Private items are only accessible with a secret API key. **Parameters:** - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return **Responses:** - `200`: A cursor-paginated list of reactions - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/reactions React to a feedback item Adds an emoji reaction to a feedback item. With a **publishable key**, the authenticated portal user is the reactor. With a **secret key**, you must provide a `reactor_id` to react on behalf of a user. Reactions must be enabled in the project's portal settings and the emoji must be in the allowed set (if configured). **Request body:** - `emoji` (string, required) — The emoji to react with - `reactor_id` (string, optional) — ID of the portal account reacting (secret key only) **Responses:** - `201`: Reaction created successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Business rule error (e.g. reactions disabled, invalid emoji, duplicate reaction) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/{id}/reactions Remove a reaction from a feedback item Removes a specific emoji reaction from a feedback item. With a **publishable key**, the authenticated portal user's reaction is removed. With a **secret key**, you must provide `reactor_id` in the request body. **Request body:** - `emoji` (string, required) — The emoji reaction to remove - `reactor_id` (string, optional) — ID of the portal account whose reaction to remove (secret key only) **Responses:** - `204`: Reaction removed successfully - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Business rule error (e.g. reactor not identified, reaction not found) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-items/{id}/comments List comments on a feedback item Retrieves a cursor-paginated list of comments on a specific feedback item. Internal and private comments are only visible when using a secret API key. Private feedback items are also only accessible with a secret API key. **Parameters:** - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return **Responses:** - `200`: A cursor-paginated list of comments - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/comments Add a comment to a feedback item Creates a new comment on a feedback item. With a **publishable key**, the authenticated portal user is the author. With a **secret key**, you must provide an `author_id` to comment on behalf of a user. Internal comments (visible only to the team) require a secret API key. Use `reply_to_comment` to create a threaded reply. **Request body:** - `text` (string, required) — Comment body, may contain HTML - `internal` (boolean, optional) — Whether this comment is internal (team-only). Requires secret API key. - `private` (boolean, optional) — Whether this comment is private (only visible to author and team). Defaults to the project setting. - `author_id` (string, optional) — ID of the portal account to set as the author (secret key only) - `reply_to_comment` (string, optional) — ID of the comment to reply to (for threaded comments) - `notify_voters` (boolean, optional) — Whether to send email notifications to all voters of the feedback item. Requires secret API key. Ignored for internal comments. **Responses:** - `201`: Comment created successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item not found - `error` (object, required) - `422`: Business rule error (e.g. empty text, internal comments require secret key) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-items/{id}/comments/{commentId} Get a comment on a feedback item Retrieves a single comment by its ID. Internal comments are only accessible with a secret API key. **Responses:** - `200`: The requested comment - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item or comment not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /feedback-items/{id}/comments/{commentId} Update a comment on a feedback item Updates an existing comment. Only provided fields are changed. With a **publishable key**, the authenticated portal user can only update their own comments. With a **secret key**, any comment can be updated. The `archived` field requires a secret key. **Request body:** - `text` (string, optional) — Updated comment body - `private` (boolean, optional) — Whether the comment should be private - `archived` (boolean, optional) — Whether to archive the comment **Responses:** - `200`: Comment updated successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Forbidden — not the comment author (publishable key) or authentication required - `error` (object, required) - `404`: Feedback item or comment not found - `error` (object, required) - `422`: Validation error (e.g. empty text) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/{id}/comments/{commentId} Delete a comment from a feedback item Permanently deletes a comment. With a **publishable key**, the authenticated portal user can only delete their own comments. With a **secret key**, any comment can be deleted. **Responses:** - `204`: Comment deleted successfully - `401`: Authentication failed - `error` (object, required) - `403`: Forbidden — not the comment author (publishable key) or authentication required - `error` (object, required) - `404`: Feedback item or comment not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-items/{id}/comments/{commentId}/likes List likes on a comment Retrieves a cursor-paginated list of likes on a specific comment. Private feedback items are only accessible with a secret API key. **Parameters:** - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return **Responses:** - `200`: A cursor-paginated list of comment likes - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item or comment not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/comments/{commentId}/likes Like a comment Adds a like to a comment. With a **publishable key**, the authenticated portal user is the liker. With a **secret key**, you must provide a `liker_id` to like on behalf of a user. **Request body:** - `liker_id` (string, optional) — ID of the portal account liking the comment (secret key only) **Responses:** - `201`: Comment liked successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item or comment not found - `error` (object, required) - `422`: Business rule error (e.g. liker not identified, already liked) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/{id}/comments/{commentId}/likes Unlike a comment Removes a like from a comment. With a **publishable key**, the authenticated portal user's like is removed. With a **secret key**, you must provide `liker_id` in the request body. **Request body:** - `liker_id` (string, optional) — ID of the portal account whose like to remove (secret key only) **Responses:** - `204`: Like removed successfully - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item or comment not found - `error` (object, required) - `422`: Business rule error (e.g. liker not identified, like not found) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-items/{id}/comments/{commentId}/reactions List reactions on a comment Retrieves a cursor-paginated list of emoji reactions on a specific comment. Private feedback items are only accessible with a secret API key. **Parameters:** - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return **Responses:** - `200`: A cursor-paginated list of comment reactions - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item or comment not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/{id}/comments/{commentId}/reactions React to a comment Adds an emoji reaction to a comment. With a **publishable key**, the authenticated portal user is the reactor. With a **secret key**, you must provide a `reactor_id` to react on behalf of a user. Reactions must be enabled in the project's portal settings and the emoji must be in the allowed set (if configured). **Request body:** - `emoji` (string, required) — The emoji to react with - `reactor_id` (string, optional) — ID of the portal account reacting (secret key only) **Responses:** - `201`: Reaction created successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item or comment not found - `error` (object, required) - `422`: Business rule error (e.g. reactions disabled, invalid emoji, duplicate reaction) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/{id}/comments/{commentId}/reactions Remove a reaction from a comment Removes a specific emoji reaction from a comment. With a **publishable key**, the authenticated portal user's reaction is removed. With a **secret key**, you must provide `reactor_id` in the request body. **Request body:** - `emoji` (string, required) — The emoji reaction to remove - `reactor_id` (string, optional) — ID of the portal account whose reaction to remove (secret key only) **Responses:** - `204`: Reaction removed successfully - `401`: Authentication failed - `error` (object, required) - `404`: Feedback item or comment not found - `error` (object, required) - `422`: Business rule error (e.g. reactor not identified, reaction not found) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-categories List feedback categories Retrieves a paginated list of feedback categories for the project. Categories organize feedback items into logical groups (e.g. "Feature Request", "Bug Report"). **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. - `filter[hidden]` (string, query, optional) — Filter by visibility. Hidden categories are not shown on the public portal. - `filter[label][like]` (string, query, optional) — Filter categories whose label contains this substring (case-insensitive) **Responses:** - `200`: A paginated list of feedback categories - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-categories Create a feedback category Creates a new feedback category for the project. Requires a secret API key. **Request body:** - `label` (string, required) — Display name for the category - `color` (string, optional) — Hex color code for the category - `icon` (string, optional) — Icon identifier - `hidden` (boolean, optional) — Whether to hide the category from the public portal - `position` (integer, optional) — Display order position **Responses:** - `201`: Category created successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-categories/{id} Get a feedback category Retrieves a single feedback category by its ID. **Responses:** - `200`: The requested feedback category - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Category not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /feedback-categories/{id} Update a feedback category Updates an existing feedback category. Only provided fields are changed. Requires a secret API key. **Request body:** - `label` (string, optional) — Display name for the category - `color` (string, optional) — Hex color code for the category - `icon` (string, optional) — Icon identifier - `hidden` (boolean, optional) — Whether to hide the category from the public portal - `position` (integer, optional) — Display order position **Responses:** - `200`: Category updated successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Category not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-statuses List feedback statuses Retrieves a paginated list of feedback statuses for the project. Statuses represent the lifecycle stage of feedback items (e.g. "Under Review", "Planned", "Completed"). **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. - `filter[hidden]` (string, query, optional) — Filter by portal visibility - `filter[default]` (string, query, optional) — Filter to only the default status **Responses:** - `200`: A paginated list of feedback statuses - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-statuses Create a feedback status Creates a new feedback status for the project. Requires a secret API key. **Request body:** - `label` (string, required) — Display name for the status - `color` (string, optional) — Hex color code for the status - `icon` (string, optional) — Icon identifier - `hidden` (boolean, optional) — Whether to hide the status from the public portal - `roadmap_hidden` (boolean, optional) — Whether to hide the status from the public roadmap - `default` (boolean, optional) — Whether this should be the default status for new feedback items. Only one status can be the default — setting this to `true` will automatically unset the previous default status. - `position` (integer, optional) — Display order position **Responses:** - `201`: Status created successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-statuses/{id} Get a feedback status Retrieves a single feedback status by its ID. **Responses:** - `200`: The requested feedback status - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Status not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /feedback-statuses/{id} Update a feedback status Updates an existing feedback status. Only provided fields are changed. Requires a secret API key. **Request body:** - `label` (string, optional) — Display name for the status - `color` (string, optional) — Hex color code for the status - `icon` (string, optional) — Icon identifier - `hidden` (boolean, optional) — Whether to hide the status from the public portal - `roadmap_hidden` (boolean, optional) — Whether to hide the status from the public roadmap - `default` (boolean, optional) — Whether this should be the default status for new feedback items - `position` (integer, optional) — Display order position **Responses:** - `200`: Status updated successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Status not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-tags List feedback tags Retrieves a paginated list of feedback tags for the project. Tags provide flexible, user-defined labels that can be attached to feedback items. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. - `filter[label][like]` (string, query, optional) — Filter tags whose label contains this substring (case-insensitive) **Responses:** - `200`: A paginated list of feedback tags - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-tags Create a feedback tag Creates a new feedback tag for the project. Requires a secret API key. **Request body:** - `label` (string, required) — Display name for the tag **Responses:** - `201`: Tag created successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-tags/{id} Get a feedback tag Retrieves a single feedback tag by its ID. **Responses:** - `200`: The requested feedback tag - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Tag not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /feedback-tags/{id} Update a feedback tag Updates an existing feedback tag. Requires a secret API key. **Request body:** - `label` (string, required) — Display name for the tag **Responses:** - `200`: Tag updated successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Tag not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-tags/{id} Delete a feedback tag Permanently deletes a feedback tag. The tag will be detached from all feedback items. Requires a secret API key. **Responses:** - `204`: Tag deleted successfully - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Tag not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-fields List custom feedback fields Retrieves a paginated list of custom fields defined for the project's feedback board. Custom fields allow teams to capture structured data beyond title and description. Requires a secret API key. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. - `filter[archived]` (string, query, optional) — Filter by archived status. Defaults to showing non-archived fields. - `filter[type]` (string, query, optional) — Filter by field type - `filter[visibility]` (string, query, optional) — Filter by field visibility **Responses:** - `200`: A paginated list of custom feedback fields - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-fields Create a custom feedback field Creates a new custom field for the project's feedback board. The `key` is auto-generated from the label. Requires a secret API key. **Request body:** - `label` (string, required) — Display name for the field - `description` (string, optional) — Optional description explaining the field's purpose - `type` (enum: text | number | select | multiselect | checkbox | url | date, required) — Data type of the field. Cannot be changed after creation. - `visibility` (enum: public | private, optional) — Who can see this field - `required` (boolean, optional) — Whether this field must be filled in when creating feedback - `public_value` (boolean, optional) — Whether the field value is visible to public portal users - `enabled_on_widget` (boolean, optional) — Whether this field appears in the embeddable widget - `options` (array of string, optional) — Available options (required for select and multiselect types) **Responses:** - `201`: Custom field created successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `422`: Validation or business rule error (e.g. options required for select type) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-fields/{id} Get a custom feedback field Retrieves a single custom feedback field by its ID. Requires a secret API key. **Responses:** - `200`: The requested custom field - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Custom field not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /feedback-fields/{id} Update a custom feedback field Updates an existing custom feedback field. The `type` cannot be changed after creation. Only provided fields are changed. Requires a secret API key. **Request body:** - `label` (string, optional) — Display name for the field - `description` (string, optional) — Optional description explaining the field's purpose - `visibility` (enum: public | private, optional) — Who can see this field - `required` (boolean, optional) — Whether this field must be filled in when creating feedback - `public_value` (boolean, optional) — Whether the field value is visible to public portal users - `enabled_on_widget` (boolean, optional) — Whether this field appears in the embeddable widget - `options` (array of string, optional) — Available options (for select and multiselect types) - `position` (integer, optional) — Display order position - `archived` (boolean, optional) — Whether to archive this field **Responses:** - `200`: Custom field updated successfully - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret API key required - `error` (object, required) - `404`: Custom field not found - `error` (object, required) - `422`: Validation or business rule error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-comments List comments across all feedback items Retrieves a cursor-paginated list of comments across all feedback items in the project. Supports filtering by feedback item, author, visibility, and date range. Internal and private comments are only visible when using a secret API key. **Parameters:** - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return - `filter[feedback_item]` (string, query, optional) — Filter comments belonging to a specific feedback item ID - `filter[author]` (string, query, optional) — Filter comments by author ID - `filter[internal]` (string, query, optional) — Filter by internal status (secret key only) - `filter[private]` (string, query, optional) — Filter by private status (secret key only) - `filter[created_at][gte]` (string, query, optional) — Filter comments created on or after this timestamp - `filter[created_at][lte]` (string, query, optional) — Filter comments created on or before this timestamp **Responses:** - `200`: A cursor-paginated list of comments - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /feedback-votes List votes across all feedback items Retrieves a cursor-paginated list of votes across every feedback item in the project. Use this to fetch all votes in one call instead of looping over each feedback item. Supports filtering by feedback item, voter, vote type, and date range. Votes on private items are only visible with a secret API key. **Parameters:** - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return - `expand` (string, query, optional) — Comma-separated list of relations to expand inline. Supported fields: `voter`, `feedback_item`. - `filter[feedback_item]` (string, query, optional) — Filter votes belonging to a specific feedback item ID - `filter[voter]` (string, query, optional) — Filter votes cast by a specific voter ID - `filter[type]` (string, query, optional) — Filter by vote direction - `filter[created_at][gte]` (string, query, optional) — Filter votes cast on or after this timestamp - `filter[created_at][lte]` (string, query, optional) — Filter votes cast on or before this timestamp **Responses:** - `200`: A cursor-paginated list of votes - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-items/batch Batch create feedback items Creates multiple feedback items in a single request. All items are processed atomically — if any item fails validation, the entire batch is rejected. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `201`: All items created successfully - `data` (array of object, required) - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `422`: Validation failed for one or more items - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /feedback-items/batch Batch update feedback items Updates multiple feedback items in a single request. Each item must include its `id` along with the fields to update. All items are processed atomically. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `200`: All items updated successfully - `data` (array of object, required) - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `404`: One or more items not found - `error` (object, required) - `422`: Validation failed for one or more items - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-items/batch Batch delete feedback items Permanently deletes multiple feedback items in a single request. Each item must include its `id`. Merged children are also deleted. All deletions are processed atomically. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `204`: All items deleted successfully - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `404`: One or more items not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /feedback-votes/batch Batch create feedback votes Creates multiple feedback votes in a single request. Each item specifies the target feedback item and voter. All votes are processed atomically. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `201`: All votes created successfully - `data` (array of object, required) - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `422`: Validation failed for one or more items - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /feedback-votes/batch Batch delete feedback votes Removes multiple feedback votes in a single request. Each item specifies the feedback item and voter whose vote should be removed. All deletions are processed atomically. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `204`: All votes deleted successfully - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `422`: Validation failed for one or more items - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /feedback-categories/batch Batch reorder feedback categories Updates the position of multiple feedback categories in a single request. Used to reorder categories by setting their display positions. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `200`: All categories reordered successfully - `data` (array of object, required) - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `404`: One or more categories not found - `error` (object, required) - `422`: Validation failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /feedback-statuses/batch Batch reorder feedback statuses Updates the position of multiple feedback statuses in a single request. Used to reorder statuses by setting their display positions. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `200`: All statuses reordered successfully - `data` (array of object, required) - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `404`: One or more statuses not found - `error` (object, required) - `422`: Validation failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /feedback-fields/batch Batch reorder feedback fields Updates the position of multiple custom feedback fields in a single request. Used to reorder fields by setting their display positions. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `200`: All fields reordered successfully - `data` (array of object, required) - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `404`: One or more fields not found - `error` (object, required) - `422`: Validation failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ## Changelog Changelog operations ### GET /changelog List changelog entries Returns a paginated list of changelog entries. With a publishable key, only published entries are returned. With a secret key, both published and draft entries are accessible. Supports filtering by `published` status and sorting by `published_at` (default descending). **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. - `expand` (string, query, optional) — Comma-separated list of relations to expand inline. Supported fields: `author`, `tags`. - `search` (string, query, optional) — Full-text search query - `filter[published]` (boolean, query, optional) — Filter by publication status (secret key only) **Responses:** - `200`: List of changelog entries - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /changelog Create a changelog entry Creates a new changelog entry as a draft. Requires a secret API key. Use the publication endpoint to publish the entry after creation. Tags can be provided as an array of tag IDs to associate with the entry. **Request body:** - `title` (string, required) — Title of the changelog entry - `content` (string, required) — Rich HTML content of the entry - `author_id` (string, optional) — Account ID of the author - `tags` (array of string, optional) — Array of changelog tag IDs to associate - `published` (boolean, optional) — Whether to immediately publish the entry. Defaults to false (draft). - `published_at` (string (date-time), optional) — Custom publication date (only used if published is true) - `notify_subscribers` (boolean, optional) — Whether to send email notifications to changelog subscribers. Only takes effect when published is true. **Responses:** - `201`: Changelog entry created - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /changelog/{id} Get a changelog entry Retrieves a single changelog entry by its ID. With a publishable key, only published entries are accessible. With a secret key, drafts can also be retrieved. Supports expansion of `author` and `tags` relations. **Parameters:** - `id` (string, path, required) - `expand` (string, query, optional) — Comma-separated list of relations to expand inline. Supported fields: `author`, `tags`. **Responses:** - `200`: Changelog entry details - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /changelog/{id} Update a changelog entry Updates an existing changelog entry. Only provided fields are changed. Requires a secret API key. Tags can be replaced by providing a new array of tag IDs. **Parameters:** - `id` (string, path, required) **Request body:** - `title` (string, optional) — Title of the changelog entry (cannot be empty) - `content` (string, optional) — Rich HTML content of the entry - `published` (boolean, optional) — Whether the entry should be published - `published_at` (string (date-time), optional) — Custom publication date - `tags` (array of string, optional) — Replace all tags with this array of tag IDs - `author_id` (string, optional) — ID of the portal account to set as the author **Responses:** - `200`: Changelog entry updated - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `422`: Validation error (e.g. empty title) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /changelog/{id} Delete a changelog entry Permanently deletes a changelog entry and all associated comments, reactions, and cover images. Requires a secret API key. This action cannot be undone. **Parameters:** - `id` (string, path, required) **Responses:** - `204`: Changelog entry deleted - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /changelog/{id}/publication Publish a changelog entry Publishes a draft changelog entry, making it visible to portal users. Requires a secret API key. Optionally notifies subscribers and allows setting a custom publication date. **Parameters:** - `id` (string, path, required) **Request body:** - `published_at` (string (date-time), optional) — Custom publication date (defaults to now) - `notify_subscribers` (boolean, optional) — Whether to send email notifications to changelog subscribers **Responses:** - `200`: Entry published - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `422`: Business rule violation (e.g. entry already published) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /changelog/{id}/publication Unpublish a changelog entry Unpublishes a changelog entry, reverting it to draft status and hiding it from portal users. Requires a secret API key. **Parameters:** - `id` (string, path, required) **Responses:** - `204`: Entry unpublished - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `422`: Business rule violation (e.g. entry already unpublished) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /changelog/{id}/cover Upload a cover image for a changelog entry Uploads a cover image for a changelog entry. If a cover already exists, it is replaced. Requires a secret API key. Accepted formats: JPG, JPEG, PNG, GIF, WebP, SVG, BMP, AVIF. Max size: 10 MB. **Parameters:** - `id` (string, path, required) **Responses:** - `200`: Cover image uploaded - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `422`: Invalid file (wrong format or too large) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /changelog/{id}/cover Remove a cover image from a changelog entry Removes the cover image from a changelog entry. Requires a secret API key. If no cover exists, the operation completes successfully with no effect. **Parameters:** - `id` (string, path, required) **Responses:** - `204`: Cover image removed - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /changelog/{id}/images Upload an inline image for a changelog entry Uploads an inline image for use in the changelog entry's content. Requires a secret API key. Returns the public URL of the uploaded image. Accepted formats: JPG, JPEG, PNG, GIF, WebP, SVG, BMP, AVIF. Max size: 10 MB. **Parameters:** - `id` (string, path, required) **Responses:** - `201`: Image uploaded - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `422`: Invalid file (wrong format or too large) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /changelog/{id}/comments List comments on a changelog entry Returns a cursor-paginated list of comments on a specific changelog entry. With a publishable key, only non-internal, non-private comments on published entries are returned. With a secret key, all comments including internal and private ones are accessible. **Parameters:** - `id` (string, path, required) - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return **Responses:** - `200`: List of comments - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /changelog/{id}/comments Create a comment on a changelog entry Adds a comment to a changelog entry. With a publishable key, the comment is attributed to the authenticated portal account. With a secret key, use `author_id` to specify the author. Internal comments (visible only to team) require a secret key. **Parameters:** - `id` (string, path, required) **Request body:** - `text` (string, required) — HTML content of the comment - `internal` (boolean, optional) — Mark as internal comment, visible only to team (secret key only) - `private` (boolean, optional) — Mark as private comment, visible only to author and team - `author_id` (string, optional) — Account ID of the comment author (secret key only) - `reply_to_comment` (string, optional) — ID of the comment to reply to (for threaded comments) **Responses:** - `201`: Comment created - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `422`: Validation error (e.g. empty text, author not identified) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /changelog/{id}/comments/{commentId} Get a comment on a changelog entry Retrieves a single comment on a changelog entry. Internal comments are only accessible with a secret API key. **Parameters:** - `id` (string, path, required) - `commentId` (string, path, required) **Responses:** - `200`: Comment details - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry or comment not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /changelog/{id}/comments/{commentId} Update a comment on a changelog entry Updates an existing comment. Only provided fields are changed. With a **publishable key**, the authenticated portal user can only update their own comments. With a **secret key**, any comment can be updated. The `archived` field requires a secret key. **Parameters:** - `id` (string, path, required) - `commentId` (string, path, required) **Request body:** - `text` (string, optional) — HTML content of the comment (cannot be empty) - `private` (boolean, optional) — Whether the comment is private - `archived` (boolean, optional) — Whether the comment is archived **Responses:** - `200`: Comment updated - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Forbidden — not the comment author (publishable key) or authentication required - `error` (object, required) - `404`: Changelog entry or comment not found - `error` (object, required) - `422`: Validation error (e.g. empty text) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /changelog/{id}/comments/{commentId} Delete a comment on a changelog entry Permanently deletes a comment. This action cannot be undone. With a **publishable key**, the authenticated portal user can only delete their own comments. With a **secret key**, any comment can be deleted. **Parameters:** - `id` (string, path, required) - `commentId` (string, path, required) **Responses:** - `204`: Comment deleted - `401`: Authentication failed - `error` (object, required) - `403`: Forbidden — not the comment author (publishable key) or authentication required - `error` (object, required) - `404`: Changelog entry or comment not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /changelog/{id}/comments/{commentId}/likes List likes on a changelog comment Returns a cursor-paginated list of likes on a specific changelog comment. With a publishable key, only accessible for comments on published entries. **Parameters:** - `id` (string, path, required) - `commentId` (string, path, required) - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return **Responses:** - `200`: List of comment likes - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry or comment not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /changelog/{id}/comments/{commentId}/likes Like a changelog comment Adds a like to a changelog comment. With a publishable key, the like is attributed to the authenticated portal account. With a secret key, use `liker_id` to specify who is liking. Each user can only like a comment once. **Parameters:** - `id` (string, path, required) - `commentId` (string, path, required) **Request body:** - `liker_id` (string, optional) — Account ID of the liker (secret key only) **Responses:** - `201`: Like created - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry or comment not found - `error` (object, required) - `422`: Business rule violation (e.g. duplicate like, liker not identified) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /changelog/{id}/comments/{commentId}/likes Unlike a changelog comment Removes a like from a changelog comment. With a publishable key, removes the authenticated portal account's like. With a secret key, use `liker_id` to specify whose like to remove. **Parameters:** - `id` (string, path, required) - `commentId` (string, path, required) **Request body:** - `liker_id` (string, optional) — Account ID of the liker whose like to remove (secret key only) **Responses:** - `204`: Like removed - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry or comment not found - `error` (object, required) - `422`: Business rule violation (e.g. liker not identified) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /changelog/{id}/comments/{commentId}/reactions List reactions on a changelog comment Returns a cursor-paginated list of emoji reactions on a specific changelog comment. With a publishable key, only accessible for comments on published entries. **Parameters:** - `id` (string, path, required) - `commentId` (string, path, required) - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return **Responses:** - `200`: List of comment reactions - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry or comment not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /changelog/{id}/comments/{commentId}/reactions React to a changelog comment Adds an emoji reaction to a changelog comment. With a publishable key, the reaction is attributed to the authenticated portal account. With a secret key, use `reactor_id` to specify who is reacting. Only allowed emojis configured in the project settings are accepted when emoji restrictions are enabled. **Parameters:** - `id` (string, path, required) - `commentId` (string, path, required) **Request body:** - `emoji` (string, required) — The emoji to react with - `reactor_id` (string, optional) — Account ID of the reactor (secret key only) **Responses:** - `201`: Reaction created - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry or comment not found - `error` (object, required) - `422`: Business rule violation (e.g. reactions disabled, emoji not allowed, reactor not identified) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /changelog/{id}/comments/{commentId}/reactions Remove a reaction from a changelog comment Removes an emoji reaction from a changelog comment. With a publishable key, removes the authenticated portal account's reaction. With a secret key, use `reactor_id` to specify whose reaction to remove. The `emoji` field must match the reaction to remove. **Parameters:** - `id` (string, path, required) - `commentId` (string, path, required) **Request body:** - `emoji` (string, required) — The emoji of the reaction to remove - `reactor_id` (string, optional) — Account ID of the reactor whose reaction to remove (secret key only) **Responses:** - `204`: Reaction removed - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry or comment not found - `error` (object, required) - `422`: Business rule violation (e.g. reactor not identified) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /changelog/{id}/reactions List reactions on a changelog entry Returns a cursor-paginated list of emoji reactions on a specific changelog entry. With a publishable key, only accessible for published entries. **Parameters:** - `id` (string, path, required) - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return **Responses:** - `200`: List of reactions - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /changelog/{id}/reactions React to a changelog entry Adds an emoji reaction to a changelog entry. With a publishable key, the reaction is attributed to the authenticated portal account. With a secret key, use `reactor_id` to specify who is reacting. Only allowed emojis configured in the project settings are accepted when emoji restrictions are enabled. **Parameters:** - `id` (string, path, required) **Request body:** - `emoji` (string, required) — The emoji to react with - `reactor_id` (string, optional) — Account ID of the reactor (secret key only) **Responses:** - `201`: Reaction created - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `422`: Business rule violation (e.g. reactions disabled, emoji not allowed, reactor not identified) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /changelog/{id}/reactions Remove a reaction from a changelog entry Removes an emoji reaction from a changelog entry. With a publishable key, removes the authenticated portal account's reaction. With a secret key, use `reactor_id` to specify whose reaction to remove. The `emoji` field must match the reaction to remove. **Parameters:** - `id` (string, path, required) **Request body:** - `emoji` (string, required) — The emoji of the reaction to remove - `reactor_id` (string, optional) — Account ID of the reactor whose reaction to remove (secret key only) **Responses:** - `204`: Reaction removed - `401`: Authentication failed - `error` (object, required) - `404`: Changelog entry not found - `error` (object, required) - `422`: Business rule violation (e.g. reactor not identified) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /changelog-tags List changelog tags Returns a paginated list of changelog tags for the project. Supports sorting by `created_at` (default ascending) and `label`. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. **Responses:** - `200`: List of changelog tags - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /changelog-tags Create a changelog tag Creates a new changelog tag for the project. Requires a secret API key. **Request body:** - `label` (string, required) — Display label for the tag - `color` (string, optional) — Hex color code for the tag **Responses:** - `201`: Changelog tag created - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `422`: Validation error (e.g. missing label) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /changelog-tags/{id} Get a changelog tag Retrieves a single changelog tag by its ID. **Parameters:** - `id` (string, path, required) **Responses:** - `200`: Changelog tag details - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Changelog tag not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /changelog-tags/{id} Update a changelog tag Updates an existing changelog tag. Only provided fields are changed. Requires a secret API key. **Parameters:** - `id` (string, path, required) **Request body:** - `label` (string, optional) — Display label for the tag (cannot be empty) - `color` (string, optional) — Hex color code for the tag **Responses:** - `200`: Changelog tag updated - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Changelog tag not found - `error` (object, required) - `422`: Validation error (e.g. empty label) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /changelog-tags/{id} Delete a changelog tag Permanently deletes a changelog tag. The tag is removed from all associated changelog entries. Requires a secret API key. This action cannot be undone. **Parameters:** - `id` (string, path, required) **Responses:** - `204`: Changelog tag deleted - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Changelog tag not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /changelog-comments List changelog comments across all entries Returns a cursor-paginated list of changelog comments across all entries in the project. Supports filtering by `changelog_entry`, `author`, `internal`, `private`, and `created_at` range. Internal and private comments are only visible when using a secret API key. Useful for building moderation dashboards or activity feeds. **Parameters:** - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return - `filter[changelog_entry]` (string, query, optional) — Filter by changelog entry ID - `filter[author]` (string, query, optional) — Filter by author account ID - `filter[internal]` (boolean, query, optional) — Filter by internal status (secret key only) - `filter[private]` (boolean, query, optional) — Filter by private status (secret key only) - `filter[created_at][gte]` (string, query, optional) — Filter comments created at or after this date - `filter[created_at][lte]` (string, query, optional) — Filter comments created at or before this date **Responses:** - `200`: List of comments across all changelog entries - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /changelog-subscribers List changelog subscribers Returns a cursor-paginated list of accounts subscribed to the project's changelog. Requires a secret API key. Supports expansion of the `subscriber` relation to include account details. **Parameters:** - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return - `expand` (string, query, optional) — Comma-separated list of relations to expand inline. Supported fields: `subscriber`. **Responses:** - `200`: List of changelog subscribers - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /changelog-subscribers Subscribe to the changelog Subscribes an account to the project's changelog to receive email notifications when new entries are published. With a publishable key, the authenticated portal account is subscribed. With a secret key, use `subscriber_id` to subscribe any account. **Request body:** - `subscriber_id` (string, optional) — Account ID to subscribe (secret key only) **Responses:** - `201`: Subscription created - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `422`: Business rule violation (e.g. already subscribed, subscriber not identified) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /changelog-subscribers Unsubscribe from the changelog Removes a changelog subscription. With a publishable key, unsubscribes the authenticated portal account. With a secret key, use `subscriber_id` to unsubscribe any account. **Request body:** - `subscriber_id` (string, optional) — Account ID to unsubscribe (secret key only) **Responses:** - `204`: Subscription removed - `401`: Authentication failed - `error` (object, required) - `422`: Business rule violation (e.g. subscriber not identified) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /changelog/batch Batch update changelog entries Updates multiple changelog entries in a single request. Useful for bulk publish/unpublish operations during release management. Each item must include its `id` along with the fields to update. All updates are processed atomically. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `200`: All changelog entries updated successfully - `data` (array of object, required) - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `404`: One or more changelog entries not found - `error` (object, required) - `422`: Validation failed for one or more items - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ## Roadmap Roadmap operations ### GET /roadmap-statuses List roadmap statuses Returns a paginated list of roadmap statuses for the project. Supports filtering by `hidden` and `label`, and sorting by `created_at` or `label`. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. - `filter[hidden]` (boolean, query, optional) — Filter by hidden status - `filter[label]` (string, query, optional) — Filter by label (case-insensitive substring match) **Responses:** - `200`: List of roadmap statuses - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /roadmap-statuses Create a roadmap status Creates a new roadmap status for the project. Requires a secret API key. **Request body:** - `label` (string, required) — Display label for the status - `color` (string, optional) — Hex color code for the status badge - `icon` (string, optional) — Icon identifier for the status - `hidden` (boolean, optional) — Whether the status should be hidden from the portal **Responses:** - `201`: Roadmap status created - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /roadmap-statuses/{id} Get a roadmap status Retrieves a single roadmap status by its ID. **Parameters:** - `id` (string, path, required) **Responses:** - `200`: Roadmap status details - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Roadmap status not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /roadmap-statuses/{id} Update a roadmap status Updates an existing roadmap status. Only provided fields are changed. Requires a secret API key. **Parameters:** - `id` (string, path, required) **Request body:** - `label` (string, optional) — Display label for the status - `color` (string, optional) — Hex color code for the status badge - `icon` (string, optional) — Icon identifier for the status - `hidden` (boolean, optional) — Whether the status should be hidden from the portal **Responses:** - `200`: Roadmap status updated - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Roadmap status not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /roadmap-items List roadmap items Returns a paginated list of roadmap items for the project. Supports sorting by `created_at` and expansion of the `status` relation. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. - `expand` (string, query, optional) — Comma-separated list of relations to expand inline. Supported fields: `status`. - `search` (string, query, optional) — Full-text search query **Responses:** - `200`: List of roadmap items - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /roadmap-items Create a roadmap item Creates a new roadmap item. Requires a secret API key. The `status_id` field is required and must reference an existing roadmap status. **Request body:** - `title` (string, required) — Title of the roadmap item - `content` (string, optional) — Rich HTML content describing the roadmap item - `launch_date` (string, optional) — Target launch date or timeframe - `status_id` (string, required) — ID of the roadmap status to assign **Responses:** - `201`: Roadmap item created - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `422`: Validation error (e.g. invalid status_id) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /roadmap-items/{id} Get a roadmap item Retrieves a single roadmap item by its ID. Supports expansion of the `status` relation. **Parameters:** - `id` (string, path, required) - `expand` (string, query, optional) — Comma-separated list of relations to expand inline. Supported fields: `status`. **Responses:** - `200`: Roadmap item details - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Roadmap item not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /roadmap-items/{id} Update a roadmap item Updates an existing roadmap item. Only provided fields are changed. Requires a secret API key. **Parameters:** - `id` (string, path, required) **Request body:** - `title` (string, optional) — Title of the roadmap item - `content` (string, optional) — Rich HTML content describing the roadmap item - `launch_date` (string, optional) — Target launch date or timeframe, or null to clear - `status_id` (string, optional) — ID of the roadmap status to assign **Responses:** - `200`: Roadmap item updated - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Roadmap item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /roadmap-items/{id} Delete a roadmap item Permanently deletes a roadmap item and all associated votes. Requires a secret API key. This action cannot be undone. **Parameters:** - `id` (string, path, required) **Responses:** - `204`: Roadmap item deleted - `401`: Authentication failed - `error` (object, required) - `403`: Secret key required - `error` (object, required) - `404`: Roadmap item not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /roadmap-items/{id}/votes List votes on a roadmap item Returns a cursor-paginated list of votes on a specific roadmap item. Supports expansion of the `voter` relation. When using a secret key with voter expansion, the voter's email is also included. **Parameters:** - `id` (string, path, required) - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return - `expand` (string, query, optional) — Comma-separated list of relations to expand inline. Supported fields: `voter`. **Responses:** - `200`: List of votes - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Roadmap item not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /roadmap-items/{id}/votes Vote on a roadmap item Casts a vote on a roadmap item. With a publishable key, the vote is attributed to the authenticated portal account. With a secret key, use `voter_id` to specify the voter. Downvotes are only allowed if the project has downvotes enabled. **Parameters:** - `id` (string, path, required) **Request body:** - `type` (enum: up | down, optional) — Type of vote to cast (defaults to `up`) - `voter_id` (string, optional) — Account ID of the voter (secret key only) **Responses:** - `201`: Vote created - `data` (object, required) - `401`: Authentication failed - `error` (object, required) - `404`: Roadmap item not found - `error` (object, required) - `422`: Business rule violation (e.g. duplicate vote, downvotes disabled) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /roadmap-items/{id}/votes Remove a vote from a roadmap item Removes the current user's vote from a roadmap item. With a publishable key, removes the authenticated portal account's vote. With a secret key, use `voter_id` to specify whose vote to remove. **Parameters:** - `id` (string, path, required) **Request body:** - `voter_id` (string, optional) — Account ID of the voter whose vote to remove (secret key only) **Responses:** - `204`: Vote removed - `401`: Authentication failed - `error` (object, required) - `404`: Roadmap item not found - `error` (object, required) - `422`: Business rule violation (e.g. voter not identified) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /roadmap-items/batch Batch update roadmap items Updates multiple roadmap items in a single request. Useful for bulk status changes or priority updates during sprint planning. Each item must include its `id` along with the fields to update. All updates are processed atomically. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `200`: All roadmap items updated successfully - `data` (array of object, required) - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `404`: One or more roadmap items not found - `error` (object, required) - `422`: Validation failed for one or more items - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ## Project Project configuration and settings ### GET /project Get project details Returns the current project's configuration and settings. Use the `expand` parameter to include sub-resources in the response. Available expansions: `authentication`, `portal`, `widget`, `custom_domain`, `sending_email`. **Key type restrictions:** - Publishable key: can only expand `portal` - Secret key: can expand all sub-resources **Parameters:** - `expand` (string, query, optional) — Comma-separated list of sub-resources to include. Available: `authentication`, `portal`, `widget`, `custom_domain`, `sending_email`. Publishable keys may only expand `portal`. **Responses:** - `200`: Project details returned successfully - `data` (object, required) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /project/authentication Get authentication settings Returns the project's authentication configuration including SSO, magic link, and Google OAuth settings. Requires a secret key. **Responses:** - `200`: Authentication settings returned successfully - `data` (object, required) — Authentication settings for the project portal. Requires secret key. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /project/portal Get portal settings Returns the project's portal appearance and behavior configuration. Works with both publishable and secret keys. When using a secret key on a Hyper Power or Super Power tier, the `custom_html` field is included in the response. **Responses:** - `200`: Portal settings returned successfully - `data` (object, required) — Portal appearance and behavior settings - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /project/widget Get widget settings Returns the project's embeddable widget configuration including floating button, sticky button, and changelog widget settings. Requires a secret key. **Responses:** - `200`: Widget settings returned successfully - `data` (object, required) — Widget configuration settings. Requires secret key. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /project/custom-domain Get custom domain settings Returns the project's custom domain configuration including DNS verification records and current setup phase. Requires a secret key. Returns 404 if no custom domain has been configured. **Responses:** - `200`: Custom domain settings returned successfully - `data` (object, required) — Custom domain configuration. Requires secret key. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: No custom domain configured for this project - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /project/sending-email Get custom sending email settings Returns the project's custom sending email configuration including DNS verification records and current setup phase. Requires a secret key. Returns 404 if no custom sending email has been configured. **Responses:** - `200`: Sending email settings returned successfully - `data` (object, required) — Custom sending email configuration. Requires secret key. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: No custom sending email configured for this project - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ## Users Portal user management ### GET /users List users Returns a paginated list of portal users (project accounts). When using a publishable key, sensitive fields (external_id, email, extra_data, role, enabled) are omitted from each user object. **Filtering:** - `filter[segment]=` — Filter users by a segment. Requires a segment ID (GUID). - `filter[external_id]=` — Filter users by external ID. Requires a secret key. **Sorting:** Sortable fields: `created_at`, `name`, `email`, `feedback_items_count`, `feedback_votes_count`. Default sort: `created_at` descending. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `search` (string, query, optional) — Full-text search query - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. - `filter[segment]` (string, query, optional) — Filter users by segment ID - `filter[external_id]` (string, query, optional) — Filter users by external ID. Ignored for publishable keys. **Responses:** - `200`: Paginated list of users - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `404`: Segment not found (when filtering by segment) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /users Create or retrieve a user Creates a new portal user with the given email, or returns the existing user if one already exists with that external ID or email. Requires a secret key. Returns `201 Created` for new users and `200 OK` for existing users. The `existed` field in the response indicates which case occurred. **Request body:** - `email` (string (email), required) — Email address for the new user - `external_id` (string, optional) — External identifier from your system. Used to link this user with Custom SSO identities. - `name` (string, optional) — Display name for the new user - `extra_data` (object, optional) — Arbitrary key-value metadata to associate with the user **Responses:** - `200`: Existing user returned (email already exists) - `data` (object, required) — Response after creating a user. Includes all secret-key user fields plus an `existed` flag indicating whether the user already existed. - `201`: User created successfully - `data` (object, required) — Response after creating a user. Includes all secret-key user fields plus an `existed` flag indicating whether the user already existed. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /users/{id} Get a user Returns a single portal user by ID. When using a publishable key, sensitive fields (external_id, email, extra_data, role, enabled) are omitted. **Parameters:** - `id` (string, path, required) — User ID **Responses:** - `200`: User details returned successfully - `data` (object, required) — A portal user (project account). When using a publishable key, sensitive fields (email, extra_data, role, enabled) are omitted from the response. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `404`: User not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /users/{id} Update a user Updates an existing portal user's profile. Requires a secret key. Only the fields provided in the request body will be updated. **Parameters:** - `id` (string, path, required) — User ID **Request body:** - `name` (string, optional) — Updated display name - `external_id` (string, optional) — Updated external identifier from your system - `enabled` (boolean, optional) — Whether the user account should be enabled or disabled - `extra_data` (object, optional) — Updated metadata (replaces existing extra_data) **Responses:** - `200`: User updated successfully - `data` (object, required) — A portal user (project account). When using a publishable key, sensitive fields (email, extra_data, role, enabled) are omitted from the response. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: User not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /attributes List user attributes Returns a paginated list of custom user attribute definitions. Requires a secret key. **Sorting:** Default sort: `created_at` ascending. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `search` (string, query, optional) — Full-text search query - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. **Responses:** - `200`: Paginated list of attributes - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /attributes Create a user attribute Creates a new custom user attribute definition. Requires a secret key. The `key` must be unique within the project. The `type` determines what kind of values can be stored. For `select` and `multi_select` types, you must provide the `options` array. **Request body:** - `key` (string, required) — Unique machine-readable key for the attribute. Must contain only lowercase letters, numbers, and underscores. Reserved keys that cannot be used: `id`, `guid`, `email`, `name`, `avatar`, `role`, `enabled`, `created_at`, `updated_at`. - `label` (string, required) — Human-readable label - `type` (enum: text | number | select | multi_select | boolean | date, required) — Data type of the attribute - `description` (string, optional) — Optional description - `options` (array of string, optional) — Allowed values for select and multi_select types **Responses:** - `201`: Attribute created successfully - `data` (object, required) — A custom user attribute definition used for segmentation and user enrichment. Requires secret key for all operations. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `409`: Conflict - an attribute with this key already exists - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /attributes/{id} Get a user attribute Returns a single custom user attribute definition by ID. Requires a secret key. **Parameters:** - `id` (string, path, required) — Attribute ID **Responses:** - `200`: Attribute details returned successfully - `data` (object, required) — A custom user attribute definition used for segmentation and user enrichment. Requires secret key for all operations. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: Attribute not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /attributes/{id} Update a user attribute Updates an existing custom user attribute. Requires a secret key. Only the fields provided in the request body will be updated. Note: The `key` and `type` fields cannot be changed after creation. **Parameters:** - `id` (string, path, required) — Attribute ID **Request body:** - `label` (string, optional) — Updated human-readable label - `description` (string, optional) — Updated description - `options` (array of string, optional) — Updated allowed values for select/multi_select types - `archived` (boolean, optional) — Whether to archive or unarchive the attribute **Responses:** - `200`: Attribute updated successfully - `data` (object, required) — A custom user attribute definition used for segmentation and user enrichment. Requires secret key for all operations. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: Attribute not found - `error` (object, required) - `409`: Conflict - update would cause a conflict - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /attributes/{id} Archive a user attribute Archives a custom user attribute definition. The attribute is soft-deleted and hidden from active use, but its data is preserved. Requires a secret key. To unarchive, use the PATCH endpoint with `archived: false`. **Parameters:** - `id` (string, path, required) — Attribute ID **Responses:** - `204`: Attribute archived successfully (no content) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: Attribute not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /segments List user segments Returns a paginated list of user segments with their filter rules and the count of currently matched users. Requires a secret key. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `search` (string, query, optional) — Full-text search query **Responses:** - `200`: Paginated list of segments - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /segments Create a user segment Creates a new user segment with filter rules based on user attributes. Requires a secret key. Maximum 50 segments per project. The response includes the `matched_users_count` showing how many users currently match the defined filters. **Request body:** - `name` (string, required) — Display name for the segment - `groups` (object, optional) — Container for segment filter groups - `groups_match_type` (enum: all | any, optional) — How groups are combined **Responses:** - `201`: Segment created successfully - `data` (object, required) — A user segment defined by filter rules applied to user attributes. Requires secret key for all operations. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /segments/{id} Get a user segment Returns a single user segment by ID, including its filter rules and the count of currently matched users. Requires a secret key. **Parameters:** - `id` (string, path, required) — Segment ID **Responses:** - `200`: Segment details returned successfully - `data` (object, required) — A user segment defined by filter rules applied to user attributes. Requires secret key for all operations. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: Segment not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /segments/{id} Update a user segment Updates an existing user segment. Requires a secret key. Only the fields provided in the request body will be updated. The response includes the updated `matched_users_count` reflecting any changes to the filter rules. **Parameters:** - `id` (string, path, required) — Segment ID **Request body:** - `name` (string, optional) — Updated display name - `groups` (object, optional) — Container for segment filter groups - `groups_match_type` (enum: all | any, optional) — Updated group match type **Responses:** - `200`: Segment updated successfully - `data` (object, required) — A user segment defined by filter rules applied to user attributes. Requires secret key for all operations. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: Segment not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /segments/{id} Delete a user segment Permanently deletes a user segment. Requires a secret key. This action cannot be undone. **Parameters:** - `id` (string, path, required) — Segment ID **Responses:** - `204`: Segment deleted successfully (no content) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: Segment not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /users/batch Batch import users Bulk imports users into the project. Deduplicates by external ID when provided, otherwise by email. If a matching user already exists, their data is updated instead of creating a duplicate. Requires the **HYPER_POWER** membership tier. Subject to daily import quotas. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `200`: Batch import results - `data` (object, required) - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key and HYPER_POWER tier - `error` (object, required) - `422`: Validation failed or quota exceeded - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /users/batch Batch update users Updates multiple users in a single request. Each item must include the user's `id` along with the fields to update. All updates are processed atomically. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `200`: All users updated successfully - `data` (array of object, required) - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `404`: One or more users not found - `error` (object, required) - `422`: Validation failed for one or more items - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ## Team Members Team member listing ### GET /team-members List team members Returns a paginated list of team members who have access to the current project. Includes the organization owner and all invited members. Requires a secret key. Members are resolved from the identity provider and include their profile information. Members that cannot be resolved (e.g., deleted accounts) are silently omitted. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page **Responses:** - `200`: Paginated list of team members - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ## Portal Links Portal navigation links ### GET /portal-links List portal links Returns a paginated list of custom navigation links configured for the portal. Links are sorted by position (ascending). Works with both publishable and secret keys. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page **Responses:** - `200`: Paginated list of portal links - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /portal-links/batch Batch reorder portal links Updates the position of multiple portal links in a single request. Used to reorder links by setting their display positions. Requires a **secret API key**. Maximum batch size is 100 items. **Request body:** - `items` (array of object, required) **Responses:** - `200`: All portal links reordered successfully - `data` (array of object, required) - `401`: Invalid or missing API key - `error` (object, required) - `403`: Forbidden — requires secret API key - `error` (object, required) - `404`: One or more portal links not found - `error` (object, required) - `422`: Validation failed - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ## Webhooks Outbound webhook management ### GET /webhooks List webhooks Returns a paginated list of outbound webhook subscriptions for the project. Requires a secret key. **Sorting:** Default sort: `created_at` descending. **Parameters:** - `pagination[page]` (integer, query, optional) — Page number - `pagination[perPage]` (integer, query, optional) — Items per page - `filter[active]` (string, query, optional) — Filter by active status - `reveal_secret` (string, query, optional) — When set to `true`, the `signing_secret` field is included in each webhook object. Omitted by default for security. - `sort` (string, query, optional) — Comma-separated list of fields to sort by. Prefix with `-` for descending order. **Responses:** - `200`: Paginated list of webhooks - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### POST /webhooks Create a webhook Creates a new outbound webhook subscription. Requires a secret key. The webhook will receive HTTP POST requests to the specified URL whenever any of the subscribed events occur. Use `*` in the events array to subscribe to all events. The URL must use HTTPS. The response includes the `signing_secret` which should be stored securely — it is used to verify webhook payloads via HMAC-SHA256 signature in the `X-Upvoty-Signature` header. Subject to the `maxWebhooks` quota for the project's membership tier. **Request body:** - `url` (string (uri), required) — HTTPS endpoint URL. Must use HTTPS. - `events` (array of enum: * | feedback.created | feedback.updated | feedback.deleted | feedback.status_changed | feedback.archived | feedback.unarchived | feedback.pinned | feedback.unpinned | feedback.private | feedback.public | feedback.merged | feedback.unmerged | feedback.duplicated | feedback.replied | feedback.vote.created | feedback.vote.deleted | feedback.reaction.created | feedback.reaction.deleted | feedback.comment.created | feedback.comment.updated | feedback.comment.deleted | feedback.comment.liked | feedback.comment.unliked | feedback.comment.reaction.created | feedback.comment.reaction.deleted | roadmap.created | roadmap.updated | roadmap.deleted | roadmap.voted | roadmap.unvoted | changelog.created | changelog.updated | changelog.deleted | changelog.published | changelog.unpublished | changelog.subscribed | changelog.unsubscribed | changelog.comment.created | changelog.comment.updated | changelog.comment.deleted | changelog.comment.liked | changelog.comment.unliked | changelog.reaction.created | changelog.reaction.deleted | changelog.comment.reaction.created | changelog.comment.reaction.deleted | user.created | user.updated | attribute.created | attribute.updated | attribute.deleted | segment.created | segment.updated | segment.deleted, required) — Event names to subscribe to (at least one required). Use `*` to subscribe to all events. - `description` (string, optional) — Optional description **Responses:** - `201`: Webhook created successfully - `data` (object, required) — An outbound webhook subscription that receives HTTP notifications when events occur in the project. Requires secret key for all operations. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### GET /webhooks/{id} Get a webhook Returns a single webhook by ID, including the 20 most recent delivery records. Requires a secret key. **Parameters:** - `id` (string, path, required) — Webhook ID - `reveal_secret` (string, query, optional) — When set to `true`, the `signing_secret` field is included in the response. Omitted by default for security. **Responses:** - `200`: Webhook details with recent deliveries - `data` (object, required) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: Webhook not found - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### PATCH /webhooks/{id} Update a webhook Updates an existing webhook. Requires a secret key. Only the fields provided in the request body will be updated. Set `rotate_secret` to `true` to generate a new signing secret. The new secret will be returned in the response. Webhooks created by external integrations cannot be edited and will return 403. **Parameters:** - `id` (string, path, required) — Webhook ID **Request body:** - `url` (string (uri), optional) — Updated HTTPS endpoint URL - `events` (array of enum: * | feedback.created | feedback.updated | feedback.deleted | feedback.status_changed | feedback.archived | feedback.unarchived | feedback.pinned | feedback.unpinned | feedback.private | feedback.public | feedback.merged | feedback.unmerged | feedback.duplicated | feedback.replied | feedback.vote.created | feedback.vote.deleted | feedback.reaction.created | feedback.reaction.deleted | feedback.comment.created | feedback.comment.updated | feedback.comment.deleted | feedback.comment.liked | feedback.comment.unliked | feedback.comment.reaction.created | feedback.comment.reaction.deleted | roadmap.created | roadmap.updated | roadmap.deleted | roadmap.voted | roadmap.unvoted | changelog.created | changelog.updated | changelog.deleted | changelog.published | changelog.unpublished | changelog.subscribed | changelog.unsubscribed | changelog.comment.created | changelog.comment.updated | changelog.comment.deleted | changelog.comment.liked | changelog.comment.unliked | changelog.reaction.created | changelog.reaction.deleted | changelog.comment.reaction.created | changelog.comment.reaction.deleted | user.created | user.updated | attribute.created | attribute.updated | attribute.deleted | segment.created | segment.updated | segment.deleted, optional) — Updated event subscriptions - `active` (boolean, optional) — Enable or disable the webhook - `description` (string, optional) — Updated description - `rotate_secret` (boolean, optional) — Set to true to generate a new signing secret **Responses:** - `200`: Webhook updated successfully - `data` (object, required) — An outbound webhook subscription that receives HTTP notifications when events occur in the project. Requires secret key for all operations. - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: Webhook not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ### DELETE /webhooks/{id} Delete a webhook Permanently deletes a webhook and all its delivery history. Requires a secret key. This action cannot be undone. **Parameters:** - `id` (string, path, required) — Webhook ID **Responses:** - `204`: Webhook deleted successfully (no content) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `404`: Webhook not found - `error` (object, required) - `422`: Validation error - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ## Feed Activity feed ### GET /feed List activity feed Returns a cursor-paginated activity feed for the project, ordered by newest first. Requires a secret key. Each feed entry represents a user action such as creating feedback, voting, commenting, or reacting. The available relations on each entry depend on the activity type. **Filtering:** - `filter[type]=feedback_upvote` — Filter by a single activity type - `filter[type]=in:feedback_upvote,feedback_comment` — Filter by multiple activity types **Activity types:** `feedback_created`, `feedback_upvote`, `feedback_downvote`, `feedback_comment`, `feedback_comment_like`, `feedback_item_reaction`, `feedback_comment_reaction`, `roadmap_upvote`, `roadmap_downvote`, `changelog_comment`, `changelog_comment_like`, `changelog_item_reaction`, `changelog_comment_reaction`, `changelog_subscribed` **Parameters:** - `pagination[cursor]` (string, query, optional) — Cursor for the next page of results - `pagination[limit]` (integer, query, optional) — Maximum number of items to return - `search` (string, query, optional) — Full-text search query - `expand` (string, query, optional) — Comma-separated list of relations to expand. Available: `actor`, `feedback_item`, `roadmap_item`, `changelog_entry`, `feedback_comment`, `changelog_comment`. - `filter[type]` (string, query, optional) — Filter by activity type. Use a single type value or prefix with `in:` for multiple types. **Responses:** - `200`: Cursor-paginated activity feed - `data` (array of object, required) - `meta` (object, required) - `401`: Authentication failed - invalid or missing API key - `error` (object, required) - `403`: Forbidden - requires a secret key - `error` (object, required) - `422`: Validation error (e.g., unknown expand field) - `error` (object, required) - `429`: Rate limit or quota exceeded - `error` (object, required) --- ## Schema Definitions ### FeedbackCategory - `id` (string (uuid), required) — Unique identifier for the feedback category - `label` (string, required) — Display name for the category - `color` (string, required) — Hex color code associated with the category - `icon` (string, required) — Icon identifier for the category - `hidden` (boolean, required) — Whether the category is hidden from the public portal - `position` (integer, required) — Display order position (lower values appear first) - `created_at` (string (date-time), required) — Timestamp when the category was created - `updated_at` (string (date-time), required) — Timestamp when the category was last updated ### FeedbackStatus - `id` (string (uuid), required) — Unique identifier for the feedback status - `label` (string, required) — Display name for the status - `color` (string, required) — Hex color code associated with the status - `icon` (string, required) — Icon identifier for the status - `hidden` (boolean, required) — Whether the status is hidden from the public portal - `roadmap_hidden` (boolean, required) — Whether the status is hidden from the public roadmap - `default` (boolean, required) — Whether this is the default status assigned to new feedback items - `position` (integer, required) — Display order position (lower values appear first) - `created_at` (string (date-time), required) — Timestamp when the status was created - `updated_at` (string (date-time), required) — Timestamp when the status was last updated ### User A portal user (project account). When using a publishable key, sensitive fields (email, extra_data, role, enabled) are omitted from the response. - `id` (string (uuid), required) — Unique identifier for the user - `external_id` (string, optional) — External identifier from your system. Only returned with a secret key. - `name` (string, required) — Display name of the user - `avatar` (string, required) — URL to the user's avatar image - `email` (string, optional) — Email address of the user. Only returned with a secret key. - `extra_data` (object, optional) — Arbitrary key-value metadata associated with the user. Only returned with a secret key. - `role` (enum: owner | admin | editor | viewer, optional) — Role of the user within the project. Only returned with a secret key. - `enabled` (boolean, optional) — Whether the user account is enabled. Only returned with a secret key. - `feedback_items_count` (integer, required) — Number of feedback items created by this user - `feedback_votes_count` (integer, required) — Number of votes cast by this user - `feedback_comments_count` (integer, required) — Number of comments posted by this user - `feedback_received_votes_count` (integer, required) — Number of votes received on this user's feedback items - `is_guest` (boolean, required) — Whether the user is a guest (not authenticated) - `created_at` (string (date-time), required) — ISO 8601 timestamp of when the user was created - `updated_at` (string (date-time), required) — ISO 8601 timestamp of the last update ### FeedbackItem - `id` (string (uuid), required) — Unique identifier for the feedback item - `title` (string, required) — Title of the feedback item - `content` (string, required) — Detailed description of the feedback, may contain HTML - `priority` (integer, required) — Priority level (0 = none, 1-5 stars) - `votes_count` (integer, required) — Total number of votes on the feedback item (includes votes_offset) - `comments_count` (integer, required) — Total number of comments on the feedback item - `private` (boolean, required) — Whether this feedback item is only visible to the team (secret key only) - `pinned` (boolean, required) — Whether this feedback item is pinned to the top of lists - `archived` (boolean, required) — Whether this feedback item has been archived - `replied` (boolean, required) — Whether a reply has been sent to the feedback author - `source` (string, optional) — Source of the feedback — a page URL, app name, or any free text describing where it came from (secret key only) - `launch_date` (string, required) — Target launch date or timeframe for the requested feature - `custom_fields` (object, required) — Custom field key-value pairs. Secret keys return all fields; publishable keys return only public fields. - `pinned_at` (string (date-time), required) — Timestamp when the item was pinned - `archived_at` (string (date-time), required) — Timestamp when the item was archived - `replied_at` (string (date-time), required) — Timestamp when the item was replied to - `status_changed_at` (string (date-time), required) — Timestamp when the status was last changed - `created_at` (string (date-time), required) — Timestamp when the feedback item was created - `updated_at` (string (date-time), required) — Timestamp when the feedback item was last updated - `assignee` (string, required) — ID of the team member assigned to this item - `screenshot` (string, required) — URL of the attached screenshot. Private screenshots return null with publishable keys unless image_private is false. - `category` (object, required) — Category of the feedback item. Returns the category ID as a string by default. When expanded via `expand=category`, returns the full FeedbackCategory object. - `status` (object, required) — Status of the feedback item. Returns the status ID as a string by default, or null if no status is set. When expanded via `expand=status`, returns the full FeedbackStatus object. - `author` (object, required) — Author of the feedback item. Returns the author ID as a string by default. When expanded via `expand=author`, returns the full User object. Email and other sensitive fields are only included with secret keys. - `parent` (object, required) — Merge parent of this feedback item. Returns the parent ID as a string by default, or null if the item is not merged. When expanded via `expand=parent`, returns the full FeedbackItem object (relations as IDs). - `tags` (array of object, required) — Tags attached to the feedback item. Returns an array of tag IDs by default. When expanded via `expand=tags`, returns an array of full FeedbackTag objects. - `email` (string, optional) — Email of the feedback submitter (secret key only) - `external_user_id` (string, optional) — External user identifier from SSO or guest token (secret key only) - `image_private` (boolean, optional) — Whether the screenshot is restricted to team-only visibility (secret key only) - `votes_offset` (integer, optional) — Manual vote count offset added to the organic vote count (secret key only) - `merge_data` (object, optional) — Metadata about the merge operation (secret key only) ### FeedbackTag - `id` (string (uuid), required) — Unique identifier for the feedback tag - `label` (string, required) — Display name for the tag - `created_at` (string (date-time), required) — Timestamp when the tag was created - `updated_at` (string (date-time), required) — Timestamp when the tag was last updated ### OffsetPagination - `total` (integer, required) — Total number of items - `page` (integer, required) — Current page number - `perPage` (integer, required) — Items per page - `lastPage` (integer, required) — Last available page ### Error - `error` (object, required) ### FeedbackVote - `type` (enum: up | down, required) — Direction of the vote - `feedback_item` (object, required) — The feedback item the vote belongs to. Returns the item ID as a string by default. When expanded via `expand=feedback_item` (top-level `/feedback-votes` endpoint only), returns the full FeedbackItem object. - `voter` (object, required) — The user who cast the vote. Returns the voter ID as a string by default. When expanded via `expand=voter`, returns the full User object. Email and other sensitive fields are only included with secret keys. - `created_at` (string (date-time), required) — Timestamp when the vote was cast ### CursorPagination - `hasMore` (boolean, required) — Whether more items are available - `cursor` (string, required) — Cursor to use for the next page ### FeedbackReaction - `emoji` (string, required) — The emoji used for the reaction - `reactor` (string, required) — ID of the user who reacted - `created_at` (string (date-time), required) — Timestamp when the reaction was created ### FeedbackComment - `id` (string (uuid), required) — Unique identifier for the comment - `text` (string, required) — Comment body, may contain HTML - `image` (string, required) — URL of an image attached to the comment - `internal` (boolean, required) — Whether this comment is only visible to team members (not shown on public portal) - `private` (boolean, required) — Whether this comment is private (only visible to the author and team) - `archived` (boolean, required) — Whether this comment has been archived - `created_at` (string (date-time), required) — Timestamp when the comment was created - `updated_at` (string (date-time), required) — Timestamp when the comment was last updated - `author` (string, required) — ID of the comment author (portal account ID) - `feedback_item` (string, required) — ID of the feedback item this comment belongs to - `reply_to_comment` (string, required) — ID of the parent comment if this is a reply ### FeedbackCommentLike - `liker` (string, required) — ID of the user who liked the comment - `created_at` (string (date-time), required) — Timestamp when the like was created ### FeedbackCommentReaction - `emoji` (string, required) — The emoji used for the reaction - `reactor` (string, required) — ID of the user who reacted - `created_at` (string (date-time), required) — Timestamp when the reaction was created ### FeedbackField - `id` (string (uuid), required) — Unique identifier for the custom field - `key` (string, required) — Machine-readable key for the field, auto-generated from the label - `label` (string, required) — Display name for the custom field - `description` (string, required) — Optional description explaining the purpose of this field - `type` (enum: text | number | select | multiselect | checkbox | url | date, required) — Data type of the custom field - `visibility` (enum: public | private, required) — Who can see this field - `required` (boolean, required) — Whether this field must be filled in when creating feedback - `public_value` (boolean, required) — Whether the field value is visible to public portal users - `enabled_on_widget` (boolean, required) — Whether this field appears in the embeddable widget - `options` (array of string, required) — Available options for select and multiselect field types - `position` (integer, required) — Display order position (lower values appear first) - `archived` (boolean, required) — Whether the field is archived and no longer in active use - `created_at` (string (date-time), required) — Timestamp when the field was created - `updated_at` (string (date-time), required) — Timestamp when the field was last updated ### RoadmapStatus - `id` (string, required) — Unique identifier for the roadmap status - `label` (string, required) — Display label for the status - `color` (string, required) — Hex color code for the status badge - `icon` (string, required) — Icon identifier for the status - `hidden` (boolean, required) — Whether the status is hidden from the portal - `created_at` (string (date-time), required) — When the status was created - `updated_at` (string (date-time), required) — When the status was last updated ### RoadmapItem - `id` (string, required) — Unique identifier for the roadmap item - `title` (string, required) — Title of the roadmap item - `content` (string, required) — Rich text content describing the roadmap item - `launch_date` (string, required) — Target launch date or timeframe for the roadmap item - `votes_count` (integer, required) — Total number of votes on this roadmap item - `status_changed_at` (string (date-time), required) — When the status was last changed - `status` (object, required) — The status of this roadmap item. Returns a status ID string by default, or the full RoadmapStatus object when expanded via `expand=status`. - `created_at` (string (date-time), required) — When the roadmap item was created - `updated_at` (string (date-time), required) — When the roadmap item was last updated ### RoadmapVote - `type` (enum: up | down, required) — Type of vote cast on the roadmap item - `voter` (object, required) — The voter who cast this vote. Returns an account ID string by default, or the full User object when expanded via `expand=voter`. - `created_at` (string (date-time), required) — When the vote was cast ### ChangelogTag - `id` (string, required) — Unique identifier for the changelog tag - `label` (string, required) — Display label for the tag - `color` (string, required) — Hex color code for the tag - `created_at` (string (date-time), required) — When the tag was created - `updated_at` (string (date-time), required) — When the tag was last updated ### ChangelogEntry - `id` (string, required) — Unique identifier for the changelog entry - `title` (string, required) — Title of the changelog entry - `content` (string, required) — Rich HTML content of the changelog entry - `cover` (string, required) — URL of the cover image - `published` (boolean, required) — Whether the entry is publicly visible - `published_at` (string (date-time), required) — When the entry was published (null if draft) - `comments_count` (integer, required) — Number of comments on this entry - `author` (object, required) — The author of this entry. Returns an account ID by default, or the full User object when expanded via `expand=author`. - `tags` (array of object, required) — Tags associated with this entry. Returns an array of tag ID strings by default, or full ChangelogTag objects when expanded via `expand=tags`. - `created_at` (string (date-time), required) — When the entry was created - `updated_at` (string (date-time), required) — When the entry was last updated ### FileUpload - `url` (string (uri), required) — Public URL of the uploaded file ### ChangelogComment - `id` (string, required) — Unique identifier for the comment - `text` (string, required) — HTML content of the comment - `image` (string, required) — URL of an attached image - `internal` (boolean, required) — Whether the comment is internal (only visible to team members) - `private` (boolean, required) — Whether the comment is private (only visible to the author and team) - `archived` (boolean, required) — Whether the comment has been archived - `author` (string, required) — Account ID of the comment author - `changelog_entry` (string, required) — ID of the parent changelog entry - `reply_to_comment` (string, required) — ID of the parent comment if this is a reply - `created_at` (string (date-time), required) — When the comment was created - `updated_at` (string (date-time), required) — When the comment was last updated ### ChangelogCommentLike - `liker` (string, required) — Account ID of the user who liked the comment - `created_at` (string (date-time), required) — When the like was created ### ChangelogCommentReaction - `emoji` (string, required) — The emoji used for the reaction - `reactor` (string, required) — Account ID of the user who reacted - `created_at` (string (date-time), required) — When the reaction was created ### ChangelogReaction - `emoji` (string, required) — The emoji used for the reaction - `reactor` (string, required) — Account ID of the user who reacted - `created_at` (string (date-time), required) — When the reaction was created ### ChangelogSubscriber - `subscriber` (object, required) — The subscribed account. Returns an account ID string by default, or the full User object when expanded via `expand=subscriber`. - `created_at` (string (date-time), required) — When the subscription was created ### AuthSsoResponse - `session_token` (string, required) — A 64-character access token to use as a Bearer token in subsequent publishable key requests. - `account` (object, required) ### ProjectAuthentication Authentication settings for the project portal. Requires secret key. - `enabled` (boolean, required) — Whether authentication is enabled on the portal - `required` (boolean, required) — Whether users must authenticate before interacting - `locked` (boolean, required) — Whether the portal is fully locked behind authentication - `exclusive` (boolean, required) — Whether only authenticated users can view the portal - `methods` (object, required) ### ProjectPortal Portal appearance and behavior settings - `enabled` (boolean, required) — Whether the portal is enabled - `theme` (enum: light | dark | auto, required) — Default portal theme. `auto` follows the user's system preference. - `default_homepage` (enum: board | roadmap | changelog, required) — Default landing page for the portal - `sorting` (enum: newest | oldest | most_votes | least_votes | latest_status_change | random, required) — Default sorting for feedback items - `roadmap_sorting` (enum: newest | oldest | most_votes | least_votes | latest_status_change | random, required) — Default sorting for roadmap items - `expanded_feedback` (boolean, required) — Whether feedback items are expanded by default - `hide_roadmap` (boolean, required) — Whether the roadmap tab is hidden - `hide_changelog` (boolean, required) — Whether the changelog tab is hidden - `hide_usernames` (boolean, required) — Whether usernames are hidden on the portal - `show_feedback_tags` (boolean, required) — Whether feedback tags are visible - `show_feedback_images` (boolean, required) — Whether feedback images are visible - `show_empty_categories` (boolean, required) — Whether empty categories are shown - `show_dates` (boolean, required) — Whether dates are displayed - `date_format` (string, required) — Date format pattern used in the portal - `light_tint` (string, required) — Custom tint color for light theme (hex) - `dark_tint` (string, required) — Custom tint color for dark theme (hex) - `reactions_enabled` (boolean, required) — Whether emoji reactions are enabled - `reaction_emojis` (array of string, required) — List of available reaction emojis - `downvotes_enabled` (boolean, required) — Whether downvoting is enabled - `show_downvoters` (boolean, required) — Whether downvoter names are visible - `leaderboard` (object, required) - `callout` (object, required) — Custom callout message displayed on the portal - `feedback_customization` (object, required) — Customization options for the feedback form and UI - `comments_customization` (object, required) — Customization options for comments - `custom_html` (string, optional) — Custom HTML injected into the portal. Only returned for secret keys on Hyper Power or Super Power tiers. ### ProjectWidget Widget configuration settings. Requires secret key. - `theme` (enum: light | dark | auto, required) — Default widget theme. `auto` follows the user's system preference. - `font` (string, required) — Custom font family for the widget - `rounded` (boolean, required) — Whether the widget uses rounded corners - `background_color` (object, required) - `feedback_widget` (object, required) - `changelog_modal` (object, required) - `changelog_widget` (object, required) ### ProjectCustomDomain Custom domain configuration. Requires secret key. - `name` (string, required) — The custom domain name - `enabled` (boolean, required) — Whether the custom domain is active - `phase` (enum: draft | ssl_activation | ssl_failed | setup | setup_failed | live | failed | deleted, required) — Current phase of domain setup. - `draft`: Domain just submitted - `ssl_activation`: SSL certificate being created, DNS records need to be added - `ssl_failed`: SSL validation failed, can retry or remove - `setup`: CloudFront distribution being created - `setup_failed`: CloudFront setup failed, can retry or remove - `live`: Fully working, portal accessible via custom domain - `failed`: Unknown failure, domain must be removed and re-added - `deleted`: Deletion in progress - `target` (string, required) — The CNAME target for the custom domain - `verification_record` (object, required) — DNS verification record details ### ProjectSendingEmail Custom sending email configuration. Requires secret key. - `email` (string, required) — The custom sending email address - `domain` (string, required) — The domain used for sending - `enabled` (boolean, required) — Whether the custom sending email is active - `phase` (enum: draft | verification | failed | live | deleted, required) — Current phase of email verification. - `draft`: Email just submitted for verification - `verification`: Verification records provided, waiting for DNS validation - `failed`: DNS validation failed or verification records are invalid - `live`: Domain verified and email is ready to use - `deleted`: Deletion in progress - `last_verified_at` (string, required) — ISO 8601 timestamp of last successful verification - `verification_records` (array of object, required) — DNS records required for email verification ### Project - `id` (string (uuid), required) — Unique identifier for the project - `name` (string, required) — Display name of the project - `slug` (string, required) — URL-friendly slug used in the portal URL - `logo` (string, required) — URL to the project logo image - `reply_email` (string, required) — Email address used for reply-to in notifications - `portal_url` (string, required) — Full URL of the customer-facing portal - `language` (string, required) — ISO language code for the portal - `show_upvoty_branding` (boolean, required) — Whether Upvoty branding is displayed on the portal - `synced_roadmap` (boolean, required) — Whether roadmap items are synced with feedback statuses - `private_feedback_by_default` (boolean, required) — Whether new feedback is private by default - `private_comments_by_default` (boolean, required) — Whether new comments are private by default - `created_at` (string (date-time), required) — ISO 8601 timestamp of when the project was created - `updated_at` (string (date-time), required) — ISO 8601 timestamp of the last update - `authentication` (object, optional) — Authentication settings for the project portal. Requires secret key. - `portal` (object, optional) — Portal appearance and behavior settings - `widget` (object, optional) — Widget configuration settings. Requires secret key. - `custom_domain` (object, optional) — Custom domain configuration. Requires secret key. - `sending_email` (object, optional) — Custom sending email configuration. Requires secret key. ### UserCreateRequest Request body for creating a new user. Requires secret key. - `email` (string (email), required) — Email address for the new user - `external_id` (string, optional) — External identifier from your system. Used to link this user with Custom SSO identities. - `name` (string, optional) — Display name for the new user - `extra_data` (object, optional) — Arbitrary key-value metadata to associate with the user ### UserCreateResponse Response after creating a user. Includes all secret-key user fields plus an `existed` flag indicating whether the user already existed. - `id` (string (uuid), required) — Unique identifier for the user - `external_id` (string, optional) — External identifier from your system. Only returned with a secret key. - `name` (string, required) — Display name of the user - `avatar` (string, required) — URL to the user's avatar image - `email` (string, optional) — Email address of the user. Only returned with a secret key. - `extra_data` (object, optional) — Arbitrary key-value metadata associated with the user. Only returned with a secret key. - `role` (enum: owner | admin | editor | viewer, optional) — Role of the user within the project. Only returned with a secret key. - `enabled` (boolean, optional) — Whether the user account is enabled. Only returned with a secret key. - `feedback_items_count` (integer, required) — Number of feedback items created by this user - `feedback_votes_count` (integer, required) — Number of votes cast by this user - `feedback_comments_count` (integer, required) — Number of comments posted by this user - `feedback_received_votes_count` (integer, required) — Number of votes received on this user's feedback items - `is_guest` (boolean, required) — Whether the user is a guest (not authenticated) - `created_at` (string (date-time), required) — ISO 8601 timestamp of when the user was created - `updated_at` (string (date-time), required) — ISO 8601 timestamp of the last update - `existed` (boolean, required) — Whether the user already existed (true = returned existing user, false = newly created) ### UserUpdateRequest Request body for updating a user. Requires secret key. All fields are optional. - `name` (string, optional) — Updated display name - `external_id` (string, optional) — Updated external identifier from your system - `enabled` (boolean, optional) — Whether the user account should be enabled or disabled - `extra_data` (object, optional) — Updated metadata (replaces existing extra_data) ### TeamMember A team member with access to the project. Requires secret key. - `id` (string, required) — Unique identifier for the team member - `name` (string, required) — Display name of the team member - `email` (string, required) — Email address of the team member - `avatar` (string, required) — URL to the team member's avatar. Falls back to the project logo if available. - `role` (enum: owner | admin | editor | viewer, required) — Role of the team member within the project. The organization owner always has the `owner` role. - `created_at` (string (date-time), required) — ISO 8601 timestamp of when the team member was added (empty for the owner) ### Attribute A custom user attribute definition used for segmentation and user enrichment. Requires secret key for all operations. - `id` (string (uuid), required) — Unique identifier for the attribute - `key` (string, required) — Machine-readable key used to reference the attribute in user data and segments - `label` (string, required) — Human-readable label for display in the dashboard - `description` (string, required) — Optional description of the attribute's purpose - `type` (enum: text | number | select | multi_select | boolean | date, required) — Data type of the attribute - `options` (array of string, required) — List of allowed values for select and multi_select types. Null for other types. - `archived` (boolean, required) — Whether the attribute is archived and hidden from active use - `created_at` (string (date-time), required) — ISO 8601 timestamp of when the attribute was created - `updated_at` (string (date-time), required) — ISO 8601 timestamp of the last update ### AttributeCreateRequest Request body for creating a new attribute. Requires secret key. - `key` (string, required) — Unique machine-readable key for the attribute. Must contain only lowercase letters, numbers, and underscores. Reserved keys that cannot be used: `id`, `guid`, `email`, `name`, `avatar`, `role`, `enabled`, `created_at`, `updated_at`. - `label` (string, required) — Human-readable label - `type` (enum: text | number | select | multi_select | boolean | date, required) — Data type of the attribute - `description` (string, optional) — Optional description - `options` (array of string, optional) — Allowed values for select and multi_select types ### AttributeUpdateRequest Request body for updating an attribute. Requires secret key. All fields are optional. Key and type cannot be changed after creation. - `label` (string, optional) — Updated human-readable label - `description` (string, optional) — Updated description - `options` (array of string, optional) — Updated allowed values for select/multi_select types - `archived` (boolean, optional) — Whether to archive or unarchive the attribute ### SegmentRule A single filter rule that evaluates a user attribute - `field` (string, required) — The attribute key to filter on - `operator` (enum: equals | not_equals | contains | not_contains | greater_than | less_than | greater_than_or_equal | less_than_or_equal | in | in_all | not_in | is_set | is_not_set | before | after, required) — Comparison operator for evaluating the attribute value - `value` (object, optional) — The value to compare against. Type depends on the attribute and operator. ### SegmentGroup A group of filter rules that are combined using the group's match_type - `match_type` (enum: all | any, required) — How rules within this group are combined - `rules` (array of object, required) — List of filter rules in this group (maximum 20) ### SegmentGroups Container for segment filter groups - `items` (array of object, required) — List of filter groups (maximum 10) ### Segment A user segment defined by filter rules applied to user attributes. Requires secret key for all operations. - `id` (string (uuid), required) — Unique identifier for the segment - `name` (string, required) — Display name of the segment - `groups` (object, required) — Container for segment filter groups - `groups_match_type` (enum: all | any, required) — How groups are combined. "all" means every group must match; "any" means at least one group must match. - `matched_users_count` (integer, required) — Number of users currently matching this segment's filters - `created_at` (string (date-time), required) — ISO 8601 timestamp of when the segment was created - `updated_at` (string (date-time), required) — ISO 8601 timestamp of the last update ### SegmentCreateRequest Request body for creating a new segment. Requires secret key. - `name` (string, required) — Display name for the segment - `groups` (object, optional) — Container for segment filter groups - `groups_match_type` (enum: all | any, optional) — How groups are combined ### SegmentUpdateRequest Request body for updating a segment. Requires secret key. All fields are optional. - `name` (string, optional) — Updated display name - `groups` (object, optional) — Container for segment filter groups - `groups_match_type` (enum: all | any, optional) — Updated group match type ### PortalLink A custom navigation link displayed in the portal sidebar - `id` (string (uuid), required) — Unique identifier for the portal link - `label` (string, required) — Display text for the link - `url` (string, required) — Target URL the link points to - `icon` (string, required) — Icon identifier for the link - `position` (integer, required) — Sort order position (lower numbers appear first) - `created_at` (string (date-time), required) — ISO 8601 timestamp of when the link was created ### FeedEntryActorExpanded Expanded actor (user) details - `id` (string (uuid), required) - `email` (string, required) - `name` (string, required) - `avatar` (string, required) ### FeedEntryFeedbackItemExpanded Expanded feedback item details - `id` (string (uuid), required) - `title` (string, required) - `votes_count` (integer, required) ### FeedEntryRoadmapItemExpanded Expanded roadmap item details - `id` (string (uuid), required) - `title` (string, required) ### FeedEntryChangelogEntryExpanded Expanded changelog entry details - `id` (string (uuid), required) - `title` (string, required) ### FeedEntryFeedbackCommentExpanded Expanded feedback comment details - `id` (string (uuid), required) - `text` (string, required) ### FeedEntryChangelogCommentExpanded Expanded changelog comment details - `id` (string (uuid), required) - `text` (string, required) ### FeedEntry An activity feed entry representing a user action in the project. Requires secret key. Relations can be expanded or collapsed to just their ID. Which relations are present depends on the activity type. - `id` (string (uuid), required) — Unique identifier for the feed entry - `type` (enum: feedback_created | feedback_upvote | feedback_downvote | feedback_comment | feedback_comment_like | feedback_item_reaction | feedback_comment_reaction | roadmap_upvote | roadmap_downvote | changelog_comment | changelog_comment_like | changelog_item_reaction | changelog_comment_reaction | changelog_subscribed, required) — The type of activity - `metadata` (object, required) — Additional metadata associated with the activity (e.g., reaction emoji) - `actor` (object, optional) — The user who performed the action. When expanded, returns an object with user details. When collapsed, returns the user's ID string. Omitted if not applicable to this activity type. - `feedback_item` (object, optional) — The related feedback item. When expanded, returns an object. When collapsed, returns the item's ID. Omitted if not applicable to this activity type. - `roadmap_item` (object, optional) — The related roadmap item. When expanded, returns an object. When collapsed, returns the item's ID. Omitted if not applicable to this activity type. - `changelog_entry` (object, optional) — The related changelog entry. When expanded, returns an object. When collapsed, returns the entry's ID. Omitted if not applicable to this activity type. - `feedback_comment` (object, optional) — The related feedback comment. When expanded, returns an object. When collapsed, returns the comment's ID. Omitted if not applicable to this activity type. - `changelog_comment` (object, optional) — The related changelog comment. When expanded, returns an object. When collapsed, returns the comment's ID. Omitted if not applicable to this activity type. - `created_at` (string (date-time), required) — ISO 8601 timestamp of when the activity occurred ### WebhookEvent Available webhook event names. Use `*` to subscribe to all events. - Type: enum: * | feedback.created | feedback.updated | feedback.deleted | feedback.status_changed | feedback.archived | feedback.unarchived | feedback.pinned | feedback.unpinned | feedback.private | feedback.public | feedback.merged | feedback.unmerged | feedback.duplicated | feedback.replied | feedback.vote.created | feedback.vote.deleted | feedback.reaction.created | feedback.reaction.deleted | feedback.comment.created | feedback.comment.updated | feedback.comment.deleted | feedback.comment.liked | feedback.comment.unliked | feedback.comment.reaction.created | feedback.comment.reaction.deleted | roadmap.created | roadmap.updated | roadmap.deleted | roadmap.voted | roadmap.unvoted | changelog.created | changelog.updated | changelog.deleted | changelog.published | changelog.unpublished | changelog.subscribed | changelog.unsubscribed | changelog.comment.created | changelog.comment.updated | changelog.comment.deleted | changelog.comment.liked | changelog.comment.unliked | changelog.reaction.created | changelog.reaction.deleted | changelog.comment.reaction.created | changelog.comment.reaction.deleted | user.created | user.updated | attribute.created | attribute.updated | attribute.deleted | segment.created | segment.updated | segment.deleted ### Webhook An outbound webhook subscription that receives HTTP notifications when events occur in the project. Requires secret key for all operations. - `id` (string (uuid), required) — Unique identifier for the webhook - `url` (string (uri), required) — HTTPS endpoint that receives webhook deliveries - `events` (array of enum: * | feedback.created | feedback.updated | feedback.deleted | feedback.status_changed | feedback.archived | feedback.unarchived | feedback.pinned | feedback.unpinned | feedback.private | feedback.public | feedback.merged | feedback.unmerged | feedback.duplicated | feedback.replied | feedback.vote.created | feedback.vote.deleted | feedback.reaction.created | feedback.reaction.deleted | feedback.comment.created | feedback.comment.updated | feedback.comment.deleted | feedback.comment.liked | feedback.comment.unliked | feedback.comment.reaction.created | feedback.comment.reaction.deleted | roadmap.created | roadmap.updated | roadmap.deleted | roadmap.voted | roadmap.unvoted | changelog.created | changelog.updated | changelog.deleted | changelog.published | changelog.unpublished | changelog.subscribed | changelog.unsubscribed | changelog.comment.created | changelog.comment.updated | changelog.comment.deleted | changelog.comment.liked | changelog.comment.unliked | changelog.reaction.created | changelog.reaction.deleted | changelog.comment.reaction.created | changelog.comment.reaction.deleted | user.created | user.updated | attribute.created | attribute.updated | attribute.deleted | segment.created | segment.updated | segment.deleted, required) — List of event names this webhook subscribes to. Use `*` to subscribe to all events. - `active` (boolean, required) — Whether the webhook is currently active and receiving deliveries - `description` (string, required) — Optional human-readable description of the webhook's purpose - `signing_secret` (string, optional) — HMAC-SHA256 signing secret used to verify webhook payloads. Always included on create and `rotate_secret` responses. On list and show endpoints, only included when `?reveal_secret=true` is passed. Store this securely. - `created_at` (string (date-time), required) — ISO 8601 timestamp of when the webhook was created - `updated_at` (string (date-time), required) — ISO 8601 timestamp of the last update ### WebhookCreateRequest Request body for creating a new webhook. Requires secret key. - `url` (string (uri), required) — HTTPS endpoint URL. Must use HTTPS. - `events` (array of enum: * | feedback.created | feedback.updated | feedback.deleted | feedback.status_changed | feedback.archived | feedback.unarchived | feedback.pinned | feedback.unpinned | feedback.private | feedback.public | feedback.merged | feedback.unmerged | feedback.duplicated | feedback.replied | feedback.vote.created | feedback.vote.deleted | feedback.reaction.created | feedback.reaction.deleted | feedback.comment.created | feedback.comment.updated | feedback.comment.deleted | feedback.comment.liked | feedback.comment.unliked | feedback.comment.reaction.created | feedback.comment.reaction.deleted | roadmap.created | roadmap.updated | roadmap.deleted | roadmap.voted | roadmap.unvoted | changelog.created | changelog.updated | changelog.deleted | changelog.published | changelog.unpublished | changelog.subscribed | changelog.unsubscribed | changelog.comment.created | changelog.comment.updated | changelog.comment.deleted | changelog.comment.liked | changelog.comment.unliked | changelog.reaction.created | changelog.reaction.deleted | changelog.comment.reaction.created | changelog.comment.reaction.deleted | user.created | user.updated | attribute.created | attribute.updated | attribute.deleted | segment.created | segment.updated | segment.deleted, required) — Event names to subscribe to (at least one required). Use `*` to subscribe to all events. - `description` (string, optional) — Optional description ### WebhookDelivery A record of a webhook delivery attempt. Included in the webhook show response as `recent_deliveries`. - `id` (integer, required) — Unique identifier for the delivery record - `event` (string, required) — The event name that triggered this delivery - `status` (enum: pending | success | failed | retrying, required) — Current delivery status. - `pending`: Not yet attempted - `success`: Delivered successfully (2xx response) - `failed`: Permanently failed (4xx response or max retries exceeded) - `retrying`: Failed but scheduled for retry - `status_code` (integer, required) — HTTP status code from the last delivery attempt - `attempts_count` (integer, required) — Number of delivery attempts made - `created_at` (string (date-time), required) — ISO 8601 timestamp of when the delivery was created - `attempted_at` (string (date-time), required) — ISO 8601 timestamp of the last delivery attempt ### WebhookUpdateRequest Request body for updating a webhook. All fields are optional. Set `rotate_secret` to `true` to generate a new signing secret. Requires secret key. - `url` (string (uri), optional) — Updated HTTPS endpoint URL - `events` (array of enum: * | feedback.created | feedback.updated | feedback.deleted | feedback.status_changed | feedback.archived | feedback.unarchived | feedback.pinned | feedback.unpinned | feedback.private | feedback.public | feedback.merged | feedback.unmerged | feedback.duplicated | feedback.replied | feedback.vote.created | feedback.vote.deleted | feedback.reaction.created | feedback.reaction.deleted | feedback.comment.created | feedback.comment.updated | feedback.comment.deleted | feedback.comment.liked | feedback.comment.unliked | feedback.comment.reaction.created | feedback.comment.reaction.deleted | roadmap.created | roadmap.updated | roadmap.deleted | roadmap.voted | roadmap.unvoted | changelog.created | changelog.updated | changelog.deleted | changelog.published | changelog.unpublished | changelog.subscribed | changelog.unsubscribed | changelog.comment.created | changelog.comment.updated | changelog.comment.deleted | changelog.comment.liked | changelog.comment.unliked | changelog.reaction.created | changelog.reaction.deleted | changelog.comment.reaction.created | changelog.comment.reaction.deleted | user.created | user.updated | attribute.created | attribute.updated | attribute.deleted | segment.created | segment.updated | segment.deleted, optional) — Updated event subscriptions - `active` (boolean, optional) — Enable or disable the webhook - `description` (string, optional) — Updated description - `rotate_secret` (boolean, optional) — Set to true to generate a new signing secret ### FeedbackItemBatch Feedback item as returned by batch endpoints. All fields are always present (batch requires a secret key). Relational fields (category, status, author, parent, tags) are returned as IDs — expansion is not supported in batch operations. - `id` (string (uuid), required) — Unique identifier for the feedback item - `title` (string, required) — Title of the feedback item - `content` (string, required) — Detailed description of the feedback, may contain HTML - `priority` (integer, required) — Priority level (0 = none, higher values = higher priority) - `votes_count` (integer, required) — Total number of votes on the feedback item (includes votes_offset) - `comments_count` (integer, required) — Total number of comments on the feedback item - `private` (boolean, required) — Whether this feedback item is only visible to the team - `pinned` (boolean, required) — Whether this feedback item is pinned to the top of lists - `archived` (boolean, required) — Whether this feedback item has been archived - `replied` (boolean, required) — Whether a reply has been sent to the feedback author - `source` (string, required) — Source of the feedback — a page URL, app name, or any free text describing where it came from - `launch_date` (string, required) — Target launch date for the requested feature - `custom_fields` (object, required) — Custom field key-value pairs. Secret keys return all fields; publishable keys return only public fields. - `screenshot` (string, required) — URL of the attached screenshot - `image_private` (boolean, required) — Whether the screenshot is restricted to team-only visibility - `email` (string, required) — Email of the feedback submitter - `external_user_id` (string, required) — External user identifier from SSO or guest token - `votes_offset` (integer, required) — Manual vote count offset added to the organic vote count - `merge_data` (object, required) — Metadata about the merge operation - `parent` (string, required) — ID of the merge parent feedback item, or null if not merged - `pinned_at` (string (date-time), required) — Timestamp when the item was pinned - `archived_at` (string (date-time), required) — Timestamp when the item was archived - `replied_at` (string (date-time), required) — Timestamp when the item was replied to - `status_changed_at` (string (date-time), required) — Timestamp when the status was last changed - `created_at` (string (date-time), required) — Timestamp when the feedback item was created - `updated_at` (string (date-time), required) — Timestamp when the feedback item was last updated - `assignee` (string, required) — ID of the team member assigned to this item - `category` (string, required) — ID of the feedback category - `status` (string, required) — ID of the feedback status - `author` (string, required) — ID of the author (portal account) - `tags` (array of string, required) — Array of tag IDs attached to the feedback item ### BatchError - `error` (object, required) ### RoadmapItemBatch Roadmap item as returned by batch endpoints. The status field is returned as an ID — expansion is not supported in batch operations. - `id` (string, required) — Unique identifier for the roadmap item - `title` (string, required) — Title of the roadmap item - `content` (string, required) — Rich text content describing the roadmap item - `launch_date` (string, required) — Target launch date or timeframe for the roadmap item - `votes_count` (integer, required) — Total number of votes on this roadmap item - `status` (string, required) — ID of the roadmap status - `status_changed_at` (string (date-time), required) — When the status was last changed - `created_at` (string (date-time), required) — When the roadmap item was created - `updated_at` (string (date-time), required) — When the roadmap item was last updated ### ChangelogEntryBatch Changelog entry as returned by batch endpoints. Relational fields (author, tags) are returned as IDs — expansion is not supported in batch operations. - `id` (string, required) — Unique identifier for the changelog entry - `title` (string, required) — Title of the changelog entry - `content` (string, required) — Rich HTML content of the changelog entry - `cover` (string, required) — URL of the cover image - `published` (boolean, required) — Whether the entry is publicly visible - `published_at` (string (date-time), required) — When the entry was published (null if draft) - `comments_count` (integer, required) — Number of comments on this entry - `author` (string, required) — ID of the author (portal account) - `tags` (array of string, required) — Array of tag IDs associated with this entry - `created_at` (string (date-time), required) — When the entry was created - `updated_at` (string (date-time), required) — When the entry was last updated ### Warning - `type` (enum: field | param, required) — Type of deprecated element - `name` (string, required) — Name of the deprecated element - `sunset` (string, required) — Date when the element will be removed - `message` (string, required) — Details about the deprecation