Skip to content
Micup API

Feed

A post is a short text, up to four images, or both, published to the accounts that follow its author. A post can instead hold one video, which becomes a post only after it is encoded and checked. A post with text can carry a poll instead of media, and a public post can also reach other accounts through Discover and the page of the category it is filed under. New posts are not pushed over the Gateway; a client reads the home feed. Accounts that may read a post can like it, comment on it and reply to its comments. Likes, comments, replies and new followers reach the account they are aimed at as notifications. Posts and comments are reported through Report feed post and Report feed comment.

Every route here except Receive Bunny Stream webhook requires a user session. A bot or OAuth2 bearer credential is refused with 403 ACCESS_DENIED, and a limited account is refused with 403 ACCOUNT_LIMITED by every route that writes. While the feature_feed limit of the caller is 0, every route that requires a session returns 403 FEATURE_TEMPORARILY_DISABLED.

A post is marked public when its author’s profile privacy level reads as EVERYONE at the time it is created; the server sets the mark and ignores the public field of the request. Posts marked earlier keep the mark, including those an earlier client marked while the author was at ALL_GUILDS. While the author’s level reads as EVERYONE, any account with a user session can read a public post by its ID, like it, comment on it and vote in its poll without the profile rule, unless either account blocks the other or the author is disabled or deleted. Once the level reads as anything else, the post falls back to the profile rule on the next request. The public post of an account flagged as a spammer keeps the profile rule. List user posts and List post likers keep the profile rule for every post. On a public post of another account, List comments leaves out the comments of accounts that block the caller or that the caller blocks. An account that reaches a public post only through its public flag needs more to write there: see Create comment and Vote in poll.

A video post is uploaded to Bunny Stream rather than through Upload post media. It needs a feature_feed_video limit other than 0 for the caller and the instance’s Bunny Stream settings; without either, Start video post and Re-sign video upload return 403 FEATURE_TEMPORARILY_DISABLED.

  1. Start video post returns a video upload ticket. It takes one post of the day at once, and an account has at most 3 videos on their way.
  2. Upload the file with TUS to upload.endpoint, sending upload.signature as AuthorizationSignature, upload.expires as AuthorizationExpire, id as VideoId and library_id as LibraryId. The signature lasts 2 hours; Re-sign video upload signs the same upload again within 6 hours of its start.
  3. Bunny Stream encodes the video and reports each step to Receive Bunny Stream webhook. Once it is encoded, the video is checked for explicit content through Bunny’s content tagging and frames of the video.
  4. A clean video becomes a post with the text given at the start, public when the author’s level read as EVERYONE at the start, and reaches home feeds and Discover like any post. No route shows the video before that.

A video that fails to encode, is stored larger than 500 MB, is refused by the check or is still not a post after 72 hours is deleted, and its author receives a system DM unless the account is disabled or deleted. List pending videos shows the caller’s videos on their way. A published video that Bunny later tags as adult, or whose upload is replaced, loses its post.

FieldTypeDescription
id1snowflakeThe post ID
authorpartial user objectThe author of the post
content?stringThe plain text of the post
attachments2array[message attachment object]The images of the post (at most 4); empty for a video post
edited_at?ISO8601 timestampWhen the post was last edited
like_countintegerHow many accounts like the post
comment_countintegerHow many comments and replies the post has
likedbooleanWhether the caller likes the post
publicbooleanWhether the post is public
poll?poll objectThe poll of the post, when it has one
link_preview?3link preview objectThe previewed link of the post, when it has one
video?4video objectThe video of a video post
view_count?integerHow many times the post was seen, a minute or two behind; absent while view counts are off
view_token?stringTicket for Record post views, for the caller only, valid 12 hours
author_following?booleanWhether the caller follows the author; only on Discover, category and topic pages, and never on the caller’s own posts
category?5?post category objectThe category the post is filed under; absent when it has none
category_listed?booleanOnly in the answer to Create post for a post filed under a category: whether that category’s page lists it
topic?6?post topic objectThe topic the post is filed under; absent when it has none
topic_listed?booleanOnly in the answer to Create post for a post filed under a topic: whether that topic’s page lists it

1 Its snowflake timestamp is when the post was published. It is the before cursor of Get home feed and List user posts

2 Each url and proxy_url is a signed attachment URL

3 Resolved in the background after the post is published, so a new post can arrive without it

4 Absent while the instance has no Bunny Stream settings, so a video post then shows only its text

5 A category turned off later still names the post’s category; null only when the category is no longer known

6 An ended topic still names the post’s topic; null once an admin removed the topic

The URLs are signed for the request that returned them and name only this video’s files. They stay the same within each hour and work until expires_at, at least 2 hours after the request (longer for a video longer than 90 minutes, at most about 7 hours); read the post again for new ones.

FieldTypeDescription
idstringThe Bunny Stream ID (GUID) of the video
width?integerThe width of the picture as shown, in pixels
height?integerThe height of the picture as shown, in pixels
duration?integerThe length in seconds
poster_urlstringThe signed URL of the poster image
hls_urlstringThe signed URL of the HLS playlist
mp4_url1?stringThe signed URL of an MP4 file for players without HLS
expires_atISO8601 timestampWhen the signed URLs stop working

1 The 720p file, or the largest one below it; null when Bunny keeps no MP4 of the video

A poll takes one vote per account. A vote cannot be changed or taken back, and the author of the post cannot vote. Which account chose which option is never shown.

FieldTypeDescription
optionsarray[string]The choices, in order
expires_atISO8601 timestampWhen voting ends
votes1?array[integer]The votes for each option, in the order of options
voted?integerThe index of the caller’s option, or null when the caller has not voted

1 null until the caller votes, unless the caller wrote the post or voting has ended

FieldTypeDescription
optionsarray[string]The choices, 2-4, each 1-80 characters; two that match ignoring case and outer spaces are refused
duration_hoursintegerHow many hours the poll takes votes (1-168)

A post previews at most one link: the first link of its text to a YouTube video, a Twitch or Kick channel, video or clip, or an invite of this instance. Other links stay plain text. The unfurled card of a YouTube, Twitch or Kick link has its media through the Media Proxy; when it cannot be unfurled, the card holds only the site name. An invite is never fetched; read it through Get invite.

FieldTypeDescription
kindstringyoutube, twitch, kick or invite
urlstringThe link as written in the post
invite_code?stringThe invite code for invite; null for the other kinds
embed?embed objectThe card of the link; null for invite
FieldTypeDescription
id1snowflakeThe comment ID
post_idsnowflakeThe post the comment belongs to
authorpartial user objectThe author of the comment
contentstringThe plain text of the comment
edited_at?ISO8601 timestampWhen the comment was last edited; comments cannot be edited yet, so it is null
parent_id?2?snowflakeThe top-level comment a reply answers; null for a top-level comment
reply_count?3integerHow many replies a top-level comment has
like_count?integerHow many accounts like the comment or reply
liked?booleanWhether the caller likes the comment or reply

1 Its snowflake timestamp is when the comment was written. It is the before cursor of List comments and List replies

2 A reply is a comment object too. Replies go one level deep: a reply cannot be answered

3 Present on top-level comments only

The actions of one type aimed at one account fold into one notification per post and clock hour (per clock hour for follows). A later action of the same hour updates the notification in place under the same id.

FieldTypeDescription
id1snowflakeThe notification ID
type5stringpost_like, post_comment, comment_reply, comment_like or follow
post_id?snowflakeThe post liked or commented on; null for follow
comment_id2?snowflakeThe newest comment for post_comment, the comment the newest reply answers for comment_reply, else null
actors3array[partial user object]Up to three of the accounts behind it, the most recent first
actor_count4integerHow many accounts are behind it
updated_atISO8601 timestampWhen the newest action folded into it happened

1 Its snowflake timestamp is when its first action was folded in. It is the before cursor of List notifications

2 The comment may have been deleted since; the post then still lists its other comments

3 Holds at least one account. Accounts the recipient blocks are left out, and a notification with no other account is left out entirely

4 Counts each account once while it stays among the three newest; an account that undoes and repeats a like after three others may count again. Undoing a like or deleting a comment does not lower it

5 comment_reply goes to the author of a comment that receives a reply, and only while the instance’s feature_feed_reply_notifications limit is not 0. comment_like goes to the author of a liked comment or reply, per post, and only while feature_feed_comment_like_notifications is not 0. A client ignores a type it does not know

The caller’s notification read cursor and unread count, sent in READY as feed_notification_state. It is the same on all of the caller’s devices.

FieldTypeDescription
last_read_at?ISO8601 timestampThe read cursor that Mark notifications read moves; null before the first read
unread_count1integerNotifications updated after last_read_at (0-10)
unread_seq2integerThe unread sequence this count includes
unread_epoch3integerWhen that sequence began, in Unix milliseconds; 0 before it exists

1 Counted over the newest 20 notifications of the newest 20 days and capped at 10, so a client shows 9+ from 10. A notification whose post was deleted counts until the notifications are read

2 FEED_NOTIFICATION_CREATE carries newer values; the rise over the last value a client saw is the number of newly unread notifications

3 The sequence lives for a day after its last change (a newly unread notification, a read or a recount) and then starts again from 0 under a later epoch. A value under a later epoch than a client’s counts from 0; one under an earlier epoch is a late event and changes no count

One of the caller’s video posts on its way.

FieldTypeDescription
idstringThe Bunny Stream ID (GUID) of the video
statestringuploading, processing (Bunny is encoding it), scanning (it is being checked) or published
created_atISO8601 timestampWhen the upload started
post_id?snowflakeThe post the video became; null until it is published
FieldTypeDescription
idstringThe Bunny Stream ID (GUID) of the video, sent as the VideoId TUS header
library_idstringThe Bunny Stream library, sent as the LibraryId TUS header
uploadTUS upload objectWhat the TUS upload is signed with
FieldTypeDescription
endpointstringThe TUS endpoint to upload the file to
signaturestringThe AuthorizationSignature TUS header
expiresintegerThe AuthorizationExpire TUS header, in Unix seconds; 2 hours after the ticket was issued
FieldTypeDescription
idsnowflakeThe ID of a post the caller saw
tokenstringThe view_token that came with the post (at most 64 characters)
count?integerViews since the last batch, one per 3 seconds on screen; the server takes 1-20, and 1 when absent

The instance’s admins curate a short list of feed categories, each with a name, an emoji and the countries it is shown in. A post can be filed under one category when it is created, and a public post filed under one is listed on that category’s page. The caller’s country comes from the address of the request, never from the client, so a category shown only in one country can still be opened and posted to from anywhere; the country only decides what List feed categories offers. Members cannot rename categories, and categories are never deleted; an admin turns one off instead.

A post filed under a category is refused with 400 INVALID_FORM_BODY and the field code FEED_CATEGORY_PHRASE_BLOCKED on category_id when its text, a poll option or an image description or title holds a phrase the admins refuse in categories; the same post without the category can be published. A phrase matches from the start of a word, with Turkish letters folded to ASCII and case ignored; a phrase of five letters or more also matches when punctuation splits it inside a word (erd.oğan). The general content filter is checked first and answers 403 CONTENT_BLOCKED on its own. Posts without a category are not checked against that list. A video post is checked again when it is published: if its text holds a refused phrase by then, it is published without being listed on the category’s page.

While the caller’s feature_feed_categories limit is 0, the category routes return 403 FEATURE_TEMPORARILY_DISABLED and a category_id in a new post is ignored.

FieldTypeDescription
idsnowflakeThe ID of the category
namestringThe name the admins gave the category, in the language of its countries
emojistringThe emoji shown before the name
FieldTypeDescription
idsnowflakeThe ID of the category
namestringThe name of the category
emojistringThe emoji shown before the name

The instance’s admins publish a few short lived topics a day, such as a match or a game patch, and members post under them. A system job proposes topics from fixtures, game and sports news headlines and the terms rising in the feed; the admins publish proposals by hand, while fixtures of the chosen leagues can publish on their own. Each topic belongs to one category, which gives it its countries and its refused phrases, and a post filed under a topic is filed under its category too. A topic takes posts while it is live, at most 72 hours from when it was published, and stays readable once it ends; an admin can remove one, which hides it and drops its name from posts. New topics are not pushed over the Gateway; List feed topics shows a new one within 30 seconds.

A post filed under a topic is refused with 400 INVALID_FORM_BODY on topic_id: with the field code INVALID_FORMAT when the topic is unknown, not live or its category is off, and with FEED_CATEGORY_PHRASE_BLOCKED when a text holds a phrase the admins refuse in categories, as for categories. While the caller’s feature_feed_topics limit is 0, the topic routes return 403 FEATURE_TEMPORARILY_DISABLED and a topic_id in a new post is ignored.

FieldTypeDescription
idsnowflakeThe ID of the topic
titlestringThe title (1-60 characters)
description?stringOne sentence about the topic (1-140 characters)
image1?stringThe hash of the topic image
category?post category objectThe category the topic and its posts are filed under; null when the category is no longer known
starts_atISO8601 timestampWhen the topic started taking posts
ends_atISO8601 timestampWhen the topic stops taking posts
statusstringlive while it takes posts, ended after
post_count2integerHow many posts its page lists

1 Served by the media proxy at branding/feed-topic-{topic_id}/{image}.webp

2 For display only; it can start again from 0 if the instance loses its cache

FieldTypeDescription
idsnowflakeThe ID of the topic
titlestringThe title of the topic

What Get feed suggestions returns.

FieldTypeDescription
users1array[partial user object]Up to five popular accounts for the caller to follow
guilds2array[discovery guild object]Up to five popular Discovery communities the caller is not in

1 Empty for a caller who may not follow strangers or is not an adult by date of birth

2 Empty while the instance has Discovery disabled

POST/v1/feed/attachments

Plans the upload of up to four post images and returns 200 with attachments, an array of singlepart attachment upload objects in request order. Each object echoes the id of its request item. Upload each file with one PUT to its upload_url, then pass the upload_filename in Create post. A video is not uploaded here; it starts at Start video post.

A filename must name a PNG, JPEG, WebP or GIF image, and a declared size may not exceed 10 MiB or the caller’s max_attachment_file_size limit when that is lower. The instance must allow presigned uploads.

FieldTypeDescription
attachmentsarray[attachment upload request item object]The images to upload (1-4)
StatusBodyCondition
200object with attachmentsThe upload URLs were issued
4001error responseThe body is invalid, a filename is not an image, or a size is too large
4032error responsePresigned uploads are off and the request returns FEATURE_TEMPORARILY_DISABLED

1 A filename that is not a PNG, JPEG, WebP or GIF returns INVALID_FORM_BODY with the field code ATTACHMENT_MUST_BE_IMAGE; a size over the limit returns FILE_SIZE_TOO_LARGE

2 Also returned for an MP4 or WebM filename while the caller’s feature_feed_video limit is 0, as before video posts moved to Bunny Stream; once it is not 0, such a filename returns 400 with ATTACHMENT_MUST_BE_IMAGE

10 requests per 10 seconds for each authenticated user, on the feed:attachment:upload bucket.

POST/v1/feed/posts

Publishes a post and returns 200 with the post object. A post needs text, media, or both. A poll needs text, which is its question, and takes no media.

Each image is checked like a message attachment, and more strictly: the media type sniffed from the uploaded bytes must be PNG, JPEG, WebP or GIF, the file may not exceed 10 MiB, explicit media is refused, and a file the media service cannot read is refused rather than kept as a plain file. An upload key is consumed by the post that uses it. A video post is made through Start video post instead.

The post is public, and can reach Discover, when the caller’s profile privacy level reads as EVERYONE; public in the body is ignored. The first link of the text that can be previewed becomes the post’s link preview.

An account can publish max_feed_posts_per_day posts a feed day (50 by default). Followers see the first feed_fanout_max_posts_per_day posts of each day (10 by default) in their home feed; later posts appear only in List user posts.

FieldTypeDescription
content??stringThe plain text of the post (1-2,000 characters)
attachments?array[pre-uploaded attachment object]Images uploaded through Upload post media (at most 4)
public?booleanIgnored; the server sets whether the post is public
poll?poll input objectA poll; needs content as its question and no attachments
category_id?4?snowflakeA category from List feed categories to file the post under
topic_id?5?snowflakeA live topic from List feed topics to file the post under
StatusBodyCondition
200post objectThe post was published
4001error responseThe post is empty, an attachment or the poll is invalid, or the category or the topic refuses it
4033error responseThe text, a poll option or an image is blocked: CONTENT_BLOCKED
4292error responseThe caller already published max_feed_posts_per_day posts this feed day

1 An empty post, or a poll without text, returns CANNOT_SEND_EMPTY_MESSAGE. An image returns INVALID_FORM_BODY with the field code ATTACHMENT_MUST_BE_IMAGE for another sniffed type and UPLOADED_ATTACHMENT_NOT_FOUND for an unknown or used upload key, MEDIA_METADATA_ERROR when it cannot be read, FILE_SIZE_TOO_LARGE over the size limit, and EXPLICIT_CONTENT_CANNOT_BE_SENT for explicit media. An MP4 or WebM filename returns ATTACHMENT_MUST_BE_IMAGE too while the caller’s feature_feed_video limit is not 0. A poll with attachments returns the field code INVALID_FORMAT on poll, and repeated options the same code on poll.options

2 Returned as RATE_LIMITED, with retry_after counting down to the start of the next feed day

3 An MP4 or WebM filename while the caller’s feature_feed_video limit is 0 returns FEATURE_TEMPORARILY_DISABLED

4 An unknown or disabled category returns INVALID_FORM_BODY with the field code INVALID_FORMAT on category_id, and a refused phrase the field code FEED_CATEGORY_PHRASE_BLOCKED on category_id. The post is listed on the category’s page only when it is public and its author qualifies as for Discover; any other post keeps the category on its object only, and the answer’s category_listed says which. Ignored while the caller’s feature_feed_categories limit is 0

5 The post is filed under the topic’s category too, and category_id is then ignored. A topic that is unknown, not live or whose category is off returns INVALID_FORM_BODY with the field code INVALID_FORMAT on topic_id, and a refused phrase FEED_CATEGORY_PHRASE_BLOCKED on topic_id. The topic’s page lists the post on the same terms as a category page, and the answer’s topic_listed says whether it does. Ignored while the caller’s feature_feed_topics limit is 0

5 requests per 10 seconds for each authenticated user, on the feed:post:create bucket.

POST/v1/feed/videos

Starts a video post and returns 200 with a video upload ticket object. The text is checked like message content now; the post is published with it once the video passes processing.

The video takes one of the caller’s max_feed_posts_per_day posts at once, and a cancelled or refused video does not give it back. An account has at most 3 videos uploading, processing or being checked at a time.

FieldTypeDescription
filenamestringThe name of the file (1-255 characters)
file_size1integerThe size of the file in bytes (1-524,288,000)
content_typestringvideo/mp4, video/webm, video/quicktime or video/x-matroska
content??stringThe plain text of the post (1-2,000 characters)
public?booleanIgnored; the server sets whether the post is public
category_id?2?snowflakeA category from List feed categories the post is filed under once it is published
topic_id?3?snowflakeA live topic from List feed topics the post is filed under once it is published

1 Bunny’s stored copy is measured again once it arrives, and a video over 500 MB is then deleted

2 Checked as in Create post when the upload starts: an unknown or disabled category returns 400 INVALID_FORM_BODY with INVALID_FORMAT on category_id, and a refused phrase in the text the field code FEED_CATEGORY_PHRASE_BLOCKED on category_id

3 Checked as in Create post when the upload starts; the post is listed on the topic’s page when it is published, even if the topic ended in the meantime

StatusBodyCondition
200video upload ticket objectThe video was started
400error responseThe body is invalid and the request returns INVALID_FORM_BODY
4031error responseVideo posts are off for the caller and the request returns FEATURE_TEMPORARILY_DISABLED
4292error response3 videos are on their way, or the caller already has max_feed_posts_per_day posts this feed day
502error responseBunny Stream did not create the video and the request returns BAD_GATEWAY

1 Off means a feature_feed_video limit of 0 or an instance without Bunny Stream settings. A blocked text returns CONTENT_BLOCKED

2 Returned as RATE_LIMITED; retry_after is 60 seconds for the videos on their way, and counts down to the start of the next feed day for the daily limit

3 requests per minute for each authenticated user, on the feed:video:create bucket.

POST/v1/feed/videos/{video_id}/signature

Returns 200 with a new video upload ticket object for one of the caller’s videos that is still uploading, for a TUS upload whose signature ran out. A video can be re-signed within 6 hours of its start.

FieldTypeDescription
video_idstringThe Bunny Stream ID (GUID) of the video
StatusBodyCondition
200video upload ticket objectThe upload was signed again
403error responseVideo posts are off for the caller and the request returns FEATURE_TEMPORARILY_DISABLED
4041error responseThe caller has no such video still uploading and the request returns NOT_FOUND
409error responseThe video is being changed by another request and the request returns CONFLICT

1 Also returned 6 hours after the start

10 requests per minute for each authenticated user, on the feed:video:sign bucket.

DELETE/v1/feed/videos/{video_id}

Cancels one of the caller’s videos that is not a post yet, deletes it from Bunny Stream and returns 204 with an empty body. A published video goes with its post through Delete post.

FieldTypeDescription
video_idstringThe Bunny Stream ID (GUID) of the video
StatusBodyCondition
204emptyThe video was cancelled
404error responseThe caller has no such video on its way and the request returns NOT_FOUND
409error responseThe video is being changed by another request and the request returns CONFLICT

10 requests per 10 seconds for each authenticated user, on the feed:video:cancel bucket.

GET/v1/feed/videos

Returns up to 20 of the caller’s pending video objects, newest first: the videos uploading, processing or being checked, and those published in the last hour with their post_id. A video that was refused or cancelled leaves the list.

StatusBodyCondition
200array of pending video objectsThe list was returned, possibly empty

10 requests per 10 seconds for each authenticated user, on the feed:video:list bucket.

POST/v1/feed/videos/webhookUnauthenticated

Accepts a Bunny Stream video status change and returns 200 with a webhook received object. The instance’s Bunny Stream library sends it; clients never call it. The signature headers are the credential, and the status is read again from Bunny before anything changes.

  • A body over 4 KiB returns 400 INVALID_REQUEST, and so does a signed body that is not the expected JSON.
  • A signature that is missing, of another version or algorithm, or not made over the raw body with the library’s read-only API key returns 401 UNAUTHORIZED.
  • An event of another library or with an unknown status is acknowledged and ignored.
  • One status of one video is handled once a minute; a repeat within the minute is acknowledged and ignored.
FieldTypeDescription
X-BunnyStream-SignaturestringLowercase hex HMAC-SHA256 of the raw body, keyed with the library’s read-only API key
X-BunnyStream-Signature-VersionstringThe signature version, which must be v1
X-BunnyStream-Signature-AlgorithmstringThe signature algorithm, which must be hmac-sha256
FieldTypeDescription
VideoLibraryIdintegerThe Bunny Stream library of the video
VideoGuidstringThe Bunny Stream ID (GUID) of the video
StatusintegerThe Bunny Stream video status
StatusBodyCondition
200webhook received objectThe event was authenticated and queued, or acknowledged and ignored
400error responseThe body is too large or malformed and the request returns INVALID_REQUEST
401error responseThe signature did not verify and the request returns UNAUTHORIZED
503error responseThe instance has no Bunny Stream settings and the request returns SERVICE_UNAVAILABLE

60,000 requests per minute for each client IP address, or for the authenticated user when a credential happens to be supplied, on the feed:video:webhook bucket. The bucket is exempt from the global bucket.

GET/v1/feed/posts/{post_id}

Returns 200 with one post object.

FieldTypeDescription
post_idsnowflakeThe ID of the post
StatusBodyCondition
200post objectThe post was returned
403error responseThe caller may not read the author’s posts and the request returns MISSING_ACCESS
4041error responseNo post has the ID and the request returns UNKNOWN_FEED_POST

1 Also returned when the caller and the author block each other

60 requests per 10 seconds for each authenticated user, on the feed:post:get bucket.

DELETE/v1/feed/posts/{post_id}

Deletes one of the caller’s posts with its media, likes, poll votes, comments and replies and returns 204 with an empty body. Home feeds drop the post on their next read. The video of a video post is deleted from Bunny Stream in the background.

FieldTypeDescription
post_idsnowflakeThe ID of the post
StatusBodyCondition
204emptyThe post was deleted
403error responseAnother account wrote the post and the request returns MISSING_PERMISSIONS
404error responseNo post has the ID and the request returns UNKNOWN_FEED_POST

10 requests per 10 seconds for each authenticated user, on the feed:post:delete bucket.

GET/v1/feed/home

Returns one page of the posts of the accounts the caller follows and of the caller, as an array of post objects with the newest post first. The home feed covers the last 30 days.

One request reads a bounded part of the home feed, so a page can hold fewer posts than limit, or none, while older posts remain.

The pagination signal is in the response headers. X-Next-Before is the before value for the next page, and it is absent when no older posts are known to remain. Read the next page with X-Next-Before as before when it is present, and otherwise with the last post’s id. An empty page without X-Next-Before is the end of the home feed. A client that follows empty pages on its own should wait between the requests and stop after a few in a row, since each one counts against the rate limit below.

Posts of an account the caller stops following, or blocks, or is blocked by, leave the home feed.

An account the caller follows shows all its posts while the caller sees its full profile under Get user profile. One the caller sees only as a public profile (for example one followed through a public Discover post with Follow user and post_id, or from the feed suggestions) shows only its public posts here, from the whole 30-day window, and only while its profile privacy level reads as EVERYONE; one whose profile is limited or refused shows none. Those public posts are left out while the author is flagged as a spammer or for suspicious activity, and while the caller’s feature_feed_discover limit is 0. As on Discover, a post hidden after reports, or one with images or a video while the instance’s feature_feed_discover_media or feature_feed_discover_video limit is 0, is left out too, and so is every post between accounts that block each other. A narrower privacy level of the author reaches the home feed on its next page, and a wider one within a minute.

FieldTypeDescription
before?snowflakeThe cursor selecting posts published before this post ID
limit?integerThe maximum number of posts to return (1-50, default 25)
StatusBodyCondition
200array of post objectsThe page was returned, possibly empty; X-Next-Before says where the next page starts

20 requests per 10 seconds for each authenticated user, on the feed:home bucket.

GET/v1/feed/discover

Returns one page of Discover, the public posts of the whole instance that drew the most reactions lately, as an array of post objects with the best post first. While the caller’s feature_feed_discover limit is 0, the route returns 403 FEATURE_TEMPORARILY_DISABLED.

Discover ranks the public posts of the last 72 hours by their likes, comments and poll votes from established accounts, a newer reaction weighing more than an older one, and spreads out the posts of one author. An author qualifies with a verified email address, an account at least 24 hours old (7 days for a post with a video) and a profile privacy level that reads as EVERYONE, and not while flagged as a spammer or for suspicious activity. While the instance’s feature_feed_discover_media limit is 0, posts with images are left out, and Twitch and Kick previews lose their images. While its feature_feed_discover_video limit is 0, video posts are left out. Discover holds no signed video URL; each page signs the videos it returns for the caller. A post that draws enough reports leaves Discover but stays readable through its ID. A post whose text, poll or link preview holds an invite link of this instance, a Discord invite or a link through a known link shortener never enters Discover; it stays in its author’s followers’ home feeds and on the author’s profile. Posts of accounts that block the caller, or that the caller blocks, are left out.

The list holds up to 1,000 posts and is rebuilt about every 30 seconds, so the counts on a page can be about a minute old; liked, author_following and the poll state are the caller’s own at the time of the request.

The pagination signal is in the response headers. X-Next-Cursor is the cursor value for the next page, and it is absent at the end of the list. A page read after a rebuild continues at the same position in the new list, so a post can repeat or be skipped; drop the posts already shown.

FieldTypeDescription
cursor?stringThe X-Next-Cursor value of the previous page
limit?integerThe maximum number of posts to return (1-50, default 20)
StatusBodyCondition
200array of post objectsThe page was returned, possibly empty; X-Next-Cursor says where the next page starts
403error responseThe caller’s feature_feed_discover limit is 0 and the request returns FEATURE_TEMPORARILY_DISABLED

20 requests per 10 seconds for each authenticated user, on the feed:discover bucket.

GET/v1/feed/categories

Returns the enabled feed categories shown in the caller’s country, as an array of category objects ordered by the position the admins set. A category lists the countries it is shown in, or none to be shown everywhere. The country comes from the request address; when it is unknown, only the categories shown everywhere are returned.

StatusBodyCondition
200array of category objectsThe categories were returned, possibly none
403error responseThe caller’s feature_feed_categories limit is 0 and the request returns FEATURE_TEMPORARILY_DISABLED

10 requests per 10 seconds for each authenticated user, on the feed:categories bucket.

GET/v1/feed/categories/{category_id}/posts

Returns one page of the public posts filed under a category, as an array of post objects with the newest post first. The page covers the last 30 days.

A post enters the page when it is published, public, without an invite link, and its author qualifies as for Discover. Every page applies Discover’s rule again: a post that was deleted, hidden after reports, left out by the instance’s media limits, or whose author is no longer public or is flagged as a spammer or for suspicious activity, is left out, and so is every post between accounts that block each other. The newest page is shared by every reader and can be up to 5 seconds old.

The pagination signal is in the response headers, as in Get home feed. X-Next-Before is the before value for the next page; a page can hold fewer posts than limit, or none, while it is present. An empty page without it is the end. An unknown or disabled category returns an empty page without the header.

FieldTypeDescription
category_idsnowflakeThe ID of the category
FieldTypeDescription
before?snowflakeThe cursor selecting posts published before this post ID
limit?integerThe maximum number of posts to return (1-50, default 25)
StatusBodyCondition
200array of post objectsThe page was returned, possibly empty; X-Next-Before says where the next page starts
403error responseThe caller’s feature_feed_categories limit is 0 and the request returns FEATURE_TEMPORARILY_DISABLED

20 requests per 10 seconds for each authenticated user, on the feed:category:posts bucket.

GET/v1/feed/topics

Returns the live feed topics of the categories shown in the caller’s country, as an array of topic objects with the newest first. The country comes from the request address; when it is unknown, only the topics of categories shown everywhere are returned. A topic published elsewhere appears within 30 seconds.

StatusBodyCondition
200array of topic objectsThe topics were returned, possibly none
403error responseThe caller’s feature_feed_topics limit is 0 and the request returns FEATURE_TEMPORARILY_DISABLED

10 requests per 10 seconds for each authenticated user, on the feed:topics bucket.

GET/v1/feed/topics/{topic_id}

Returns a live or ended topic as a topic object. An ended topic is read only.

FieldTypeDescription
topic_idsnowflakeThe ID of the topic
StatusBodyCondition
200topic objectThe topic was returned
403error responseThe caller’s feature_feed_topics limit is 0 and the request returns FEATURE_TEMPORARILY_DISABLED
404error responseThe topic is unknown, removed or not started yet, and the request returns NOT_FOUND

10 requests per 10 seconds for each authenticated user, on the feed:topics bucket.

GET/v1/feed/topics/{topic_id}/posts

Returns one page of the public posts filed under a topic, as an array of post objects. A post enters the page on the terms of a category page, and every page applies Discover’s rule again in the same way. sort=top orders posts by Discover’s trend score and pages by offset, down to the 1,000th post; sort=new puts the newest first and pages by before. The first page of each order is shared by every reader and can be up to 5 seconds old.

The pagination signal is in the response headers: X-Next-Offset is the offset value for the next top page and X-Next-Before the before value for the next new page. A page can hold fewer posts than limit, or none, while its header is present; an empty page without it is the end. An unknown, removed or not yet started topic returns an empty page without a header.

FieldTypeDescription
topic_idsnowflakeThe ID of the topic
FieldTypeDescription
sort?stringtop (default) or new
before?snowflakenew only: the cursor selecting posts published before this post ID
offset?integertop only: the posts to skip (0-1,000, default 0)
limit?integerThe maximum number of posts to return (1-50, default 25)
StatusBodyCondition
200array of post objectsThe page was returned, possibly empty; X-Next-Offset or X-Next-Before says where the next page starts
403error responseThe caller’s feature_feed_topics limit is 0 and the request returns FEATURE_TEMPORARILY_DISABLED

20 requests per 10 seconds for each authenticated user, on the feed:topic:posts bucket.

GET/v1/feed/suggestions

Returns a feed suggestions object with up to five popular accounts for the caller to follow and up to five popular Discovery communities for the caller to join. While the caller’s feature_feed_suggestions limit is 0, the route returns 403 FEATURE_TEMPORARILY_DISABLED.

The instance keeps a list of up to 100 accounts and a list of up to 100 communities, and rebuilds both about once a minute. The account list takes the accounts with the most followers, each with at least as many as the feed_suggestions_min_followers limit of the default rule (at least 1), that qualify: not a bot or system account; a verified email address, a claimed account at least 24 hours old and the date of birth of an adult; not banned, deleted or flagged as a spammer or for suspicious activity; a feature_feed limit other than 0; a profile privacy level that reads as EVERYONE; and at least half of its 100 newest followers with a verified email address and a claimed, unflagged account at least 72 hours old. The community list takes the approved Discovery listings with the most members whose NSFW level is 0 and that lack the INVITES_DISABLED guild feature. It is empty while the instance has Discovery disabled or no search backend.

Each request draws up to 20 entries of each list at random and returns the first five that suit the caller, so two requests seldom return the same entries; keep one answer while the view stays open. users leaves out the caller, the accounts the caller follows and the accounts that block the caller or that the caller blocks. It stays empty unless the caller has a verified email address, a claimed account at least 24 hours old and no spammer or suspicious activity flag, and is an adult by date of birth; an account without a date of birth receives no accounts. guilds leaves out the communities the caller is in and does not depend on the caller’s standing.

A new ban, spammer or suspicious activity flag, or a deletion removes an account from the list at once. Any other change to a listed account or community reaches the lists within about a minute and a half. When the lists have not been rebuilt for 10 minutes, both arrays are empty. A listed account can be followed through Follow user without post_id, and a listed community joined through Join discovery guild.

StatusBodyCondition
200feed suggestions objectThe suggestions were returned, possibly with empty arrays
403error responseThe caller’s feature_feed_suggestions limit is 0 and the request returns FEATURE_TEMPORARILY_DISABLED

10 requests per 10 seconds for each authenticated user, on the feed:suggestions bucket.

GET/v1/users/{user_id}/posts

Returns one page of an account’s posts, as an array of post objects with the newest post first. A caller that sees the full profile under Get user profile gets every post, reads the next page with the last post’s id as before, and stops at an empty page; no X-Next-Before is sent.

A caller that sees the account only as a public profile gets its public posts only, and spends the daily stranger allowance described under Get user profile. One request reads up to three pages of the account’s posts to fill the page, so a page can come back short or empty while older public posts remain. X-Next-Before is then the before value for the next page; when it is absent, read the next page with the last post’s id, and an empty page without it is the end.

FieldTypeDescription
user_idsnowflakeThe ID of the author
FieldTypeDescription
before?snowflakeThe cursor selecting posts published before this post ID
limit?integerThe maximum number of posts to return (1-50, default 25)
StatusBodyCondition
200array of post objectsThe page was returned, possibly empty; X-Next-Before says where a public profile’s next page starts
403error responseThe caller may not read the author’s posts and the request returns MISSING_ACCESS
404error responseNo account has the ID and the request returns UNKNOWN_USER

30 requests per 10 seconds for each authenticated user, on the feed:user_posts bucket.

PUT/v1/feed/posts/{post_id}/like

Likes a post and returns 204 with an empty body. Liking a post the caller already likes changes nothing. The post’s like_count counts each account once, and liked is true in the caller’s post objects.

FieldTypeDescription
post_idsnowflakeThe ID of the post
StatusBodyCondition
204emptyThe caller likes the post
403error responseThe caller may not read the author’s posts and the request returns MISSING_ACCESS
4041error responseNo post has the ID and the request returns UNKNOWN_FEED_POST

1 Also returned when the caller and the author block each other

30 requests per 10 seconds for each authenticated user, on the feed:post:like bucket.

DELETE/v1/feed/posts/{post_id}/like

Takes the caller’s like off a post and returns 204 with an empty body. Without a like it changes nothing. Only the caller’s own like is read: no access to the post is needed, and a block or a deleted post does not keep the like in place.

FieldTypeDescription
post_idsnowflakeThe ID of the post
StatusBodyCondition
204emptyThe caller does not like the post

30 requests per 10 seconds for each authenticated user, on the feed:post:unlike bucket.

PUT/v1/feed/posts/{post_id}/poll/vote

Casts the caller’s vote for one option of a post’s poll and returns 200 with the post object, whose poll now shows the votes. An account votes once and cannot change or take back its vote, and the author of the post cannot vote. A vote also counts toward the post’s place in Discover, with a quarter of a like’s weight. On a public post, a caller who could not read the author’s posts without the public flag needs a verified email address to vote.

FieldTypeDescription
post_idsnowflakeThe ID of the post
FieldTypeDescription
optionintegerThe index of the chosen option in options (0-3)
StatusBodyCondition
200post objectThe vote was cast
4001error responseThe body is invalid, the post has no poll, or it has no option of the index
4032error responseThe caller may not read the author’s posts and the request returns MISSING_ACCESS
4043error responseNo post has the ID and the request returns UNKNOWN_FEED_POST
409error responseThe caller already voted or voting has ended, and the request returns CONFLICT

1 A post without a poll or an index past its options returns INVALID_FORM_BODY with the field code INVALID_FORMAT

2 The author of the post receives MISSING_PERMISSIONS. On a public post, a caller without a verified email address who could not read the author’s posts otherwise receives EMAIL_VERIFICATION_REQUIRED

3 Also returned when the caller and the author block each other

10 requests per 10 seconds for each authenticated user, on the feed:poll:vote bucket.

POST/v1/feed/views

Counts the posts the caller saw and returns 204 with an empty body. Each view carries the view_token of a post object the caller received. A ticket of another account, a forged or an expired ticket is dropped without an error. A view adds its count, held to 1-20 and to one view per 3 seconds since its view_token was handed out plus one, and a post named twice in a batch adds its larger count once. A batch adds at most 60 views in all, in its order. The caller passes one batch a minute, so an account adds at most 20 views to a post a minute. The views reach view_count a minute or two later. While the instance’s feature_feed_views limit is 0, the route returns 403 and post objects carry neither view_count nor view_token.

FieldTypeDescription
viewsarray[view input object]Posts seen since the last batch (1-100)
StatusBodyCondition
204emptyThe batch was taken; views with an invalid ticket were dropped
400error responseThe body is invalid or holds more than 100 views, and the request returns INVALID_FORM_BODY
403error responseThe instance’s feature_feed_views limit is 0 and the request returns FEATURE_TEMPORARILY_DISABLED
4291error responseThe caller’s batch of this minute was already taken, or the bucket is spent, and the request returns RATE_LIMITED

1 For the minute, retry_after counts down to its end. A batch in which no ticket is valid does not take the minute

6 requests per 60 seconds for each authenticated user, on the feed:views bucket, outside the global limit; one batch a minute passes.

GET/v1/feed/posts/{post_id}/likes

Returns one page of the accounts that like a post, as an array of partial user objects by ascending user ID. Read the next page with the last account’s id as after, and stop at a page shorter than limit.

FieldTypeDescription
post_idsnowflakeThe ID of the post
FieldTypeDescription
after?snowflakeThe cursor selecting accounts with a user ID above this one
limit?integerThe maximum number of accounts to return (1-100, default 50)
StatusBodyCondition
200array of partial user objectsThe page was returned, possibly empty
403error responseThe caller may not read the author’s posts and the request returns MISSING_ACCESS
4041error responseNo post has the ID and the request returns UNKNOWN_FEED_POST

1 Also returned when the caller and the author block each other

20 requests per 10 seconds for each authenticated user, on the feed:post:likers bucket.

GET/v1/feed/posts/{post_id}/comments

Returns one page of the top-level comments on a post, as an array of comment objects with the newest comment first. Each carries its reply_count; read its replies through List replies. Read the next page with the last comment’s id as before, and stop at a page shorter than limit.

FieldTypeDescription
post_idsnowflakeThe ID of the post
FieldTypeDescription
before?snowflakeThe cursor selecting comments written before this comment ID
limit?integerThe maximum number of comments to return (1-50, default 25)
StatusBodyCondition
200array of comment objectsThe page was returned, possibly empty
403error responseThe caller may not read the author’s posts and the request returns MISSING_ACCESS
4041error responseNo post has the ID and the request returns UNKNOWN_FEED_POST

1 Also returned when the caller and the author block each other

40 requests per 10 seconds for each authenticated user, on the feed:comments:list bucket.

POST/v1/feed/posts/{post_id}/comments

Adds a comment to a post and returns 200 with the comment object. The text is checked like message content. A post takes up to max_comments_per_post comments and replies together (10,000 by default); deleting a comment frees its place.

On a public post of another account, the caller writes at most 20 comments and replies together while the post is in its first 72 hours (72 hours from the caller’s first comment after that). A caller who could not read the author’s posts without the public flag also needs a verified email address, a claimed account and an account at least 24 hours old.

FieldTypeDescription
post_idsnowflakeThe ID of the post
FieldTypeDescription
contentstringThe plain text of the comment (1-1,000 characters)
StatusBodyCondition
200comment objectThe comment was added
4001error responseThe body is invalid or the post is full
4033error responseThe caller may not read the author’s posts and the request returns MISSING_ACCESS
4042error responseNo post has the ID and the request returns UNKNOWN_FEED_POST
4294error responseThe caller already wrote 20 comments and replies on this public post and the request returns RATE_LIMITED

1 A post with max_comments_per_post comments returns MAX_FEED_COMMENTS, with the limit in max_comments_per_post

2 Also returned when the caller and the author block each other

3 A blocked text returns CONTENT_BLOCKED. On a public post, a caller without a verified email address who could not read the author’s posts otherwise receives EMAIL_VERIFICATION_REQUIRED, and a claimed, verified account younger than 24 hours MISSING_ACCESS

4 retry_after is the time until the count resets

5 requests per 10 seconds for each authenticated user, on the feed:comment:create bucket, and 500 requests a day on the feed:comment:create:daily bucket.

DELETE/v1/feed/posts/{post_id}/comments/{comment_id}

Deletes a top-level comment with its replies and returns 204 with an empty body. The post’s comment_count drops by one plus the number of replies. The comment’s author and the post’s author may delete it, whatever blocks lie between them.

FieldTypeDescription
post_idsnowflakeThe ID of the post
comment_idsnowflakeThe ID of the comment
StatusBodyCondition
204emptyThe comment was deleted
403error responseThe caller wrote neither the comment nor the post and the request returns MISSING_PERMISSIONS
4041error responseNo post has the ID and the request returns UNKNOWN_FEED_POST

1 A post with no comment of the ID returns UNKNOWN_FEED_COMMENT

10 requests per 10 seconds for each authenticated user, on the feed:comment:delete bucket.

PUT/v1/feed/posts/{post_id}/comments/{comment_id}/like

Likes a top-level comment and returns 204 with an empty body. Liking it again changes nothing. Its like_count counts each account once, and liked is true in the caller’s comment objects. The caller needs the access Like post needs; an author may like their own comment. The author of the top-level comment receives a comment_like notification while the instance’s feature_feed_comment_like_notifications limit is not 0; a like of one’s own comment notifies nobody.

FieldTypeDescription
post_idsnowflakeThe ID of the post
comment_idsnowflakeThe ID of the comment
StatusBodyCondition
204emptyThe caller likes the comment
403error responseThe caller may not read the author’s posts and the request returns MISSING_ACCESS
4041error responseNo post has the ID and the request returns UNKNOWN_FEED_POST

1 A post with no such comment returns UNKNOWN_FEED_COMMENT (a reply ID named as a comment too), and so does a comment whose author and the caller block each other

30 requests per 10 seconds for each authenticated user, on the feed:comment:like bucket.

DELETE/v1/feed/posts/{post_id}/comments/{comment_id}/like

Takes the caller’s like off a top-level comment and returns 204 with an empty body, also when the caller did not like it. Only the caller’s own like is read: no access, visibility or block check applies.

FieldTypeDescription
post_idsnowflakeThe ID of the post
comment_idsnowflakeThe ID of the comment
StatusBodyCondition
204emptyThe caller does not like the comment

30 requests per 10 seconds for each authenticated user, on the feed:comment:unlike bucket.

GET/v1/feed/posts/{post_id}/comments/{comment_id}/replies

Returns one page of the replies to a top-level comment, as an array of comment objects with the newest reply first. Read the next page with the last reply’s id as before, and stop at a page shorter than limit. On a public post of another account, replies of accounts that block the caller or that the caller blocks are left out, as in List comments.

FieldTypeDescription
post_idsnowflakeThe ID of the post
comment_idsnowflakeThe ID of the top-level comment
FieldTypeDescription
before?snowflakeThe cursor selecting replies written before this reply ID
limit?integerThe maximum number of replies to return (1-50, default 25)
StatusBodyCondition
200array of comment objectsThe page was returned, possibly empty
403error responseThe caller may not read the author’s posts and the request returns MISSING_ACCESS
4041error responseNo post has the ID and the request returns UNKNOWN_FEED_POST

1 Also returned when the caller and the author block each other. A post with no top-level comment of the ID returns UNKNOWN_FEED_COMMENT, and so does a reply ID

40 requests per 10 seconds for each authenticated user, on the feed:comments:list bucket, shared with List comments.

POST/v1/feed/posts/{post_id}/comments/{comment_id}/replies

Adds a reply to a top-level comment and returns 200 with the comment object of the reply. Replies go one level deep, so a reply cannot be answered. A reply follows the rules of Create comment: the text check, the access rules on a public post, the allowance of 20 comments and replies on another account’s public post, and max_comments_per_post, which counts comments and replies together. A reply raises the post’s comment_count and the comment’s reply_count by one.

The comment’s author receives a comment_reply notification while the instance’s feature_feed_reply_notifications limit is not 0.

FieldTypeDescription
post_idsnowflakeThe ID of the post
comment_idsnowflakeThe ID of the top-level comment
FieldTypeDescription
contentstringThe plain text of the reply (1-1,000 characters)
StatusBodyCondition
200comment objectThe reply was added
4001error responseThe body is invalid or the post is full
4033error responseThe caller may not read the author’s posts and the request returns MISSING_ACCESS
4042error responseNo post has the ID and the request returns UNKNOWN_FEED_POST
4294error responseThe caller already wrote 20 comments and replies on this public post and the request returns RATE_LIMITED

1 A post with max_comments_per_post comments and replies returns MAX_FEED_COMMENTS, with the limit in max_comments_per_post

2 Also returned when the caller and the author block each other. A post with no top-level comment of the ID returns UNKNOWN_FEED_COMMENT, and so does a reply ID

3 As for Create comment

4 retry_after is the time until the count resets

5 requests per 10 seconds for each authenticated user, on the feed:comment:create bucket, and 500 requests a day on the feed:comment:create:daily bucket, both shared with Create comment.

DELETE/v1/feed/posts/{post_id}/comments/{comment_id}/replies/{reply_id}

Deletes a reply and returns 204 with an empty body. The post’s comment_count and the comment’s reply_count drop by one. The reply’s author and the post’s author may delete it, whatever blocks lie between them.

FieldTypeDescription
post_idsnowflakeThe ID of the post
comment_idsnowflakeThe ID of the top-level comment the reply answers
reply_idsnowflakeThe ID of the reply
StatusBodyCondition
204emptyThe reply was deleted
403error responseThe caller wrote neither the reply nor the post and the request returns MISSING_PERMISSIONS
4041error responseNo post has the ID and the request returns UNKNOWN_FEED_POST

1 A comment with no reply of the ID returns UNKNOWN_FEED_COMMENT

10 requests per 10 seconds for each authenticated user, on the feed:comment:delete bucket, shared with Delete comment.

PUT/v1/feed/posts/{post_id}/comments/{comment_id}/replies/{reply_id}/like

Likes a reply and returns 204 with an empty body. Liking it again changes nothing. Its like_count counts each account once, and liked is true in the caller’s comment objects. The caller needs the access Like post needs; an author may like their own reply. The author of the reply receives a comment_like notification while the instance’s feature_feed_comment_like_notifications limit is not 0; a like of one’s own reply notifies nobody.

FieldTypeDescription
post_idsnowflakeThe ID of the post
comment_idsnowflakeThe ID of the top-level comment the reply answers
reply_idsnowflakeThe ID of the reply
StatusBodyCondition
204emptyThe caller likes the reply
403error responseThe caller may not read the author’s posts and the request returns MISSING_ACCESS
4041error responseNo post has the ID and the request returns UNKNOWN_FEED_POST

1 A post with no such reply returns UNKNOWN_FEED_COMMENT, and so does a reply whose author and the caller block each other

30 requests per 10 seconds for each authenticated user, on the feed:comment:like bucket, shared with Like comment.

DELETE/v1/feed/posts/{post_id}/comments/{comment_id}/replies/{reply_id}/like

Takes the caller’s like off a reply and returns 204 with an empty body, also when the caller did not like it. Only the caller’s own like is read: no access, visibility or block check applies.

FieldTypeDescription
post_idsnowflakeThe ID of the post
comment_idsnowflakeThe ID of the top-level comment the reply answers
reply_idsnowflakeThe ID of the reply
StatusBodyCondition
204emptyThe caller does not like the reply

30 requests per 10 seconds for each authenticated user, on the feed:comment:unlike bucket, shared with Unlike comment.

GET/v1/feed/notifications

Returns one page of the likes, comments, replies and follows aimed at the caller, as an array of notification objects with the newest id first. Notifications are kept for 90 days. Liking or commenting on your own post, or replying to your own comment, raises none.

A page can hold fewer notifications than limit while more remain. Read the next page with the last notification’s id as before, and stop at an empty page. Notifications of a deleted post are left out.

New and updated notifications also arrive as FEED_NOTIFICATION_CREATE on the caller’s sessions, at most one a minute; an update held back by that limit follows when the minute ends.

FieldTypeDescription
before?snowflakeThe cursor selecting notifications with an ID below this notification ID
limit?integerThe maximum number of notifications to return (1-50, default 25)
StatusBodyCondition
200array of notification objectsThe page was returned, possibly empty

20 requests per 10 seconds for each authenticated user, on the feed:notifications bucket.

POST/v1/feed/notifications/ack

Moves the caller’s notification read cursor to read_at and marks the notifications counted up to unread_seq read. The cursor only moves forward and never past the server’s time. A notification counted after the sequence the session sent stays unread, so a push that crossed the request is not lost. All of the caller’s sessions, this one included, receive FEED_NOTIFICATION_ACK with the response body.

FieldTypeDescription
read_at1ISO8601 timestampWhen the caller saw their notifications
unread_seqintegerThe newest unread sequence the session has seen (READY, pushes and acks), 0 or more
unread_epochintegerThe epoch of that sequence, 0 or more

1 A server time: the newest updated_at shown or the time the list was served, never the device clock

FieldTypeDescription
last_read_atISO8601 timestampThe read cursor after the request
unread_seq1integerThe unread sequence the read covers
unread_epochintegerThe epoch of the server’s current sequence

1 The greater of the sequence an earlier read covered and the lower of unread_seq and the server’s current sequence; unread_seq counts as 0 when unread_epoch is not the current epoch

StatusBodyCondition
200response bodyThe read was taken; a cursor that would move back stays where it was
400error responseA field is missing, read_at is not ISO8601 or a number is negative; returns INVALID_FORM_BODY
403error responseThe caller’s feature_feed limit is 0 and the request returns FEATURE_TEMPORARILY_DISABLED

10 requests per 10 seconds for each authenticated user, on the feed:notifications:ack bucket.

Deleting a post deletes its likes, poll votes, comments and replies and their likes, and its video from Bunny Stream. Deleting an account deletes its posts and their media, its videos that are not posts yet, its likes, comment likes, poll votes, comments and replies, and its notifications, and the posts and comments it liked, voted in or commented on stop counting them. Its data harvest lists in feed.json its posts, each with its text, timestamps, whether it is public, its poll and its media URLs, its comments and replies with their post and text (a reply with the parent_id of its comment), the posts it likes, its poll votes, the comments and replies it likes (comment_likes, a reply with the parent_id of its comment), and its notifications of the last 90 days with the IDs of their accounts. A video is listed by the URL of its HLS playlist, signed for the 7 days the archive can be downloaded, or null while the instance has no Bunny Stream settings.