GuidesFeedback

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 subdomainmyapp.feedback.aldero.io (production) or myapp-sandbox.feedback.aldero.io (sandbox).
  • By headers — send X-Tenant-Slug: myapp and X-Tenant-Env: production (or sandbox) 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 their userId).
  • 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 of bug, feature, general, support.
  • priority — one of low, 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 userId filter 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 found

Paginate 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 openin-progressresolvedclosed.

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.