UPDATED 2026-11-03 — Breaking: posts no longer carry interestStatus. Stop reading it and don't show a tick or any "active" state on Interested / Not interested. Both options are one-shot actions now: show the undo toast, and call PUT /api/posts/:id/interest only after the toast closes without undo. Undo sends nothing, so it never needs a reverse call (it used to flip to interested, which was wrong).
Post Interest API — Mobile Integration Guide
Interested / Not Interested on posts: private, tri-state recommendation feedback that reshapes the user's feed. This doc is for the mobile (React Native) client. Feed internals: feed-algorithm.md.
What's new
- 2026-10-08 — Breaking: posts no longer carry
interestStatus. Stop reading it and don't show a tick or any "active" state on Interested / Not interested. Both options are one-shot actions now: show the undo toast, and callPUT /api/posts/:id/interestonly after the toast closes without undo. Undo sends nothing, so it never needs a reverse call (it used to flip tointerested, which was wrong).
Scope
This is recommendation feedback only — "show me more / fewer posts like this." It is not a generic hide-post, mute-creator, or seen-tracking mechanism; those would be separate features. One state per (user, post).
Endpoint
PUT /api/posts/:id/interest
Authorization: Bearer <accessToken>
Content-Type: application/json
{ "status": "interested" | "not_interested" | null }
null clears any previous state. Idempotent — sending the current state again
is a no-op.
Response 200
{
"success": true,
"data": {
"status": "not_interested",
"updatedAt": "2026-07-08T09:12:33.000Z"
}
}
Errors
| Code | When |
|---|---|
400 | Post is the caller's own (Cannot set interest on your own post) |
401 | Missing/expired token |
404 | Post not found, or not visible to the caller |
429 | Rate limited (30 calls / 10 s) |
Reading state
There is none. Posts do not carry the viewer's interest state; the menu
never shows a tick or an active option. Repeating a call is harmless: setting
the state the post already has is a no-op on the server (no double-counting),
so the client doesn't need to know the current state. Don't show the options
on the viewer's own posts (author.id === me).
What it does to the feed
not_interested— the post is permanently excluded from every future feed build for this user (all pools and fallback tiers). Clearing the state makes it eligible again.- Both states — the viewer's topic-interest weights shift immediately
(one explicit tap ≈ 10 likes' worth of signal):
interestedboosts the post's topics,not_interestedsuppresses them. The next feed rebuild reflects it — no sync-cycle delay. - Reversible — signals track the user's current preference, not accumulated taps. Flipping interested → not_interested fully undoes the boost and applies the suppression; clearing undoes whichever was set.
- Reposts: marking a repost affects only that repost row, not the original.
- Privacy: the author is never notified; no public counts exist.
Recommended client UX
- Post overflow ("…") menu on non-own posts: "Interested" / "Not interested". Plain actions — no checkmark, no toggle.
- Commit after the undo window, not on tap. Tap → show the toast with
Undo. Undo tapped → restore the local UI, send nothing. Toast closes
without undo → send the
PUTonce. No reverse call is ever needed. - On
not_interested: optimistically remove the post from the local feed list, show the toast ("You'll see fewer posts like this"). After thePUTsucceeds you may refetch feed page 1 without a cursor — a no-cursorGET /api/feedrebuilds the server-side snapshot with the exclusion applied. Continuing to page an old cursor is also safe — snapshot pages are frozen and deduped, so the post cannot resurface mid-scroll. - On
interested: toast ("You'll see more posts like this"). Nothing changes on the card. - On error: put a hidden post back; no other UI to roll back.
null(clear) still works but the client has no use for it under this UX.
Examples
TOKEN=... # from POST /api/auth/login (use your own test account)
# Mark not interested
curl -X PUT localhost:3001/api/posts/<POST_ID>/interest \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"status":"not_interested"}'
# Flip to interested
curl -X PUT localhost:3001/api/posts/<POST_ID>/interest \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"status":"interested"}'
# Clear (not used by the app UX; kept for completeness)
curl -X PUT localhost:3001/api/posts/<POST_ID>/interest \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"status":null}'
# Verify exclusion — id must be absent after marking not_interested
curl -s localhost:3001/api/feed -H "Authorization: Bearer $TOKEN" \
| jq '[.data.items[].id] | index("<POST_ID>")'
Swagger UI: http://localhost:3001/api/docs (Posts → PUT /api/posts/{id}/interest).