Feedback Service Guide
Collect product feedback — bug reports, feature requests, support questions — from your users, scoped per tenant and per environment. Feedback threads support status tracking, threaded replies, and file attachments.
How it works
Each tenant is a slug (myapp). Every tenant has two isolated environments: production and sandbox. Feedback, replies, and attachments are partitioned by tenant + environment, so sandbox data never mixes with production.
Tenants are resolved one of two ways:
- By subdomain —
myapp.feedback.aldero.io(production) ormyapp-sandbox.feedback.aldero.io(sandbox). - By headers — send
X-Tenant-Slug: myappandX-Tenant-Env: production(orsandbox) to any host.
Auth and identity
All /v1/* routes require a bearer token. The token can be one of:
- A per-tenant API key (
fbk_prefix) — for your backend. Full read/write across the tenant + environment. - A user JWT — issued by the Aldero auth service. Identifies an end user. On
GET /v1/feedbacks, JWT callers are automatically scoped to their own feedback (filtered by theiruserId). - The master admin key — for tenant provisioning and cross-tenant admin.
# API key (backend)
Authorization: Bearer fbk_live_...
# User JWT (end user, from the auth service)
Authorization: Bearer eyJhbGci...Getting an API key
Provision a tenant, then mint a key for an environment:
curl -X POST https://myapp.feedback.aldero.io/v1/keys \
-H "Authorization: Bearer ${MASTER_KEY}" \
-H "Content-Type: application/json" \
-d '{"environment": "production"}'
# → { "key": "fbk_...", "environment": "production", "prefix": "fbk_...", "createdAt": "..." }The raw key is returned once — store it securely. Rotate with POST /v1/keys/{environment}/rotate.
Submit feedback
POST /v1/feedbacks. Required fields: category, title, body. Optional: priority, appContext, attachmentIds, tags.
When called with a user JWT, the feedback is automatically attributed to that user (userId / userEmail are set from the token).
curl -X POST https://myapp.feedback.aldero.io/v1/feedbacks \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"category": "bug",
"title": "Export button does nothing",
"body": "Clicking Export on the reports page has no effect.",
"priority": "high",
"appContext": { "appName": "MyApp", "appVersion": "2.3.1", "os": "iOS", "osVersion": "17.4" },
"tags": ["reports", "export"]
}'Same call from a browser/client with fetch:
const res = await fetch('https://myapp.feedback.aldero.io/v1/feedbacks', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
category: 'feature',
title: 'Dark mode',
body: 'Please add a dark theme.',
}),
});
const feedback = await res.json();
// → 201 Created — full Feedback object with feedbackId, status: "open", ...category— one ofbug,feature,general,support.priority— one oflow,medium,high,critical.attachmentIds— up to 5 (see Attachments).tags— up to 10.
List and get
GET /v1/feedbacks returns a cursor-paginated list. Filter with status, category, userId, and page with cursor + limit (max 100).
curl "https://myapp.feedback.aldero.io/v1/feedbacks?status=open&category=bug&limit=20" \
-H "Authorization: Bearer ${TOKEN}"
# → { "items": [ { ... } ], "cursor": "..." | null }JWT scoping: when the caller is a user JWT, the list is automatically restricted to that user’s own feedback — the
userIdfilter is forced to their identity. API-key and master callers see all feedback for the tenant + environment.
Fetch a single thread by ID:
curl https://myapp.feedback.aldero.io/v1/feedbacks/fb_123 \
-H "Authorization: Bearer ${TOKEN}"
# → 200 Feedback, or 404 if not foundPaginate by passing the returned cursor back as the cursor query param until it comes back null.
Update and close
PATCH /v1/feedbacks/{id} updates triage fields: status, priority, assigneeId, assigneeEmail, tags.
# Move to in-progress and assign
curl -X PATCH https://myapp.feedback.aldero.io/v1/feedbacks/fb_123 \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{ "status": "in-progress", "assigneeEmail": "triage@myapp.com" }'status cycles through open → in-progress → resolved → closed.
Deleting is a soft delete — it sets the feedback’s status to closed rather than removing it:
curl -X DELETE https://myapp.feedback.aldero.io/v1/feedbacks/fb_123 \
-H "Authorization: Bearer ${TOKEN}"
# → { "deleted": true }Replies
Feedback threads support a back-and-forth conversation between users and your team via replies.
Add a reply with POST /v1/feedbacks/{id}/replies (only body is required; up to 5 attachmentIds):
curl -X POST https://myapp.feedback.aldero.io/v1/feedbacks/fb_123/replies \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{ "body": "Thanks for the report — we are looking into it." }'
# → 201 Reply with replyId, authorId, authorRole, createdAt, ...The reply author is taken from the bearer token. authorRole is one of user, admin, owner. Posting a reply bumps the feedback’s replyCount and lastReplyAt.
List the thread with GET /v1/feedbacks/{id}/replies (cursor-paginated):
curl https://myapp.feedback.aldero.io/v1/feedbacks/fb_123/replies \
-H "Authorization: Bearer ${TOKEN}"
# → { "items": [ { "replyId": "...", "body": "...", "authorRole": "admin", ... } ], "cursor": null }Attachments
To attach a file to feedback or a reply, request a presigned upload URL, PUT the bytes to it, then reference the returned attachmentId.
# 1. Presign
curl -X POST https://myapp.feedback.aldero.io/v1/attachments/presign \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-d '{ "filename": "screenshot.png", "contentType": "image/png", "sizeBytes": 84211 }'
# → { "attachmentId": "att_...", "uploadUrl": "https://...", "filename": "...", "contentType": "..." }# 2. Upload the bytes directly to the presigned URL
curl -X PUT "${UPLOAD_URL}" \
-H "Content-Type: image/png" \
--data-binary @screenshot.png// 3. Reference the attachmentId when creating feedback or a reply
await fetch('https://myapp.feedback.aldero.io/v1/feedbacks', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
category: 'bug',
title: 'Crash on upload',
body: 'See attached screenshot.',
attachmentIds: ['att_...'],
}),
});filename, contentType, and sizeBytes are all required when presigning. contentType must be one of the allowed types, and each feedback or reply accepts up to 5 attachments.
Next steps
See the full API reference for every endpoint, field, and error shape — including tenant settings, webhooks (Slack / email), members, and self-service provisioning.