Record whether an answer helped. A rating is up or down.
POST   https://app.kelu.dev/api/v1/public/feedback
DELETE https://app.kelu.dev/api/v1/public/feedback/:messageId
Auth: client key. Public keys also need a CAPTCHA token on POST when CAPTCHA is on. DELETE does not.

Rate an answer

FieldTypeRequiredDescription
message_idstringYesThe message_id from the chat stream’s last frame, the WebSocket done frame, or form-deflect
rating"up" or "down"YesWhether the answer helped. The older values 5 and 1 also work
commentstringNoFree text
curl https://app.kelu.dev/api/v1/public/feedback \
  -H "X-Client-Id: kl_ci_..." \
  -H "X-Client-Secret: kl_cs_..." \
  -H "Content-Type: application/json" \
  -d '{"message_id": "c3a2...", "rating": "up", "comment": "Spot on"}'
Returns 201 with the stored rating. Note the field is vote:
{
  "id": "0d9e...",
  "workspace_id": "51b0...",
  "message_id": "c3a2...",
  "vote": "up",
  "comment": "Spot on",
  "source": "widget",
  "created_at": "2026-09-27T10:12:03Z",
  "updated_at": "2026-09-27T10:12:03Z"
}
  • Each answer holds one rating. Posting again replaces it.
  • DELETE /feedback/:messageId withdraws the rating and returns 204.
  • Errors: 400 for a bad rating or message_id, 403 for a message from another knowledge base, 404 for an unknown message.
A rating needs a stored answer. With Zero data retention, answers are not stored and carry no message_id. Hide your rating buttons when it is missing.
Ratings from the API, the widget and the bots land in the same place. They show up in analytics, and down-rated answers count as knowledge gaps.

SDKs

// @kelu/sdk
await kelu.feedback({ messageId: "c3a2...", rating: "up", comment: "Spot on" });
await kelu.clearFeedback("c3a2...");

// @kelu/react (useKeluChat)
await sendFeedback(messageId, "down", "Wrong answer");
await clearFeedback(messageId);