API reference

Feedback

How feedback is sent from the site, and the operator endpoints that read and triage it.

Coding agents: read this page as markdown.

Ideas, bug reports and questions are sent from the feedback page (linked in the header and footer). The site’s operators read them through a private, token-protected API.

Sending feedback #

POST /feedback takes a regular HTML form (application/x-www-form-urlencoded). No account is needed.

FieldRequiredRules
kindyesidea, bug, question or other
messageyes10–5,000 characters
namenoUp to 100 characters
emailnoA valid address, up to 254; used only to reply to you
pagenoThe site path you were on, e.g. /docs

A successful post redirects (303) to /feedback?sent=1. Invalid fields re-render the form with a message next to each one (400).

Spam protection, so please use the form rather than scripting it:

  • The Origin header must match the site, so cross-site posts get 403.
  • The form carries a hidden honeypot field and the time it was shown. Posts that fill the honeypot or arrive within 3 seconds are quietly dropped.
  • At most 5 messages per address per hour, then 429.

Sending feedback from a script or an agent #

POST /api/v1/feedback takes the same message as JSON. No token is needed; with one, the message carries your handle unless you give a name.

curl -s https://releaseszn.com/api/v1/feedback \
  -H 'Content-Type: application/json' \
  -d '{"kind": "bug", "message": "POST /projects answers 500 for a repository with no releases.", "page": "/docs/errors"}'
{ "received": true }

The fields and their rules are the form’s (kind, message, optional name, email, page); an invalid one is a 422 validation_failed listing each problem, and a field we don’t know a 400 invalid_body. The limit is the form’s too: 5 messages an hour per account, or per address without a token, then 429 feedback_limited with Retry-After. For a bug, say what you sent, the error code that came back, and what you expected.

Reading feedback (operators only) #

These endpoints are not public. They listen on a separate port bound to localhost (http://127.0.0.1:8081/internal/v1) and need Authorization: Bearer <INTERNAL_API_TOKEN>. Without the token they answer 401.

MethodPathDescription
GET/feedbackThe inbox, newest first
POST/feedback/{id}/statusMark a message new, read or archived
DELETE/feedback/{id}Delete a message for good

GET /feedback #

ParameterDefaultNotes
statusallnew, read or archived
limit501–500
curl -s -H "Authorization: Bearer $INTERNAL_API_TOKEN" \
  "http://127.0.0.1:8081/internal/v1/feedback?status=new"
{
  "messages": [
    {
      "id": 1,
      "kind": "idea",
      "message": "An RSS feed per tag would be great.",
      "name": "Ada",
      "email": "ada@example.com",
      "page": "/docs",
      "status": "new",
      "created_at": "2026-10-09T18:29:51.964287Z"
    }
  ]
}

name, email and page are null when not given. An unknown status returns 400 bad_status.

POST /feedback/{id}/status #

curl -s -H "Authorization: Bearer $INTERNAL_API_TOKEN" \
  -H 'Content-Type: application/json' -d '{"status":"read"}' \
  http://127.0.0.1:8081/internal/v1/feedback/1/status
{ "id": 1, "status": "read" }

DELETE /feedback/{id} #

Removes a message for good, with the name, email and page that came with it: what we do when you ask us to delete feedback you sent (privacy). There is no undo.

curl -s -X DELETE -H "Authorization: Bearer $INTERNAL_API_TOKEN" \
  http://127.0.0.1:8081/internal/v1/feedback/1
{ "deleted": true, "id": 1 }

Errors use the usual format: 400 bad_status for a status other than new, read or archived, 404 not_found for an unknown id (or a message already deleted).