Super People Docs
API Reference

Feed & Posts

The feed, writing and editing posts with media, polls and recognitions, reactions, bookmarks, shares, follows and comments.

The feed, writing and editing posts with media, polls and recognitions, reactions, bookmarks, shares, follows and comments.

Generated from the OpenAPI spec by npm run docs:api -w api. Do not edit this page — change the decorators in apps/api/src/ and regenerate.

Conventions

  • Auth — a session on every route: the cookie on web, the bearer token on mobile. See Authentication.
  • Errors — every non-2xx response has the body described in Errors.
  • Types — a ? after a field name means it is optional; | null means it is present but may be null.
  • Guide — The Content Model.

Endpoints

GET /api/v1/posts — The feed

Cursor-paged, and the single most important route in the app. scope picks the tab: all, following, announcements, saved, polls or recognitions. Pass authorId instead to read one person’s posts. Every counter and viewer flag on a post (viewerReaction, hasBookmarked, author.isFollowed) is resolved for the caller, so the client renders straight from the response without a second lookup.

QueryTypeRequiredNotes
authorIdstringnoOne person’s posts.
scopeall | following | announcements | saved | polls | recognitionsnoDefaults to all. An unrecognised value falls back to it.
sortlatest | topno
tagstringnoA hashtag, without the #.
qstringnoFree text over the post body.
cursorstringnoThe nextCursor from the previous page. Omit for the first.
limitnumbernoRows per page. Defaults to 20, capped at 50.

Returns 200 → Page<PostResponse> — see Paging

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

POST /api/v1/posts — Write a post

Upload any media first (POST /posts/media, /posts/videos, /posts/documents) and send back what those returned. A post must carry something — text, an attachment, a poll or a recognition — but not necessarily text. Mention somebody by putting @[userId] in content; hashtags are parsed out of the body, so do not send tags. isAnnouncement is ignored unless the author is a super admin.

Body — WritePostDto

Returns 201 → PostResponse

ErrorWhen
400The request was rejected. message is an array of field errors when validation failed.
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

Lowercase, without the leading #. Feed them back as ?tag=.

Returns 200 → string[]

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

GET /api/v1/posts/counts — How many posts sit behind each tab

One call for every badge on the feed’s tab bar.

Returns 200 → FeedCountsResponse

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

GET /api/v1/posts/saved/count — How many posts the viewer has saved

Returns 200 → CountResponse

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

POST /api/v1/posts/media — Upload a post image

JPG, PNG or WebP, up to 10MB. One call per image; put the returned URLs in images on the post, in the order they should appear. The original is stored whole, and 1200px and 400px WebP renditions are generated alongside it — the feed is served those, so the URL returned here is the fallback rather than what a reader downloads.

Body — multipart/form-data, one part named file.

Returns 201 → UploadedImageResponse

ErrorWhen
400The request was rejected. message is an array of field errors when validation failed.
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
413The file is larger than 10MB.

POST /api/v1/posts/videos/upload-url — Ask where to upload a video

Returns a short-lived URL to PUT one clip straight into object storage, skipping this API entirely — so a 50MB upload is one hop rather than two and nothing is buffered here. MP4, WebM or MOV, up to 50MB. 1. Call this with the file’s name, type and size. 2. PUT the file to url with no credentials and with headers set exactly as returned — they are signed into the URL, so the bucket refuses a PUT that sends anything else. 3. Call POST /posts/videos/complete with the key, and put what it returns in videos on the post. A deployment with no bucket configured returns url: null; fall back to the multipart POST /posts/videos when that happens.

Body — VideoUploadUrlDto

Returns 201 → VideoUploadUrlResponse

ErrorWhen
400The request was rejected. message is an array of field errors when validation failed.
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

POST /api/v1/posts/videos/complete — Record a directly-uploaded video

Call this once the PUT from POST /posts/videos/upload-url has succeeded. The object is checked to exist and its real size and content type are read back from storage. Send the result as-is in videos on the post.

Body — CompleteVideoUploadDto

Returns 201 → UploadedFileResponse

ErrorWhen
400The request was rejected. message is an array of field errors when validation failed.
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

POST /api/v1/posts/videos — Upload a post video (through the API)

MP4, WebM or MOV, up to 50MB. The whole object comes back — send it as-is in videos. Uploads are buffered in memory, so on a phone send the file on a foreground task and show progress; a dropped connection means starting over. Prefer POST /posts/videos/upload-url, which puts the bytes straight into object storage without passing them through this API. This route remains for clients that cannot, and for deployments with no bucket configured.

Body — multipart/form-data, one part named file.

Returns 201 → UploadedFileResponse

ErrorWhen
400The request was rejected. message is an array of field errors when validation failed.
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
413The file is larger than 50MB.

POST /api/v1/posts/documents — Upload a post document

PDF, Word, Excel, PowerPoint, CSV or plain text, up to 20MB. The whole object comes back — send it as-is in documents.

Body — multipart/form-data, one part named file.

Returns 201 → UploadedFileResponse

ErrorWhen
400The request was rejected. message is an array of field errors when validation failed.
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
413The file is larger than 20MB.

GET /api/v1/posts/{postId} — One post

The same shape the feed returns. Use it to open a post from a notification.

Path parameterTypeNotes
postIdstring

Returns 200 → PostResponse

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post.

PATCH /api/v1/posts/{postId} — Edit a post

The author only. Send the whole post, not a patch: omitting images clears them. The result comes back with isEdited: true.

Path parameterTypeNotes
postIdstring

Body — WritePostDto

Returns 200 → PostResponse

ErrorWhen
400The request was rejected. message is an array of field errors when validation failed.
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post.

DELETE /api/v1/posts/{postId} — Delete a post

The author, or any super admin.

Path parameterTypeNotes
postIdstring

Returns 204 → no body

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post.

POST /api/v1/posts/{postId}/upvote — React to a post

One reaction per person per post — sending another replaces it. The body is optional and a missing reaction reads as like. Returns the whole post with the tallies recomputed, so the card can be redrawn from the response.

Path parameterTypeNotes
postIdstring

Body (optional)

FieldTypeNotes
reaction?like | dislike | celebrate | support | love | insightful | funny

Returns 201 → PostResponse

ErrorWhen
400The request was rejected. message is an array of field errors when validation failed.
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post.

DELETE /api/v1/posts/{postId}/upvote — Take back a reaction

Path parameterTypeNotes
postIdstring

Returns 200 → PostResponse

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post.

GET /api/v1/posts/{postId}/upvotes — Who reacted, and with what

Most recent first. Visible to everyone — the count was already public.

Path parameterTypeNotes
postIdstring
QueryTypeRequiredNotes
limitnumbernoRows per page. Defaults to 20, capped at 50.

Returns 200 → LikerResponse[]

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post.

POST /api/v1/posts/{postId}/poll/vote — Vote in a poll

Voting for the option you already hold takes the vote back, so one route both casts and clears — the client never has to know which it is doing. Refused once the poll has closed.

Path parameterTypeNotes
postIdstring

Body

FieldTypeNotes
optionIdstring

Returns 201 → PostResponse

ErrorWhen
400The request was rejected. message is an array of field errors when validation failed.
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post, or it has no poll.

DELETE /api/v1/posts/{postId}/poll/vote — Take back a poll vote

The same as voting for the option you already hold.

Path parameterTypeNotes
postIdstring

Returns 200 → PostResponse

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post, or it has no poll.

GET /api/v1/posts/{postId}/poll/voters — Who voted for what

Refused with 403 until the poll has closed — a poll whose votes are visible while it is running is not a secret ballot.

Path parameterTypeNotes
postIdstring

Returns 200 → PollVotersResponse[]

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Votes are revealed when the poll closes.
404No such post, or it has no poll.

POST /api/v1/posts/{postId}/poll/close — End voting early

The author only, and one way — a closed poll cannot be reopened.

Path parameterTypeNotes
postIdstring

Returns 201 → PostResponse

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post, or it has no poll.

GET /api/v1/recognitions — Recent recognitions, company-wide

Returns the posts that carry a recognition, newest first — read recognition off each one.

QueryTypeRequiredNotes
limitnumbernoRows per page. Defaults to 20, capped at 50.

Returns 200 → PostResponse[]

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

GET /api/v1/people/{userId}/recognitions — Everything one person has been recognised for

Newest first, as posts carrying a recognition.

Path parameterTypeNotes
userIdstring
QueryTypeRequiredNotes
limitnumbernoRows per page. Defaults to 20, capped at 50.

Returns 200 → PostResponse[]

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

POST /api/v1/posts/{postId}/bookmark — Save a post

Shows up under ?scope=saved. Idempotent.

Path parameterTypeNotes
postIdstring

Returns 201 → PostResponse

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post.

DELETE /api/v1/posts/{postId}/bookmark — Unsave a post

Path parameterTypeNotes
postIdstring

Returns 200 → PostResponse

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post.

POST /api/v1/posts/{postId}/share — Record a share

Bumps shareCount. Call it when the native share sheet is opened — there is nothing to undo afterwards.

Path parameterTypeNotes
postIdstring

Returns 201 → PostResponse

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post.

POST /api/v1/people/{userId}/follow — Follow somebody

Their posts then appear under ?scope=following. Idempotent.

Path parameterTypeNotes
userIdstring

Returns 204 → no body

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

DELETE /api/v1/people/{userId}/follow — Unfollow somebody

Path parameterTypeNotes
userIdstring

Returns 204 → no body

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

GET /api/v1/posts/{postId}/comments — A post’s comments

Top-level only, oldest first, cursor-paged. Each carries its own replyCount; fetch those from GET /comments/{commentId}/replies. Threads are one level deep — a reply to a reply is folded into the same thread.

Path parameterTypeNotes
postIdstring
QueryTypeRequiredNotes
cursorstringnoThe nextCursor from the previous page. Omit for the first.
limitnumbernoRows per page. Defaults to 20, capped at 50.

Returns 200 → Page<CommentResponse> — see Paging

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post.

POST /api/v1/posts/{postId}/comments — Comment on a post

Pass parentId to reply to a comment. Mentions work as they do in a post: @[userId] in the body, resolved into mentions on read.

Path parameterTypeNotes
postIdstring

Body — WriteCommentDto

Returns 201 → CommentResponse

ErrorWhen
400The request was rejected. message is an array of field errors when validation failed.
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such post.

GET /api/v1/comments/{commentId}/replies — One comment’s replies

Oldest first, same shape as the parents.

Path parameterTypeNotes
commentIdstring
QueryTypeRequiredNotes
cursorstringnoThe nextCursor from the previous page. Omit for the first.
limitnumbernoRows per page. Defaults to 20, capped at 50.

Returns 200 → Page<CommentResponse> — see Paging

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.

POST /api/v1/comments/{commentId}/vote — Vote on a comment

Up or down — deliberately not the post reaction set: a comment gets a verdict, not a mood. Voting the way you already voted clears it.

Path parameterTypeNotes
commentIdstring

Body

FieldTypeNotes
votelike | dislike

Returns 201 → CommentResponse

ErrorWhen
400The request was rejected. message is an array of field errors when validation failed.
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such comment.

DELETE /api/v1/comments/{commentId}/vote — Take back a comment vote

Path parameterTypeNotes
commentIdstring

Returns 200 → CommentResponse

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such comment.

DELETE /api/v1/comments/{commentId} — Delete a comment

The author, or any super admin.

Path parameterTypeNotes
commentIdstring

Returns 204 → no body

ErrorWhen
401No valid session. Clear the stored token and sign in again.
403Signed in, but not allowed to do this.
404No such comment.

Data models

PostResponse

FieldTypeNotes
idstring
contentstringThe body as stored. Mentions are @[userId] tokens, not names — swap each one for the matching entry in mentions when rendering. Hashtags are plain #text in the body and are also listed in tags.
isEditedboolean
isAnnouncementbooleanPosted to the Announcements tab. Super admins only.
websiteStatuspending | approved | rejected | unpublishedWhere the post stands for the public Office Updates page on superlabs.co. Only approved is shown there. Editing an approved post sets it back to pending until a super admin looks again.
tagsstring[]
mentionsMentionResponse[]
imagesstring[]Image URLs, in display order, at most four — each one the optimised rendition where the image has one and the original where it does not. Display these; imageVariants carries the same list with the smaller rendition, the untouched original, and the pixel size to lay out against.
imageVariantsPostImageResponse[]
videosPostVideoResponse[]At most one.
documentsPostDocumentResponse[]
pollPollResponse | null
recognitionRecognitionResponse | null
createdAtstring ISO 8601
updatedAtstring ISO 8601
authorAuthorResponse
upvoteCountnumberTotal across every reaction — the number shown next to the button.
reactionsReactionTallyResponse[]Per-reaction counts, busiest first; empty when nobody has reacted.
viewerReactionlike | dislike | celebrate | support | love | insightful | funny | nullWhat the viewer picked, or null.
commentCountnumber
shareCountnumber
hasUpvotedbooleanWhether the viewer reacted at all, regardless of which one.
hasBookmarkedboolean

MentionResponse

FieldTypeNotes
idstring
namestring
imagestring | null

PostImageResponse

FieldTypeNotes
urlstringThe optimised rendition — 1200px WebP — or the original for an image that has none yet. Points at object storage, not at this API.
thumbnailUrlstring | nullThe 400px rendition, or null when the original was already smaller.
sourceUrlstringThe original as stored, and the only stable URL here — url and thumbnailUrl carry a signature and expire. Send this back in images when updating a post; it is the value images has always held.
widthnumber | nullNull on images uploaded before variants existed.
heightnumber | null

PostVideoResponse

FieldTypeNotes
idstring
urlstringA signed URL onto the storage bucket, good for about a day and identical for every viewer within it, so it caches. It carries the right Content-Type and supports range requests. Send no credentials with it — the signature is the authorisation.
sourceUrlstringThe stable identifier, and the only URL here that can be written back. url is signed and expires, so an editor round-tripping a post sends this in videos[].url — sending a delivery URL back would store a signature as the post's video.
namestringThe name it was uploaded under; in storage it is a uuid.
mimeTypestring
sizeBytesnumber
thumbnailUrlstring | null

PostDocumentResponse

FieldTypeNotes
idstring
urlstring
namestring
mimeTypestring
sizeBytesnumber

PollResponse

FieldTypeNotes
questionstring
descriptionstringOptional context under the question. Empty when there is none.
optionsPollOptionResponse[]
totalVotesnumberThe denominator every bar is drawn against.
viewerOptionIdstring | nullWhich option the viewer picked, or null.
closesAtstring ISO 8601 | null
isClosedbooleanDerived from closesAt on read — do not compute it yourself.

PollOptionResponse

FieldTypeNotes
idstring
labelstring
votesnumber

RecognitionResponse

FieldTypeNotes
recipientRecognitionRecipientResponse
categoryteamwork | innovation | above_and_beyond | leadership | customer_focus | learning
messagestringWhat was said about them. Empty when nothing was written.

RecognitionRecipientResponse

FieldTypeNotes
idstring
namestring
imagestring | null
titlestring

AuthorResponse

FieldTypeNotes
idstring
namestring
imagestring | null
titlestring
rolemember | super_admin
topSkillstring | nullTheir most-used skill, shown where a department would go.
isFollowedbooleanWhether the viewer follows them — drives the Follow button's state.

ReactionTallyResponse

FieldTypeNotes
reactionlike | dislike | celebrate | support | love | insightful | funny
countnumber

WritePostDto

FieldTypeNotes
content?stringThe post body. Mention somebody with @[userId]; hashtags are parsed out of the text, so do not send tags. Optional — a post may carry only a poll, a recognition or an attachment — but a post with nothing at all is rejected. · ≤ 2000 chars
images?string[]URLs from POST /posts/media, in display order. At most 4.
documents?PostDocumentDto[]Documents from POST /posts/documents. At most 5.
videos?PostVideoDto[]Videos from POST /posts/videos. At most 1.
isAnnouncement?booleanPost to the Announcements tab. Silently ignored unless the author is a super admin, so it is safe to send either way.
poll?WritePollDto
recognition?WriteRecognitionDto

PostDocumentDto

FieldTypeNotes
urlstring
namestring≤ 255 chars
mimeTypestring
sizeBytesnumber

PostVideoDto

FieldTypeNotes
urlstring
namestring≤ 255 chars
mimeTypestring
sizeBytesnumber
thumbnailUrl?string | null

WritePollDto

FieldTypeNotes
optionsstring[]Between 2 and 6 options, 80 characters each. Ids are assigned on write.
questionstring≤ 200 chars
description?stringOptional context under the question. · ≤ 300 chars
closesAt?string | nullWhen voting stops. Absent or null leaves the poll open indefinitely.

WriteRecognitionDto

FieldTypeNotes
recipientIdstring
categoryteamwork | innovation | above_and_beyond | leadership | customer_focus | learning
message?stringWhat is said about them, quoted on the card. · ≤ 500 chars

FeedCountsResponse

FieldTypeNotes
allnumber
followingnumber
announcementsnumber
savednumber
pollsnumber
recognitionsnumber

CountResponse

FieldTypeNotes
countnumber

UploadedImageResponse

FieldTypeNotes
urlstring

VideoUploadUrlDto

FieldTypeNotes
namestringThe filename, kept only so the post can show it. · ≤ 255 chars
mimeTypevideo/mp4 | video/webm | video/quicktimeThe clip’s own type. Signed into the upload URL, so the object cannot land as anything else — and the bucket rejects a PUT that sends a different Content-Type.
sizeBytesnumberThe clip’s size. Checked here so an oversized upload is refused before the bytes are sent rather than after.

VideoUploadUrlResponse

FieldTypeNotes
urlstring | nullPUT the file here, with no credentials and with headers set exactly as given. Null when the deployment has no bucket configured — fall back to POST /posts/videos then.
keystringSend this back to POST /posts/videos/complete.
headersobjectSigned into url. Send them verbatim on the PUT; a mismatch is a 403 from the bucket, not a warning.
expiresInnumberSeconds url stays valid.

CompleteVideoUploadDto

FieldTypeNotes
keystringThe key from POST /posts/videos/upload-url.
namestring≤ 255 chars

UploadedFileResponse

FieldTypeNotes
urlstring
namestring
mimeTypestring
sizeBytesnumber

LikerResponse

FieldTypeNotes
idstring
namestring
imagestring | null
titlestring
likedAtstring ISO 8601
reactionlike | dislike | celebrate | support | love | insightful | funny

PollVotersResponse

FieldTypeNotes
optionIdstring
votersMentionResponse[]

CommentResponse

FieldTypeNotes
idstring
postIdstring
parentIdstring | nullThe comment this answers, or null for a top-level one.
contentstring
isEditedboolean
createdAtstring ISO 8601
authorAuthorResponse
mentionsMentionResponse[]
likeCountnumber
dislikeCountnumber
replyCountnumberReplies to this one. Always 0 on a reply — threads are one level deep.
viewerVotelike | dislike | nullWhat the viewer chose, or null.

WriteCommentDto

FieldTypeNotes
contentstring≤ 1000 chars
parentId?stringThe comment this answers. Absent for a top-level one; a reply to a reply is folded into its thread by the service rather than rejected here.

On this page

ConventionsEndpointsGET /api/v1/posts — The feedPOST /api/v1/posts — Write a postGET /api/v1/posts/tags — Trending hashtagsGET /api/v1/posts/counts — How many posts sit behind each tabGET /api/v1/posts/saved/count — How many posts the viewer has savedPOST /api/v1/posts/media — Upload a post imagePOST /api/v1/posts/videos/upload-url — Ask where to upload a videoPOST /api/v1/posts/videos/complete — Record a directly-uploaded videoPOST /api/v1/posts/videos — Upload a post video (through the API)POST /api/v1/posts/documents — Upload a post documentGET /api/v1/posts/{postId} — One postPATCH /api/v1/posts/{postId} — Edit a postDELETE /api/v1/posts/{postId} — Delete a postPOST /api/v1/posts/{postId}/upvote — React to a postDELETE /api/v1/posts/{postId}/upvote — Take back a reactionGET /api/v1/posts/{postId}/upvotes — Who reacted, and with whatPOST /api/v1/posts/{postId}/poll/vote — Vote in a pollDELETE /api/v1/posts/{postId}/poll/vote — Take back a poll voteGET /api/v1/posts/{postId}/poll/voters — Who voted for whatPOST /api/v1/posts/{postId}/poll/close — End voting earlyGET /api/v1/recognitions — Recent recognitions, company-wideGET /api/v1/people/{userId}/recognitions — Everything one person has been recognised forPOST /api/v1/posts/{postId}/bookmark — Save a postDELETE /api/v1/posts/{postId}/bookmark — Unsave a postPOST /api/v1/posts/{postId}/share — Record a sharePOST /api/v1/people/{userId}/follow — Follow somebodyDELETE /api/v1/people/{userId}/follow — Unfollow somebodyGET /api/v1/posts/{postId}/comments — A post’s commentsPOST /api/v1/posts/{postId}/comments — Comment on a postGET /api/v1/comments/{commentId}/replies — One comment’s repliesPOST /api/v1/comments/{commentId}/vote — Vote on a commentDELETE /api/v1/comments/{commentId}/vote — Take back a comment voteDELETE /api/v1/comments/{commentId} — Delete a commentData modelsPostResponseMentionResponsePostImageResponsePostVideoResponsePostDocumentResponsePollResponsePollOptionResponseRecognitionResponseRecognitionRecipientResponseAuthorResponseReactionTallyResponseWritePostDtoPostDocumentDtoPostVideoDtoWritePollDtoWriteRecognitionDtoFeedCountsResponseCountResponseUploadedImageResponseVideoUploadUrlDtoVideoUploadUrlResponseCompleteVideoUploadDtoUploadedFileResponseLikerResponsePollVotersResponseCommentResponseWriteCommentDto