RelayDocs

API reference

Feedback API

The public API behind the feedback board. CORS is open to any origin, so you can call it from your own frontend. For a walkthrough, see Build your own UI.

Objects#

Post

json
{
  "id": "fb_k2j3h4g5f6d7",
  "title": "Dark mode for the dashboard",
  "description": "Working late nights and the bright dashboard is rough on the eyes.",
  "status": "planned",
  "authorName": "Priya",
  "votes": 42,
  "comments": 2,
  "voted": true,
  "createdAt": "2026-09-09T08:53:52.962Z",
  "updatedAt": "2026-09-12T10:01:00.000Z"
}
idstringrequired
Unique id.
titlestringrequired
Up to 120 characters.
descriptionstringrequired
Up to 4000 characters. May be empty.
statusstringrequired
One of open, planned, in_progress, done, closed.
authorNamestringoptional
Omitted when the author didn’t give a name.
votesnumberrequired
Upvote count.
commentsnumberrequired
Comment count.
votedbooleanrequired
Whether the visitor in x-visitor-id has upvoted. false when no header is sent.
createdAt / updatedAtstringrequired
ISO 8601 timestamps.

Comment

json
{
  "id": "fbc_a1b2c3d4e5f6",
  "postId": "fb_k2j3h4g5f6d7",
  "author": "agent",
  "name": "Alex from Acme",
  "body": "This is on the roadmap for next quarter.",
  "createdAt": "2026-09-12T10:01:00.000Z"
}

author is "customer" for your users and "agent" for your team’s official replies. name is omitted when not given. Visitor ids are never returned.

Endpoints#

List posts#

GET/api/feedback
sortqueryoptional
top (most votes, then newest) or new. Default top.
statusqueryoptional
Only posts with this status.
qqueryoptional
Search text, matched against title and description.
x-visitor-idheaderoptional
Fills in voted.
200 OK
{
  "posts": [ /* Post, up to 200 */ ],
  "counts": { "open": 4, "planned": 1, "in_progress": 1, "done": 1, "closed": 1, "all": 8 }
}

Create a post#

POST/api/feedback
x-visitor-idheaderrequired
The author.
titlestringrequired
Trimmed; up to 120 characters.
descriptionstringoptional
Up to 4000 characters.
namestringoptional
Author’s display name; up to 80 characters.

Returns 201 with { "post": Post }. The author’s upvote is added automatically, so the post starts with votes: 1, voted: true.

Get a post with its comments#

GET/api/feedback/:id
200 OK
{ "post": { /* Post */ }, "comments": [ /* Comment, oldest first */ ] }

Upvote or remove an upvote#

POST/api/feedback/:id/vote
x-visitor-idheaderrequired
The voter.
votedbooleanoptional
true to upvote (default), false to remove.

Idempotent: upvoting twice still counts once. Returns { "post": Post } with the updated votes and voted.

Add a comment#

POST/api/feedback/:id/comments
x-visitor-idheaderrequired
The commenter.
bodystringrequired
Up to 2000 characters.
namestringoptional
Display name; up to 80 characters.

Returns 201 with { "comment": Comment }.

Errors#

StatusExample messageWhen
400Title is required · Comment can’t be emptyValidation failed
401Missing visitor idNo valid x-visitor-id on a write
404Not foundThe post doesn’t exist or was deleted

CORS#

http
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: content-type, x-visitor-id
Access-Control-Max-Age: 86400

Note

Changing a post’s status, editing it or deleting it is done by you in the inbox (see the Owner API), never through the public API.