Feedback API
Reference for the Loop Feedback API: create items with POST /v2/feedback and list or filter with GET /v2/feedback, with request and response examples.
Overview
The Feedback API is the core of Loop. It exposes two endpoints: one to create a feedback item and one to list and filter items. All requests use the base URL https://api.loop.dev/v2 and a bearer API key. Responses are JSON.
POST https://api.loop.dev/v2/feedback # create a feedback item
GET https://api.loop.dev/v2/feedback # list and filter items
Create feedback
POST /v2/feedback stores a single feedback item and returns it with a server-assigned id, status, and createdAt.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
user.id |
string | Yes | Your stable identifier for the end user |
message |
string | Yes | The feedback text |
sentiment |
enum | No | positive, neutral, or negative |
source |
string | No | Origin of the feedback, e.g. in-app-widget |
tag |
string | No | Optional label used for triage and routing |
Request
curl https://api.loop.dev/v2/feedback \
-H "Authorization: Bearer $LOOP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"user": { "id": "u_8f2c" },
"message": "The CSV export timed out on large datasets.",
"sentiment": "negative",
"source": "in-app-widget",
"tag": "exports"
}'
The same call through each SDK:
const item = await loop.feedback.create({
user: { id: 'u_8f2c' },
message: 'The CSV export timed out on large datasets.',
sentiment: 'negative',
source: 'in-app-widget',
tag: 'exports',
});
item = loop.feedback.create(
user={'id': 'u_8f2c'},
message='The CSV export timed out on large datasets.',
sentiment='negative',
tag='exports',
)
item, err := client.Feedback.Create(ctx, &loop.Feedback{
User: loop.User{ID: "u_8f2c"},
Message: "The CSV export timed out on large datasets.",
Sentiment: loop.Negative,
Tag: "exports",
})
Response
{
"id": "fb_3a91",
"user": { "id": "u_8f2c" },
"message": "The CSV export timed out on large datasets.",
"sentiment": "negative",
"source": "in-app-widget",
"tag": "exports",
"status": "open",
"createdAt": "2026-06-19T14:02:11Z"
}
New items always start with status: "open". A feedback.created webhook fires on success — see Webhooks.
List feedback
GET /v2/feedback returns a paginated list of items, most recent first. Use query parameters to filter.
Query parameters
| Parameter | Type | Description |
|---|---|---|
status |
enum | Filter by open or resolved |
sentiment |
enum | Filter by positive, neutral, or negative |
tag |
string | Filter by a specific tag |
limit |
integer | Items per page (default 20, max 100) |
cursor |
string | Pagination cursor from a previous response |
Request
Fetch open, negative feedback:
curl "https://api.loop.dev/v2/feedback?status=open&sentiment=negative&limit=2" \
-H "Authorization: Bearer $LOOP_API_KEY"
const { data, nextCursor } = await loop.feedback.list({
status: 'open',
sentiment: 'negative',
limit: 2,
});
result = loop.feedback.list(status='open', sentiment='negative', limit=2)
result, err := client.Feedback.List(ctx, &loop.ListParams{
Status: loop.Open,
Sentiment: loop.Negative,
Limit: 2,
})
Response
{
"data": [
{
"id": "fb_3a91",
"user": { "id": "u_8f2c" },
"message": "The CSV export timed out on large datasets.",
"sentiment": "negative",
"source": "in-app-widget",
"tag": "exports",
"status": "open",
"createdAt": "2026-06-19T14:02:11Z"
}
],
"nextCursor": "c_9d12",
"hasMore": true
}
Pagination
When hasMore is true, pass nextCursor as the cursor parameter on the next request to fetch the following page. Continue until hasMore is false.
Errors
The API uses standard HTTP status codes:
| Status | Meaning |
|---|---|
400 |
Validation error — a required field is missing/invalid |
401 |
Invalid or missing API key |
429 |
Rate limited or monthly item quota exceeded |
500 |
Unexpected server error — safe to retry |
Error bodies follow a consistent shape with error.type and error.message. Continue to Webhooks to act on new feedback in real time.