Social API
API endpoints for social features - following users, activity feeds, likes, and comments.
The Social API provides endpoints for managing social interactions between users, including following, activity feeds, likes, and comments.
Base URL
/api/v1/socialAuthentication
All social endpoints require authentication via session cookie or API key.
Following
Follow a User
POST /api/v1/social
Follow another user to see their activity in your feed.
Request:
{
"followingId": "uuid"
}Response:
{
"success": true,
"data": {
"follower_id": "uuid",
"following_id": "uuid",
"created_at": "2024-01-15T10:30:00Z"
}
}Error Codes: | Code | Description |
|------|-------------|
| VALIDATION_ERROR | Cannot follow yourself |
| VALIDATION_ERROR | Already following this user |
| NOT_FOUND | User not found |
Example:
const response = await fetch('/api/v1/social', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ followingId: 'user-uuid-here' }),
});
const result = await response.json();
if (result.success) {
console.log('Now following user!');
}Unfollow a User
DELETE /api/v1/social
Stop following a user.
Query Parameters or Body: | Parameter | Type | Description |
|-----------|------|-------------|
| followingId | uuid | User ID to unfollow |
Request (via body):
{
"followingId": "uuid"
}Or via query string:
DELETE /api/v1/social?followingId=uuidResponse:
{
"success": true,
"data": {
"unfollowed": true
}
}Error Codes: | Code | Description |
|------|-------------|
| VALIDATION_ERROR | User ID required |
| NOT_FOUND | Follow relationship not found |
Example:
const response = await fetch('/api/v1/social', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ followingId: 'user-uuid-here' }),
});User Profiles
Get User Profile
GET /api/v1/social
Retrieve a user’s profile with stats and social information.
Query Parameters: | Parameter | Type | Description |
|-----------|------|-------------|
| userId | uuid | Required: User ID to fetch |
Response:
{
"success": true,
"data": {
"user": {
"id": "uuid",
"username": "beerlover",
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"avatar_url": "https://cdn.brewhoard.com/avatars/...",
"bio": "Craft beer enthusiast",
"location": "Portland, OR",
"created_at": "2023-06-15T10:00:00Z",
"displayName": "John Doe"
},
"stats": {
"followers": 42,
"following": 28,
"collection": 156,
"ratings": 89
},
"social": {
"isFollowing": false,
"isOwnProfile": true
},
"recentActivity": [
{
"id": "uuid",
"type": "rating",
"data": {
"beer_name": "Hop Heaven IPA",
"brewery_name": "Craft Brewery",
"rating": 4.5
},
"timestamp": "2024-01-14T18:30:00Z"
}
]
}
}Example:
const response = await fetch('/api/v1/social?userId=user-uuid-here');
const { data } = await response.json();
console.log(`${data.user.displayName} has ${data.stats.followers} followers`);Activity Feed
Get Activity Feed
GET /api/v1/social?action=feed
Get the activity feed showing recent actions from users you follow.
Query Parameters: | Parameter | Type | Description |
|-----------|------|-------------|
| action | string | Required: feed |
| limit | number | Results per page (default: 20) |
| offset | number | Pagination offset (default: 0) |
Response:
{
"success": true,
"data": [
{
"id": "uuid",
"eventType": "collection_add",
"targetType": "beer",
"targetId": "beer-uuid",
"metadata": {
"beerName": "Imperial Stout",
"breweryName": "Dark Brewing Co",
"quantity": 6
},
"createdAt": "2024-01-15T14:30:00Z",
"user": {
"id": "uuid",
"username": "craftlover",
"avatarUrl": "https://cdn.brewhoard.com/avatars/...",
"displayName": "Sarah Smith"
}
},
{
"id": "uuid",
"eventType": "rating",
"targetType": "beer",
"targetId": "beer-uuid",
"metadata": {
"beerName": "Hoppy Blonde",
"rating": 4.5,
"review": "Excellent summer beer!"
},
"createdAt": "2024-01-15T12:15:00Z",
"user": {
"id": "uuid",
"username": "hophead",
"displayName": "Mike Johnson"
}
}
]
}Activity Event Types
| Event Type | Description | Metadata |
|---|---|---|
collection_add | Added beer to collection | beerName, breweryName, quantity |
collection_remove | Removed beer from collection | beerName |
rating | Rated a beer | beerName, rating, review |
listing_create | Created marketplace listing | beerName, price |
listing_sold | Sold a listing | beerName, price |
follow | Followed a user | followingId, followingUsername |
consume | Consumed a beer | beerName, quantity |
like | Liked content | targetType, targetId |
comment | Commented on content | targetType, content |
Example:
async function loadFeed(page = 1) {
const limit = 20;
const offset = (page - 1) * limit;
const response = await fetch(
`/api/v1/social?action=feed&limit=${limit}&offset=${offset}`
);
const { data } = await response.json();
return data;
}Likes
Toggle Like
POST /api/v1/social/like
Like or unlike content (ratings, collection items, listings).
Request:
{
"targetType": "rating",
"targetId": "uuid"
}Target Types: | Type | Description |
|------|-------------|
| rating | A beer rating/review |
| collection_item | A collection entry |
| listing | A marketplace listing |
Response:
{
"success": true,
"data": {
"liked": true
}
}The response liked field indicates the new state:
true- Content is now likedfalse- Content is now unliked
Example:
async function toggleLike(targetType, targetId) {
const response = await fetch('/api/v1/social/like', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ targetType, targetId }),
});
const { data } = await response.json();
return data.liked;
}
// Usage
const isLiked = await toggleLike('rating', 'rating-uuid');Comments
Get Comments
GET /api/v1/social/comments
Get comments for a piece of content.
Query Parameters: | Parameter | Type | Description |
|-----------|------|-------------|
| targetType | string | Content type |
| targetId | uuid | Content ID |
| limit | number | Results per page (default: 50) |
| offset | number | Pagination offset |
Response:
{
"success": true,
"data": {
"comments": [
{
"id": "uuid",
"content": "Great choice! I love this beer too.",
"parentId": null,
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-01-15T10:30:00Z",
"user": {
"id": "uuid",
"username": "beerfan",
"avatarUrl": "https://cdn.brewhoard.com/avatars/...",
"displayName": "Alex Brown"
}
}
]
}
}Add Comment
POST /api/v1/social/comments
Add a comment to content.
Request:
{
"targetType": "rating",
"targetId": "uuid",
"content": "Excellent review! I had the same experience.",
"parentId": null
}| Field | Type | Required | Description |
|---|---|---|---|
targetType | string | Yes | Type of content |
targetId | uuid | Yes | ID of content |
content | string | Yes | Comment text |
parentId | uuid | No | Parent comment ID for replies |
Response:
{
"success": true,
"data": {
"comment": {
"id": "uuid",
"content": "Excellent review! I had the same experience.",
"parentId": null,
"createdAt": "2024-01-15T14:30:00Z",
"user": {
"id": "uuid",
"username": "currentuser",
"displayName": "Current User"
}
}
}
}Followers & Following
Get User Followers
GET /api/v1/social/followers
Get list of users following a specific user.
Query Parameters: | Parameter | Type | Description |
|-----------|------|-------------|
| userId | uuid | Required: User ID |
| limit | number | Results per page (default: 20) |
| offset | number | Pagination offset |
Response:
{
"success": true,
"data": {
"followers": [
{
"id": "uuid",
"username": "craftlover",
"first_name": "Sarah",
"last_name": "Smith",
"avatar_url": "https://cdn.brewhoard.com/avatars/...",
"displayName": "Sarah Smith",
"followed_at": "2024-01-10T08:00:00Z"
}
],
"pagination": {
"total": 42,
"limit": 20,
"offset": 0,
"hasMore": true
}
}
}Get User Following
GET /api/v1/social/following
Get list of users that a specific user follows.
Query Parameters: | Parameter | Type | Description |
|-----------|------|-------------|
| userId | uuid | Required: User ID |
| limit | number | Results per page (default: 20) |
| offset | number | Pagination offset |
Response:
{
"success": true,
"data": {
"following": [
{
"id": "uuid",
"username": "hophead",
"displayName": "Mike Johnson",
"followed_at": "2024-01-05T12:00:00Z"
}
],
"pagination": {
"total": 28,
"limit": 20,
"offset": 0,
"hasMore": true
}
}
}Real-Time Events
Social features emit real-time events via Socket.IO. Subscribe to these events for live updates.
Event Types
| Event | Description | Payload |
|---|---|---|
activity:new | New activity from followed user | Activity object |
notification:follow | Someone followed you | { fromUserId, fromUsername } |
notification:like | Someone liked your content | { fromUser, targetType, targetId } |
notification:comment | Someone commented on your content | { fromUser, content } |
Client Example
import { io } from 'socket.io-client';
const socket = io();
// Subscribe to activity updates
socket.on('activity:new', (activity) => {
console.log('New activity:', activity);
// Add to feed UI
});
// Subscribe to notifications
socket.on('notification:follow', (data) => {
console.log(`${data.fromUsername} started following you!`);
});
socket.on('notification:like', (data) => {
console.log(`${data.fromUser} liked your ${data.targetType}`);
});Error Handling
All endpoints return consistent error responses:
{
"success": false,
"error": "User ID is required",
"code": "MISSING_USER_ID"
}Common Error Codes
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Authentication required |
MISSING_USER_ID | 400 | User ID parameter missing |
VALIDATION_ERROR | 400 | Invalid request data |
NOT_FOUND | 404 | User or resource not found |
INTERNAL_ERROR | 500 | Server error |
Usage Examples
Complete Follow Flow
// Check if following
async function checkFollowing(userId) {
const response = await fetch(`/api/v1/social?userId=${userId}`);
const { data } = await response.json();
return data.social.isFollowing;
}
// Toggle follow
async function toggleFollow(userId, isCurrentlyFollowing) {
const method = isCurrentlyFollowing ? 'DELETE' : 'POST';
const response = await fetch('/api/v1/social', {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ followingId: userId }),
});
return response.ok;
}Building an Activity Feed
<script>
let activities = $state([]);
let loading = $state(false);
let page = $state(1);
async function loadMore() {
loading = true;
const limit = 20;
const offset = (page - 1) * limit;
const response = await fetch(
`/api/v1/social?action=feed&limit=${limit}&offset=${offset}`
);
const { data } = await response.json();
activities = [...activities, ...data];
page += 1;
loading = false;
}
// Initial load
$effect(() => {
loadMore();
});
</script>
{#each activities as activity}
<ActivityCard {activity} />
{/each}
<button onclick={loadMore} disabled={loading}>
{loading ? 'Loading...' : 'Load More'}
</button>Next Steps
- Social Features Guide - User-facing social features
- Notifications API - Push notifications
- User Profiles - Profile customization