The Content Model
Mentions, hashtags, writing a post, reactions, polls, recognitions and comments.
Mentions
A post or comment body stores mentions as @[userId] tokens, not names:
{
"content": "Shipping this today with @[Xq3Tn8Wd2Kp5Lm9Rv1Bz6Hc4Jf7Ys0Ga] #release",
"mentions": [
{ "id": "Xq3Tn8Wd2Kp5Lm9Rv1Bz6Hc4Jf7Ys0Ga", "name": "Priya", "image": null }
]
}When rendering, swap each token for the matching entry in mentions. When
composing, write the token. This is deliberate: names are resolved on every read,
so renaming somebody updates every post that ever tagged them.
A body with no matching entry in mentions — the person was deactivated — should
render as plain text rather than a broken chip.
Hashtags
Plain #text in the body, parsed out by the server into tags (lowercase,
without the #, at most 10). Never send tags yourself. Feed a tag back as
GET /posts?tag=release.
Writing a post
POST /api/v1/posts{
"content": "Great work this sprint, @[usr_12] #shipit",
"images": ["/api/v1/files/posts/9f2c….jpg"],
"videos": [],
"documents": [{ "url": "…", "name": "notes.pdf", "mimeType": "application/pdf", "sizeBytes": 84213 }],
"poll": {
"question": "Where should the offsite be?",
"description": "Optional context",
"options": ["Goa", "Coorg", "Pondicherry"],
"closesAt": "2026-09-20T12:00:00Z"
},
"recognition": { "recipientId": "usr_9", "category": "teamwork", "message": "Carried the migration." },
"isAnnouncement": false
}Everything is optional, but a post must carry something — text, an
attachment, a poll or a recognition. An empty one is a 400:
"Write something before posting."
isAnnouncement is ignored unless the author is a super admin. Poll option ids
are assigned by the server on write; you send plain strings.
Editing takes the whole post, not a patch. PATCH /posts/{postId} with
images omitted clears the images. Send the full object.
Reactions
Seven of them: like, dislike, celebrate, support, love, insightful,
funny. One per person per post — sending another replaces it.
POST /api/v1/posts/{postId}/upvote
{ "reaction": "celebrate" }The body is optional; a missing reaction reads as like. The whole post comes
back with the tallies recomputed, so redraw the card from the response rather
than incrementing locally.
Read viewerReaction (which one the caller picked, or null), reactions
(per-reaction counts, busiest first) and upvoteCount (the total across all of
them).
Comments are separate and narrower: like or dislike only, via
POST /comments/{commentId}/vote. A comment gets a verdict, not a mood.
Polls
poll on a post carries options with live vote counts, totalVotes,
viewerOptionId and isClosed.
- Vote:
POST /posts/{postId}/poll/votewith{ "optionId": "…" }. Voting for the option you already hold takes the vote back — one route both casts and clears, so the client never has to know which it is doing. isClosedis derived on read fromclosesAt. Trust it; do not compute it from the clock yourself — the device's clock is not the server's.- Who voted for what (
GET /posts/{postId}/poll/voters) is 403 until the poll closes. A poll whose votes are visible while it runs is not a secret ballot. - The author can end voting early with
POST /posts/{postId}/poll/close. One way — a closed poll cannot be reopened.
Recognitions
A post can recognise somebody in one of six categories: teamwork,
innovation, above_and_beyond, leadership, customer_focus, learning.
Create one with POST /api/v1/posts, sending a recognition object
(recipientId, category, message) as shown in
Writing a post. The full request and response are in the
Super People Docs, under API
Reference → Feed & Posts.
GET /recognitions and GET /people/{userId}/recognitions both return posts
that carry a recognition — read recognition off each one.
Comments
Threads are one level deep. Top-level comments come from
GET /posts/{postId}/comments, each carrying its own replyCount; fetch those
from GET /comments/{commentId}/replies. Replying to a reply is folded into the
same thread rather than nesting, so replyCount on a reply is always 0.