API Reference
Complete REST API reference for Feedhog with request/response examples for all endpoints.
API Reference
Complete reference for all Feedhog REST API endpoints.
Authentication
All endpoints require the x-api-key header:
x-api-key: fhpk_your_public_keyPOST /api/v1/identify
Identify or create an end user. If a user with the same externalId exists, their data is updated.
Request
curl -X POST https://feedhog.com/api/v1/identify \
-H "Content-Type: application/json" \
-H "x-api-key: fhpk_your_public_key" \
-d '{
"externalId": "user-123",
"email": "user@example.com",
"name": "John Doe",
"avatarUrl": "https://example.com/avatar.jpg",
"metadata": {
"plan": "pro",
"company": "Acme Inc"
}
}'Request Body
| Field | Type | Required | Description |
|---|---|---|---|
externalId | string | Yes | Your system's unique user ID |
email | string | No | User's email address |
name | string | No | User's display name |
avatarUrl | string | No | URL to user's avatar |
metadata | object | No | Additional key-value data |
Response (200 OK)
{
"user": {
"id": "user-123",
"email": "user@example.com",
"name": "John Doe",
"avatarUrl": "https://example.com/avatar.jpg",
"createdAt": "2024-01-15T10:30:00.000Z"
}
}POST /api/v1/feedback
Submit new feedback.
Request
curl -X POST https://feedhog.com/api/v1/feedback \
-H "Content-Type: application/json" \
-H "x-api-key: fhpk_your_public_key" \
-d '{
"title": "Add dark mode",
"description": "Would love a dark theme option for better night-time usage",
"type": "idea",
"metadata": {
"page": "/settings",
"browser": "Chrome"
},
"endUser": {
"externalId": "user-123",
"email": "user@example.com",
"name": "John Doe"
}
}'Request Body
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Feedback title (max 200 chars) |
description | string | No | Detailed description |
type | string | No | bug, idea, question, or other (default: idea) |
metadata | object | No | Additional key-value data |
endUser | object | No | User submitting feedback |
endUser.externalId | string | Yes* | User's ID (*required if endUser provided) |
endUser.email | string | No | User's email |
endUser.name | string | No | User's name |
endUser.avatarUrl | string | No | User's avatar URL |
endUser.metadata | object | No | User metadata |
Response (201 Created)
{
"feedback": {
"id": "fb_abc123xyz",
"title": "Add dark mode",
"description": "Would love a dark theme option for better night-time usage",
"type": "idea",
"status": "new",
"voteCount": 1,
"commentCount": 0,
"createdAt": "2024-01-15T10:30:00.000Z",
"endUser": {
"name": "John Doe",
"avatarUrl": null
}
}
}GET /api/v1/feedback
List feedback with optional filters and pagination.
Request
curl -X GET "https://feedhog.com/api/v1/feedback?status=planned,in-progress&type=idea&sortBy=votes&page=1&limit=10" \
-H "x-api-key: fhpk_your_public_key"Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
status | string | - | Filter by status (comma-separated for multiple) |
type | string | - | Filter by type (comma-separated for multiple) |
search | string | - | Search in title and description |
sortBy | string | newest | Sort order: newest, oldest, votes, comments |
page | number | 1 | Page number (1-indexed) |
limit | number | 20 | Items per page (max 100) |
Status Values
new- Newly submittedunder-review- Being reviewedplanned- Planned for implementationin-progress- Currently being worked oncompleted- Implementedclosed- Closed without implementing
Type Values
bug- Bug reportidea- Feature request / ideaquestion- Questionother- Other feedback
Response (200 OK)
{
"items": [
{
"id": "fb_abc123xyz",
"title": "Add dark mode",
"description": "Would love a dark theme option",
"type": "idea",
"status": "planned",
"voteCount": 42,
"commentCount": 5,
"createdAt": "2024-01-15T10:30:00.000Z",
"endUser": {
"name": "John Doe",
"avatarUrl": null
}
}
],
"total": 42,
"page": 1,
"limit": 10,
"totalPages": 5
}GET /api/v1/feedback/:id
Get a single feedback item with full details including comments.
Request
curl -X GET "https://feedhog.com/api/v1/feedback/fb_abc123xyz?endUserId=user-123" \
-H "x-api-key: fhpk_your_public_key"Query Parameters
| Parameter | Type | Description |
|---|---|---|
endUserId | string | External user ID to check vote status |
Response (200 OK)
{
"feedback": {
"id": "fb_abc123xyz",
"title": "Add dark mode",
"description": "Would love a dark theme option",
"type": "idea",
"status": "planned",
"voteCount": 42,
"commentCount": 2,
"createdAt": "2024-01-15T10:30:00.000Z",
"userHasVoted": true,
"endUser": {
"name": "John Doe",
"avatarUrl": null
},
"comments": [
{
"id": "cmt_xyz789",
"content": "Great idea! We're planning to add this in Q2.",
"createdAt": "2024-01-16T09:00:00.000Z",
"author": {
"name": "Product Team",
"isTeam": true
}
},
{
"id": "cmt_abc456",
"content": "Can't wait for this feature!",
"createdAt": "2024-01-16T10:30:00.000Z",
"author": {
"name": "Jane Smith",
"avatarUrl": "https://example.com/jane.jpg"
}
}
]
}
}POST /api/v1/feedback/:id/vote
Toggle vote on a feedback item. If the user has voted, removes their vote. If not, adds a vote.
Request (Identified User)
curl -X POST https://feedhog.com/api/v1/feedback/fb_abc123xyz/vote \
-H "Content-Type: application/json" \
-H "x-api-key: fhpk_your_public_key" \
-d '{
"endUser": {
"externalId": "user-123",
"email": "user@example.com"
}
}'Request (Anonymous)
curl -X POST https://feedhog.com/api/v1/feedback/fb_abc123xyz/vote \
-H "Content-Type: application/json" \
-H "x-api-key: fhpk_your_public_key" \
-d '{}'Request Body
| Field | Type | Required | Description |
|---|---|---|---|
endUser | object | No | User voting (omit for anonymous) |
endUser.externalId | string | Yes* | User's ID (*required if endUser provided) |
endUser.email | string | No | User's email |
endUser.name | string | No | User's name |
Response (200 OK)
{
"voted": true,
"voteCount": 43
}The voted field indicates the new state:
true- Vote was addedfalse- Vote was removed
GET /api/v1/feedback/:id/vote
Check if a user has voted on a feedback item.
Request
curl -X GET "https://feedhog.com/api/v1/feedback/fb_abc123xyz/vote?endUserId=user-123" \
-H "x-api-key: fhpk_your_public_key"Query Parameters
| Parameter | Type | Description |
|---|---|---|
endUserId | string | External user ID to check |
If endUserId is not provided, checks by IP address.
Response (200 OK)
{
"voted": true,
"voteCount": 43
}Error Responses
400 Bad Request
Validation error:
{
"error": "Invalid input",
"details": {
"formErrors": [],
"fieldErrors": {
"title": ["Title is required"],
"type": ["Invalid type. Must be one of: bug, idea, question, other"]
}
}
}401 Unauthorized
Missing or invalid API key:
{
"error": "Missing x-api-key header"
}{
"error": "Invalid API key"
}404 Not Found
Resource not found:
{
"error": "Feedback not found"
}429 Too Many Requests
Rate limited:
{
"error": "Too many requests, please slow down"
}500 Internal Server Error
Server error:
{
"error": "Internal server error"
}Type Definitions
For reference, here are the TypeScript types used in responses:
type FeedbackType = "bug" | "idea" | "question" | "other";
type FeedbackStatus =
| "new"
| "under-review"
| "planned"
| "in-progress"
| "completed"
| "closed";
interface FeedbackListItem {
id: string;
title: string;
description: string | null;
type: FeedbackType;
status: FeedbackStatus;
voteCount: number;
commentCount: number;
createdAt: string;
endUser: {
name: string | null;
avatarUrl: string | null;
} | null;
}
interface FeedbackDetail extends FeedbackListItem {
userHasVoted: boolean;
comments: {
id: string;
content: string;
createdAt: string;
author: {
name: string | null;
avatarUrl?: string | null;
isTeam?: boolean;
} | null;
}[];
}
interface IdentifiedUser {
id: string;
email: string | null;
name: string | null;
avatarUrl: string | null;
createdAt: string;
}
interface VoteResult {
voted: boolean;
voteCount: number;
}
interface PaginatedResponse<T> {
items: T[];
total: number;
page: number;
limit: number;
totalPages: number;
}