Skip to content

Social

Base URL: /v1

All authenticated endpoints require a valid Bearer token in the Authorization header.


GET /v1/feed

Returns published posts from followed users and the authenticated user’s own posts, ordered by created_at descending. Each post includes the author object, media attachments, poll data, a reactions summary grouped by type, and a total comments count.

Query Parameters

ParameterTypeDefaultDescription
cursorstringOpaque cursor for pagination (optional)
limitint20Number of posts to return. Max 50.

Response 200 OK

{
"posts": [
{
"id": "post_abc123",
"user_id": "user_xyz",
"title": "Hello world",
"content": "My first post on the platform!",
"status": "published",
"is_sensitive": false,
"is_ai_generated": false,
"created_at": "2026-03-28T14:30:00Z",
"updated_at": "2026-03-28T14:30:00Z",
"author": {
"id": "user_xyz",
"username": "janedoe",
"first_name": "Jane",
"last_name": "Doe",
"avatar_url": "https://cdn.example.com/avatars/janedoe.jpg",
"verified": true
},
"media": [
{
"id": "media_001",
"url": "https://cdn.example.com/uploads/photo.jpg",
"type": "image"
}
],
"poll": [],
"reactions_summary": {
"like": 12,
"love": 3,
"fire": 1
},
"comments_count": 5
}
],
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wMy0yOCJ9"
}

POST /v1/posts

Creates a new post. If a poll object is provided, an associated poll record is created and linked to the post.

Request Body

FieldTypeRequiredDefaultDescription
titlestringNonullPost title.
contentstringNonullPost text content.
statusstringNo"published"One of: draft, published.
is_sensitivebooleanNofalseFlag content as sensitive / NSFW.
is_ai_generatedbooleanNofalseFlag content as AI-generated.
pollobjectNonullPoll object. See shape below.

Poll Object

FieldTypeRequiredDescription
questionstringYesThe poll question.
optionsstring[]YesArray of answer option strings.

Example Request

{
"title": "Weekend plans?",
"content": "What is everyone doing this weekend?",
"status": "published",
"is_sensitive": false,
"is_ai_generated": false,
"poll": {
"question": "What are you up to?",
"options": ["Hiking", "Reading", "Coding", "Sleeping"]
}
}

Response 201 Created

{
"post": {
"id": "post_def456",
"user_id": "user_xyz",
"title": "Weekend plans?",
"content": "What is everyone doing this weekend?",
"status": "published",
"is_sensitive": false,
"is_ai_generated": false,
"created_at": "2026-03-29T10:00:00Z",
"updated_at": "2026-03-29T10:00:00Z",
"author": {
"id": "user_xyz",
"username": "janedoe",
"first_name": "Jane",
"last_name": "Doe",
"avatar_url": "https://cdn.example.com/avatars/janedoe.jpg",
"verified": true
},
"media": [],
"poll": [
{
"id": "poll_001",
"post_id": "post_def456",
"question": "What are you up to?",
"options": ["Hiking", "Reading", "Coding", "Sleeping"],
"votes": {}
}
]
}
}

GET /v1/posts/{id}

Public endpoint. Returns a single post with its author, media attachments, poll data, the full reactions array, and the first 50 comments with their authors.

Path Parameters

ParameterTypeDescription
idstringThe post ID.

Response 200 OK

{
"post": {
"id": "post_abc123",
"user_id": "user_xyz",
"title": "Hello world",
"content": "My first post on the platform!",
"status": "published",
"is_sensitive": false,
"is_ai_generated": false,
"created_at": "2026-03-28T14:30:00Z",
"updated_at": "2026-03-28T14:30:00Z",
"author": {
"id": "user_xyz",
"username": "janedoe",
"first_name": "Jane",
"last_name": "Doe",
"avatar_url": "https://cdn.example.com/avatars/janedoe.jpg",
"verified": true
},
"media": [],
"poll": [],
"reactions": [
{ "post_id": "post_abc123", "user_id": "user_aaa", "type": "like" },
{ "post_id": "post_abc123", "user_id": "user_bbb", "type": "love" }
],
"comments": [
{
"id": "comment_001",
"post_id": "post_abc123",
"user_id": "user_aaa",
"content": "Great post!",
"parent_id": null,
"created_at": "2026-03-28T15:00:00Z",
"author": {
"id": "user_aaa",
"username": "alice",
"first_name": "Alice",
"last_name": "Smith",
"avatar_url": "https://cdn.example.com/avatars/alice.jpg"
}
}
]
}
}

Response 404 Not Found

{
"error": "Post not found"
}

PATCH /v1/posts/{id}

Path Parameters

ParameterTypeDescription
idstringThe post ID.

Request Body

FieldTypeRequiredDescription
titlestringNoUpdated post title.
contentstringNoUpdated post text content.
statusstringNoOne of: draft, published.
is_sensitivebooleanNoUpdated sensitive flag.

Example Request

{
"content": "Updated post content.",
"is_sensitive": true
}

Response 200 OK

Returns the updated post object (same shape as Create Post response).


DELETE /v1/posts/{id}

Path Parameters

ParameterTypeDescription
idstringThe post ID.

Response 200 OK

{
"message": "Post deleted"
}

GET /v1/drafts

Returns the authenticated user’s draft posts, ordered by created_at descending.

Response 200 OK

{
"posts": [
{
"id": "post_draft001",
"user_id": "user_xyz",
"title": "Work in progress",
"content": "Still writing this...",
"status": "draft",
"is_sensitive": false,
"is_ai_generated": false,
"created_at": "2026-03-27T08:00:00Z",
"updated_at": "2026-03-27T09:15:00Z",
"author": {
"id": "user_xyz",
"username": "janedoe",
"first_name": "Jane",
"last_name": "Doe",
"avatar_url": "https://cdn.example.com/avatars/janedoe.jpg",
"verified": true
},
"media": [],
"poll": []
}
]
}

POST /v1/follow

Sends a follow request or immediately follows the target user. The server checks the target user’s privacy settings (follower_approval). If approval is required, the follow status is set to "requested"; otherwise it is set to "following". Self-follows are rejected.

Request Body

FieldTypeRequiredDescription
user_idstringYesThe ID of the user to follow.

Example Request

{
"user_id": "user_target123"
}

Response 201 Created (immediate follow)

{
"follow": {
"follower_id": "user_xyz",
"following_id": "user_target123",
"status": "following"
}
}

Response 201 Created (approval required)

{
"follow": {
"follower_id": "user_xyz",
"following_id": "user_target123",
"status": "requested"
}
}

POST /v1/follow/accept

Accepts a pending follow request. Updates the follow record status from "requested" to "following".

Request Body

FieldTypeRequiredDescription
follower_idstringYesThe ID of the user whose request to accept.

Example Request

{
"follower_id": "user_requester456"
}

Response 200 OK

{
"follow": {
"follower_id": "user_requester456",
"following_id": "user_xyz",
"status": "following"
}
}

POST /v1/follow/decline

Declines and deletes a pending follow request.

Request Body

FieldTypeRequiredDescription
follower_idstringYesThe ID of the user whose request to decline.

Example Request

{
"follower_id": "user_requester456"
}

Response 200 OK

{
"message": "Follow request declined"
}

DELETE /v1/follow/{userId}

Unfollows the specified user.

Path Parameters

ParameterTypeDescription
userIdstringThe ID of the user to unfollow.

Response 200 OK

{
"message": "Unfollowed"
}

POST /v1/posts/{id}/reactions

Adds or updates a reaction on a post. Upserts on the composite key (post_id, user_id) — if the user already reacted, the type is updated.

Path Parameters

ParameterTypeDescription
idstringThe post ID.

Request Body

FieldTypeRequiredDescription
typestringYesReaction type (e.g. like, love, fire).

Example Request

{
"type": "like"
}

Response 201 Created

{
"reaction": {
"post_id": "post_abc123",
"user_id": "user_xyz",
"type": "like"
}
}

DELETE /v1/posts/{id}/reactions

Removes the authenticated user’s reaction from the specified post.

Path Parameters

ParameterTypeDescription
idstringThe post ID.

Response 200 OK

{
"message": "Reaction removed"
}

GET /v1/posts/{id}/comments

Public endpoint. Returns comments for a post in ascending created_at order. Supports cursor-based pagination.

Path Parameters

ParameterTypeDescription
idstringThe post ID.

Query Parameters

ParameterTypeDefaultDescription
cursorstringCursor value (created_at timestamp) for pagination.
limitint30Number of comments to return. Max 100.

Response 200 OK

{
"comments": [
{
"id": "comment_001",
"post_id": "post_abc123",
"user_id": "user_aaa",
"content": "Great post!",
"parent_id": null,
"created_at": "2026-03-28T15:00:00Z",
"author": {
"id": "user_aaa",
"username": "alice",
"first_name": "Alice",
"last_name": "Smith",
"avatar_url": "https://cdn.example.com/avatars/alice.jpg"
}
},
{
"id": "comment_002",
"post_id": "post_abc123",
"user_id": "user_bbb",
"content": "Thanks, Alice!",
"parent_id": "comment_001",
"created_at": "2026-03-28T15:05:00Z",
"author": {
"id": "user_bbb",
"username": "bob",
"first_name": "Bob",
"last_name": "Jones",
"avatar_url": "https://cdn.example.com/avatars/bob.jpg"
}
}
],
"next_cursor": "2026-03-28T15:05:00Z"
}

POST /v1/posts/{id}/comments

Adds a comment to a post. Supply parent_id to create a threaded reply.

Path Parameters

ParameterTypeDescription
idstringThe post ID.

Request Body

FieldTypeRequiredDescription
contentstringYesThe comment text.
parent_idstringNoParent comment ID for threaded replies.

Example Request

{
"content": "This is a reply!",
"parent_id": "comment_001"
}

Response 201 Created

{
"comment": {
"id": "comment_003",
"post_id": "post_abc123",
"user_id": "user_xyz",
"content": "This is a reply!",
"parent_id": "comment_001",
"created_at": "2026-03-28T16:00:00Z",
"author": {
"id": "user_xyz",
"username": "janedoe",
"first_name": "Jane",
"last_name": "Doe",
"avatar_url": "https://cdn.example.com/avatars/janedoe.jpg"
}
}
}

DELETE /v1/comments/{id}

Path Parameters

ParameterTypeDescription
idstringThe comment ID.

Response 200 OK

{
"message": "Comment deleted"
}

GET /v1/bookmarks

Returns the authenticated user’s bookmarks in descending order. Supports filtering by bookmarkable type and cursor-based pagination.

Query Parameters

ParameterTypeDefaultDescription
typestringFilter by type. One of: post, course, product, event.
cursorstringOpaque cursor for pagination.
limitint20Number of bookmarks to return. Max 50.

Response 200 OK

{
"bookmarks": [
{
"id": "bookmark_001",
"user_id": "user_xyz",
"bookmarkable_id": "post_abc123",
"bookmarkable_type": "post",
"created_at": "2026-03-29T12:00:00Z"
},
{
"id": "bookmark_002",
"user_id": "user_xyz",
"bookmarkable_id": "course_456",
"bookmarkable_type": "course",
"created_at": "2026-03-28T08:00:00Z"
}
],
"next_cursor": "eyJjcmVhdGVkX2F0IjoiMjAyNi0wMy0yOCJ9"
}

POST /v1/bookmarks

Bookmarks an item. Upserts — if the bookmark already exists, it is returned as-is.

Request Body

FieldTypeRequiredDescription
bookmarkable_idstringYesThe ID of the item to bookmark.
bookmarkable_typestringYesOne of: post, course, product, event.

Example Request

{
"bookmarkable_id": "post_abc123",
"bookmarkable_type": "post"
}

Response 201 Created

{
"bookmark": {
"id": "bookmark_003",
"user_id": "user_xyz",
"bookmarkable_id": "post_abc123",
"bookmarkable_type": "post",
"created_at": "2026-03-30T09:00:00Z"
}
}

DELETE /v1/bookmarks/{id}

Path Parameters

ParameterTypeDescription
idstringThe bookmark ID.

Response 200 OK

{
"message": "Bookmark deleted"
}

POST /v1/polls/{id}/vote

Casts a vote on a poll. If the user has previously voted, the old vote is removed before recording the new one. Votes are stored as a JSON object where each key is the option index and the value is an array of user IDs who selected that option.

Path Parameters

ParameterTypeDescription
idstringThe poll ID.

Request Body

FieldTypeRequiredDescription
option_indexintYesZero-based index of the selected option.

Example Request

{
"option_index": 2
}

Response 200 OK

{
"poll": {
"id": "poll_001",
"post_id": "post_def456",
"question": "What are you up to?",
"options": ["Hiking", "Reading", "Coding", "Sleeping"],
"votes": {
"0": ["user_aaa"],
"1": ["user_bbb", "user_ccc"],
"2": ["user_xyz"],
"3": []
}
}
}

GET /v1/stories

Returns active stories from followed users and the authenticated user’s own stories. Stories expire after 24 hours and are automatically filtered out.

Response 200 OK

{
"stories": [
{
"id": "story_001",
"user_id": "user_aaa",
"created_at": "2026-03-30T06:00:00Z",
"expires_at": "2026-03-31T06:00:00Z",
"author": {
"id": "user_aaa",
"username": "alice",
"first_name": "Alice",
"last_name": "Smith",
"avatar_url": "https://cdn.example.com/avatars/alice.jpg"
},
"story_frames": [
{
"id": "frame_001",
"media_url": "https://cdn.example.com/stories/frame1.jpg",
"media_type": "image",
"duration": 5
}
]
}
]
}

POST /v1/stories

Creates a new story with optional frames. Each frame represents a single slide in the story.

Request Body

FieldTypeRequiredDescription
framesarrayNoArray of frame objects.

Frame Object

FieldTypeRequiredDefaultDescription
media_urlstringYesURL of the media asset.
media_typestringYesType of media (e.g. image, video).
durationintNo5Display duration in seconds.

Example Request

{
"frames": [
{
"media_url": "https://cdn.example.com/stories/morning.jpg",
"media_type": "image",
"duration": 5
},
{
"media_url": "https://cdn.example.com/stories/clip.mp4",
"media_type": "video",
"duration": 15
}
]
}

Response 201 Created

{
"story": {
"id": "story_002",
"user_id": "user_xyz",
"created_at": "2026-03-30T10:00:00Z",
"expires_at": "2026-03-31T10:00:00Z",
"story_frames": [
{
"id": "frame_010",
"media_url": "https://cdn.example.com/stories/morning.jpg",
"media_type": "image",
"duration": 5
},
{
"id": "frame_011",
"media_url": "https://cdn.example.com/stories/clip.mp4",
"media_type": "video",
"duration": 15
}
]
}
}

GET /v1/stories/{id}

Returns a single story with all its frames.

Path Parameters

ParameterTypeDescription
idstringThe story ID.

Response 200 OK

{
"story": {
"id": "story_001",
"user_id": "user_aaa",
"created_at": "2026-03-30T06:00:00Z",
"expires_at": "2026-03-31T06:00:00Z",
"story_frames": [
{
"id": "frame_001",
"media_url": "https://cdn.example.com/stories/frame1.jpg",
"media_type": "image",
"duration": 5
}
]
}
}

Response 404 Not Found

{
"error": "Story not found"
}

DELETE /v1/stories/{id}

Deletes a story. All associated frames are deleted first, then the story record itself is removed.

Path Parameters

ParameterTypeDescription
idstringThe story ID.

Response 200 OK

{
"message": "Story deleted"
}

POST /v1/stories/{id}/view

Records that the authenticated user has viewed the story. Upserts — duplicate views by the same user are silently ignored.

Path Parameters

ParameterTypeDescription
idstringThe story ID.

Response 200 OK

{
"message": "View recorded"
}