Version: 1.0.0
Base URL: https://api.linkedup.com/api/v1/
Authentication: JWT Bearer Token
Content-Type: application/json
- Quick Start
- Authentication
- API Response Format
- Error Handling
- Core Modules
- Best Practices
- Common Issues & Solutions
// Using Fetch API
const API_BASE_URL = 'https://api.linkedup.com/api/v1';
// Initialize API client
const linkedUpAPI = {
baseURL: API_BASE_URL,
token: null,
// Set token after login
setToken(token) {
this.token = token;
},
// Make authenticated requests
async request(method, endpoint, body = null, headers = {}) {
const options = {
method,
headers: {
'Content-Type': 'application/json',
...headers,
},
};
if (this.token) {
options.headers.Authorization = `Bearer ${this.token}`;
}
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(`${this.baseURL}${endpoint}`, options);
return response.json();
},
// Convenience methods
get(endpoint) { return this.request('GET', endpoint); },
post(endpoint, body) { return this.request('POST', endpoint, body); },
put(endpoint, body) { return this.request('PUT', endpoint, body); },
delete(endpoint) { return this.request('DELETE', endpoint); },
};// 1. Register new user
const registerResponse = await linkedUpAPI.post('/auth/register/', {
username: 'john_doe',
email: 'john@example.com',
password: 'SecurePassword123!',
});
// 2. Login
const loginResponse = await linkedUpAPI.post('/auth/login/', {
email: 'john@example.com',
password: 'SecurePassword123!',
});
// 3. Store token for future requests
linkedUpAPI.setToken(loginResponse.data.access_token);
// 4. Verify email (if required)
await linkedUpAPI.post('/auth/verify-email/', {
token: loginResponse.data.verification_token,
});LinkedUp uses JWT (JSON Web Token) for authentication. All API endpoints require authentication via Bearer token in the Authorization header, except for public endpoints like registration and login.
- Access Token - Short-lived (15 minutes). Used for API requests.
- Refresh Token - Long-lived (7 days). Used to obtain new access tokens.
All authenticated requests must include:
Authorization: Bearer {access_token}
Content-Type: application/json
// Decode token to check expiration (client-side, optional)
function parseJwt(token) {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(
atob(base64).split('').map(c =>
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
).join('')
);
return JSON.parse(jsonPayload);
}
// Check if token expires in next 2 minutes
const token = linkedUpAPI.token;
const decoded = parseJwt(token);
const expiresIn = decoded.exp * 1000 - Date.now();
if (expiresIn < 2 * 60 * 1000) {
// Refresh token
const newToken = await linkedUpAPI.post('/auth/refresh/', {
refresh_token: storedRefreshToken,
});
linkedUpAPI.setToken(newToken.data.access_token);
}async function apiRequestWithAutoRefresh(method, endpoint, body) {
try {
return await linkedUpAPI.request(method, endpoint, body);
} catch (error) {
if (error.status_code === 401 && error.message.includes('Token expired')) {
// Refresh token
const response = await linkedUpAPI.post('/auth/refresh/', {
refresh_token: localStorage.getItem('refresh_token'),
});
linkedUpAPI.setToken(response.data.access_token);
localStorage.setItem('refresh_token', response.data.refresh_token);
// Retry original request
return linkedUpAPI.request(method, endpoint, body);
}
throw error;
}
}All successful responses follow this format:
{
"success": true,
"data": {
// Response payload varies by endpoint
},
"pagination": {
"page": 1,
"page_size": 20,
"total_count": 150,
"total_pages": 8,
"has_next": true,
"has_prev": false
}
}{
"error": true,
"message": "Authentication failed",
"status_code": 401,
"errors": {
// Detailed validation errors (if applicable)
"email": ["Invalid email format"],
"password": ["Password must be at least 8 characters"]
}
}async function handleResponse(response) {
const json = await response.json();
if (json.error) {
console.error(`[${json.status_code}] ${json.message}`);
if (json.errors) {
// Handle validation errors
Object.entries(json.errors).forEach(([field, messages]) => {
console.error(`${field}: ${messages.join(', ')}`);
});
}
throw new Error(json.message);
}
return json.data;
}| Status | Meaning | Description |
|---|---|---|
| 200 | OK | Request successful |
| 201 | Created | Resource created successfully |
| 400 | Bad Request | Invalid request parameters or validation failed |
| 401 | Unauthorized | Missing or invalid authentication token |
| 403 | Forbidden | Authenticated but not authorized for this action |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Resource already exists (e.g., duplicate email) |
| 429 | Too Many Requests | Rate limit exceeded (max 100 requests/minute) |
| 500 | Server Error | Internal server error |
{
"error": true,
"message": "Invalid input",
"status_code": 400,
"errors": {
"username": ["Username must be 3-50 characters"],
"password": ["Password must contain uppercase, lowercase, number, and special character"]
}
}{
"error": true,
"message": "Token expired or invalid",
"status_code": 401
}{
"error": true,
"message": "Resource already exists",
"status_code": 409,
"detail": "Email 'john@example.com' is already registered"
}class APIError extends Error {
constructor(message, statusCode, errors = null) {
super(message);
this.statusCode = statusCode;
this.errors = errors;
}
}
async function handleAPIError(response) {
const json = await response.json();
throw new APIError(
json.message,
json.status_code,
json.errors || null
);
}
// Usage
try {
const result = await linkedUpAPI.post('/auth/login/', {
email: 'invalid',
password: 'short',
});
} catch (error) {
if (error instanceof APIError) {
if (error.statusCode === 400) {
// Handle validation errors
console.error('Validation failed:', error.errors);
} else if (error.statusCode === 401) {
// Handle authentication errors
window.location.href = '/login';
}
}
}Base URL: /api/v1/auth/
POST /auth/register/
Description: Create a new user account
Request Body:
{
"username": "john_doe",
"email": "john@example.com",
"password": "SecurePassword123!",
"password_confirm": "SecurePassword123!"
}Response (201 Created):
{
"success": true,
"data": {
"user_id": 123,
"username": "john_doe",
"email": "john@example.com",
"created_at": "2026-01-21T10:30:00Z",
"verification_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"message": "User registered. Please verify your email."
}
}Password Requirements:
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number
- At least one special character (!@#$%^&*)
Validation Errors (400):
{
"error": true,
"message": "Invalid input",
"status_code": 400,
"errors": {
"username": ["Username must be 3-50 characters", "This username is already taken"],
"email": ["Invalid email format"],
"password": ["Password must contain special characters"]
}
}JavaScript Example:
async function registerUser() {
try {
const response = await linkedUpAPI.post('/auth/register/', {
username: 'john_doe',
email: 'john@example.com',
password: 'SecurePassword123!',
password_confirm: 'SecurePassword123!',
});
console.log('Registration successful:', response.user_id);
console.log('Verification email sent to:', response.email);
// Store verification token for email confirmation
localStorage.setItem('verificationToken', response.verification_token);
} catch (error) {
console.error('Registration failed:', error.message);
displayErrorMessage(error.message);
}
}POST /auth/login/
Description: Authenticate user and get JWT tokens
Request Body:
{
"email": "john@example.com",
"password": "SecurePassword123!"
}Response (200 OK):
{
"success": true,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"user_id": 123,
"username": "john_doe",
"email": "john@example.com",
"profile_picture": "https://cdn.linkedup.com/profiles/123.jpg",
"is_premium": false,
"created_at": "2026-01-21T10:30:00Z"
},
"expires_in": 900
}
}Error Responses:
// 401 - Invalid credentials
{
"error": true,
"message": "Authentication failed",
"status_code": 401
}
// 403 - Account blocked/suspended
{
"error": true,
"message": "Account is temporarily locked",
"status_code": 403
}JavaScript Example:
async function loginUser(email, password) {
try {
const response = await linkedUpAPI.post('/auth/login/', {
email,
password,
});
// Store tokens
localStorage.setItem('accessToken', response.access_token);
localStorage.setItem('refreshToken', response.refresh_token);
// Set in API client
linkedUpAPI.setToken(response.access_token);
// Store user info
localStorage.setItem('currentUser', JSON.stringify(response.user));
// Redirect to dashboard
window.location.href = '/dashboard';
} catch (error) {
if (error.status_code === 401) {
showErrorMessage('Invalid email or password');
} else if (error.status_code === 403) {
showErrorMessage('Your account is locked. Contact support.');
}
}
}POST /auth/logout/
Description: Invalidate user session
Authentication: Required ✅
Request Body:
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Response (200 OK):
{
"success": true,
"data": {
"message": "Logged out successfully"
}
}JavaScript Example:
async function logoutUser() {
try {
await linkedUpAPI.post('/auth/logout/', {
refresh_token: localStorage.getItem('refreshToken'),
});
// Clear local storage
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('currentUser');
// Clear token from API client
linkedUpAPI.setToken(null);
// Redirect to login
window.location.href = '/login';
} catch (error) {
console.error('Logout failed:', error.message);
}
}POST /auth/refresh/
Description: Get new access token using refresh token
Request Body:
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Response (200 OK):
{
"success": true,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 900
}
}JavaScript Example:
async function refreshAccessToken() {
try {
const response = await linkedUpAPI.post('/auth/refresh/', {
refresh_token: localStorage.getItem('refreshToken'),
});
localStorage.setItem('accessToken', response.access_token);
localStorage.setItem('refreshToken', response.refresh_token);
linkedUpAPI.setToken(response.access_token);
return response.access_token;
} catch (error) {
// Refresh token expired, redirect to login
window.location.href = '/login';
}
}POST /auth/verify-email/
Description: Confirm user email address
Request Body:
{
"verification_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Response (200 OK):
{
"success": true,
"data": {
"message": "Email verified successfully"
}
}JavaScript Example:
async function verifyEmail(token) {
try {
await linkedUpAPI.post('/auth/verify-email/', {
verification_token: token,
});
console.log('Email verified successfully');
} catch (error) {
console.error('Email verification failed:', error.message);
}
}POST /auth/verify-email/resend/
Description: Send verification email again
Request Body:
{
"email": "john@example.com"
}Response (200 OK):
{
"success": true,
"data": {
"message": "Verification email sent"
}
}POST /auth/password/reset/
Description: Initiate password reset process
Request Body:
{
"email": "john@example.com"
}Response (200 OK):
{
"success": true,
"data": {
"message": "Password reset email sent",
"reset_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}JavaScript Example:
async function requestPasswordReset(email) {
try {
const response = await linkedUpAPI.post('/auth/password/reset/', {
email,
});
console.log('Reset email sent to:', email);
return response.reset_token;
} catch (error) {
if (error.status_code === 404) {
// Don't reveal if email exists
console.log('If email exists, reset link will be sent');
}
}
}POST /auth/password/reset/confirm/
Description: Complete password reset
Request Body:
{
"reset_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"new_password": "NewSecurePassword123!",
"new_password_confirm": "NewSecurePassword123!"
}Response (200 OK):
{
"success": true,
"data": {
"message": "Password reset successful"
}
}POST /auth/password/change/
Description: Change password for authenticated user
Authentication: Required ✅
Request Body:
{
"current_password": "OldPassword123!",
"new_password": "NewSecurePassword456!",
"new_password_confirm": "NewSecurePassword456!"
}Response (200 OK):
{
"success": true,
"data": {
"message": "Password changed successfully"
}
}POST /auth/deactivate/
Description: Deactivate user account
Authentication: Required ✅
Request Body:
{
"password": "SecurePassword123!",
"reason": "Taking a break"
}Response (200 OK):
{
"success": true,
"data": {
"message": "Account deactivated successfully",
"reactivation_deadline": "2026-04-21T10:30:00Z"
}
}Base URL: /api/v1/users/
GET /users/{username}/
Description: Get public user profile information
Authentication: Optional (more data if authenticated)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| username | string | Target user's username |
Response (200 OK):
{
"success": true,
"data": {
"user_id": 456,
"username": "jane_smith",
"first_name": "Jane",
"last_name": "Smith",
"bio": "Software engineer | Tech enthusiast",
"profile_picture": "https://cdn.linkedup.com/profiles/456.jpg",
"banner": "https://cdn.linkedup.com/banners/456.jpg",
"location": "San Francisco, CA",
"website": "https://janesmith.dev",
"verified": true,
"is_premium": true,
"followers_count": 1250,
"following_count": 380,
"posts_count": 145,
"joined_date": "2024-06-15T08:20:00Z",
"last_active": "2026-01-21T09:45:00Z"
}
}JavaScript Example:
async function getUserProfile(username) {
try {
const user = await linkedUpAPI.get(`/users/${username}/`);
displayUserProfile(user);
} catch (error) {
if (error.status_code === 404) {
showErrorMessage(`User '${username}' not found`);
}
}
}GET /users/{username}/contact-info/
Description: Get user's contact details (email, phone - if public)
Authentication: Required ✅
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| username | string | Target user's username |
Response (200 OK):
{
"success": true,
"data": {
"email": "jane@example.com",
"phone": "+1-555-0123",
"contact_preference": "email",
"is_email_public": false,
"is_phone_public": true
}
}POST /users/{username}/follow/
Description: Follow a user
Authentication: Required ✅
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| username | string | Username to follow |
Request Body:
{
"notify": true
}Response (201 Created):
{
"success": true,
"data": {
"message": "Following jane_smith",
"following": true,
"user": {
"username": "jane_smith",
"followers_count": 1251
}
}
}Error Responses:
// 409 - Already following
{
"error": true,
"message": "Resource already exists",
"status_code": 409,
"detail": "Already following this user"
}
// 403 - User blocked you or is private
{
"error": true,
"message": "Not authorized",
"status_code": 403
}JavaScript Example:
async function followUser(username) {
try {
await linkedUpAPI.post(`/users/${username}/follow/`, {
notify: true,
});
console.log(`Now following ${username}`);
updateFollowButton(username, 'following');
} catch (error) {
if (error.status_code === 409) {
showMessage('Already following this user');
}
}
}POST /users/{username}/unfollow/
Description: Unfollow a user
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"message": "Unfollowed jane_smith",
"following": false,
"user": {
"username": "jane_smith",
"followers_count": 1249
}
}
}POST /users/{username}/block/
Description: Block a user
Authentication: Required ✅
Response (201 Created):
{
"success": true,
"data": {
"message": "User blocked",
"blocked": true
}
}POST /users/{username}/unblock/
Description: Unblock a previously blocked user
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"message": "User unblocked",
"blocked": false
}
}GET /users/{username}/posts/
Description: Get all posts from a specific user
Authentication: Optional
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| username | string | Username to fetch posts from |
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number for pagination |
| page_size | integer | 20 | Posts per page (max 100) |
| sort | string | newest | Sort order: newest, oldest, trending |
Response (200 OK):
{
"success": true,
"data": [
{
"post_id": 789,
"post_uid": "post_abc123xyz",
"author": {
"user_id": 456,
"username": "jane_smith",
"profile_picture": "https://cdn.linkedup.com/profiles/456.jpg"
},
"content": "Just launched my new project!",
"images": [
{
"url": "https://cdn.linkedup.com/posts/789/image1.jpg",
"width": 1200,
"height": 630
}
],
"created_at": "2026-01-20T15:30:00Z",
"updated_at": "2026-01-20T15:30:00Z",
"visibility": "public",
"engagement": {
"likes_count": 42,
"comments_count": 8,
"shares_count": 5,
"views_count": 230
},
"is_liked": false,
"is_saved": true
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total_count": 145,
"total_pages": 8,
"has_next": true,
"has_prev": false
}
}JavaScript Example:
async function getUserPosts(username, page = 1) {
try {
const response = await linkedUpAPI.get(
`/users/${username}/posts/?page=${page}&page_size=20&sort=newest`
);
displayPosts(response.data);
displayPagination(response.pagination);
} catch (error) {
console.error('Failed to load posts:', error.message);
}
}GET /users/{username}/followers/
Description: Get list of user's followers
Authentication: Optional
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number |
| page_size | integer | 50 | Followers per page (max 100) |
| sort | string | newest | Sort: newest, oldest, alphabetical |
Response (200 OK):
{
"success": true,
"data": [
{
"user_id": 123,
"username": "john_doe",
"profile_picture": "https://cdn.linkedup.com/profiles/123.jpg",
"bio": "Full-stack developer",
"is_following": false,
"followed_at": "2025-12-10T14:20:00Z"
}
],
"pagination": {
"page": 1,
"page_size": 50,
"total_count": 1250,
"total_pages": 25,
"has_next": true,
"has_prev": false
}
}GET /users/{username}/following/
Description: Get list of users that this user is following
Authentication: Optional
Response: Similar to followers endpoint
GET /users/{username}/relationship-state/
Description: Get relationship status with another user
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"username": "jane_smith",
"is_following": true,
"is_followed_by": false,
"is_blocked": false,
"is_blocked_by": false,
"can_message": true,
"relationship_status": "following"
}
}Base URL: /api/v1/profile/
GET /profile/me/{username}/
Description: Get authenticated user's own profile
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"user_id": 123,
"username": "john_doe",
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"bio": "Full-stack developer | Open source enthusiast",
"profile_picture": "https://cdn.linkedup.com/profiles/123.jpg",
"banner": "https://cdn.linkedup.com/banners/123.jpg",
"location": "New York, NY",
"website": "https://johndoe.dev",
"verified": true,
"is_premium": true,
"followers_count": 850,
"following_count": 420,
"posts_count": 156,
"is_public_profile": true,
"email_notifications_enabled": true,
"push_notifications_enabled": true,
"created_at": "2024-03-10T09:15:00Z"
}
}PUT /profile/me/{username}/
Description: Update authenticated user's profile
Authentication: Required ✅
Request Body:
{
"first_name": "Jonathan",
"last_name": "Doe",
"bio": "Senior Full-stack Developer",
"location": "San Francisco, CA",
"website": "https://jonathandoe.dev",
"is_public_profile": true
}Response (200 OK):
{
"success": true,
"data": {
"message": "Profile updated successfully",
"user": {
"username": "john_doe",
"first_name": "Jonathan",
"bio": "Senior Full-stack Developer"
}
}
}JavaScript Example:
async function updateProfile(formData) {
try {
const currentUser = JSON.parse(localStorage.getItem('currentUser'));
const response = await linkedUpAPI.put(
`/profile/me/${currentUser.username}/`,
{
first_name: formData.firstName,
last_name: formData.lastName,
bio: formData.bio,
location: formData.location,
website: formData.website,
}
);
console.log('Profile updated:', response.user);
showSuccessMessage('Profile updated successfully');
} catch (error) {
console.error('Update failed:', error.message);
}
}POST /profile/me/{username}/
Description: Upload new profile picture
Authentication: Required ✅
Request (Multipart Form Data):
Content-Type: multipart/form-data
profile_picture: <file>
Response (200 OK):
{
"success": true,
"data": {
"message": "Profile picture updated",
"profile_picture": "https://cdn.linkedup.com/profiles/123_v2.jpg"
}
}JavaScript Example:
async function uploadProfilePicture(file) {
const formData = new FormData();
formData.append('profile_picture', file);
const response = await fetch(
`${linkedUpAPI.baseURL}/profile/me/${currentUsername}/`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${linkedUpAPI.token}`,
},
body: formData,
}
);
return response.json();
}GET /profile/me/{username}/posts/
Description: Get authenticated user's posts
Authentication: Required ✅
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number |
| page_size | integer | 20 | Posts per page |
| visibility | string | all | Filter: all, public, private |
| status | string | all | Filter: all, published, draft |
Response: Similar to Users posts endpoint
GET /profile/me/{username}/comments/
Description: Get all comments made by authenticated user
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": [
{
"comment_id": 234,
"comment_uid": "comment_def456ghi",
"post": {
"post_uid": "post_abc123xyz",
"content_preview": "Just launched..."
},
"content": "Great project! Love the implementation.",
"likes_count": 5,
"replies_count": 2,
"created_at": "2026-01-21T08:15:00Z"
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total_count": 42,
"total_pages": 3,
"has_next": true,
"has_prev": false
}
}GET /profile/me/{username}/analytics/
Description: Get profile analytics (requires premium)
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"period": "last_30_days",
"impressions": 5420,
"profile_views": 320,
"followers_gained": 45,
"posts_engagement": {
"total_likes": 156,
"total_comments": 32,
"total_shares": 12
}
}
}Base URL: /api/v1/posts/
POST /posts/
Description: Create a new post
Authentication: Required ✅
Request Body:
{
"content": "Just completed an amazing project! #webdevelopment",
"visibility": "public",
"allow_comments": true,
"allow_reactions": true,
"images": [
{
"url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABA..."
}
],
"tagged_users": ["jane_smith", "bob_wilson"],
"hashtags": ["webdevelopment", "javascript"],
"polls": {
"question": "Favorite framework?",
"options": ["React", "Vue", "Angular"],
"expires_at": "2026-01-28T10:00:00Z"
}
}Response (201 Created):
{
"success": true,
"data": {
"post_id": 1001,
"post_uid": "post_xyz789abc",
"author": {
"user_id": 123,
"username": "john_doe"
},
"content": "Just completed an amazing project!",
"visibility": "public",
"created_at": "2026-01-21T10:30:00Z",
"engagement": {
"likes_count": 0,
"comments_count": 0
}
}
}Validation Errors (400):
{
"error": true,
"message": "Invalid input",
"status_code": 400,
"errors": {
"content": ["Content is required"],
"images": ["Each image must be less than 5MB"]
}
}JavaScript Example:
async function createPost(content, images = []) {
try {
const postData = {
content,
visibility: 'public',
allow_comments: true,
allow_reactions: true,
};
if (images.length > 0) {
postData.images = await Promise.all(
images.map(file => fileToBase64(file))
);
}
const response = await linkedUpAPI.post('/posts/', postData);
console.log('Post created:', response.post_uid);
return response;
} catch (error) {
console.error('Failed to create post:', error.message);
}
}
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve({ url: reader.result });
reader.onerror = reject;
reader.readAsDataURL(file);
});
}GET /posts/{post_uid}/
Description: Get full post details
Authentication: Optional
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| post_uid | string | Unique post identifier |
Response (200 OK):
{
"success": true,
"data": {
"post_id": 1001,
"post_uid": "post_xyz789abc",
"author": {
"user_id": 123,
"username": "john_doe",
"profile_picture": "https://cdn.linkedup.com/profiles/123.jpg"
},
"content": "Just completed an amazing project! #webdevelopment",
"content_html": "<p>Just completed an amazing project! <a href='...'>#webdevelopment</a></p>",
"images": [
{
"url": "https://cdn.linkedup.com/posts/1001/image1.jpg",
"width": 1200,
"height": 630,
"alt": "Project screenshot"
}
],
"visibility": "public",
"allow_comments": true,
"allow_reactions": true,
"created_at": "2026-01-20T15:30:00Z",
"updated_at": "2026-01-20T15:30:00Z",
"is_edited": false,
"engagement": {
"likes_count": 42,
"comments_count": 8,
"shares_count": 5,
"views_count": 230
},
"is_liked": false,
"is_saved": true,
"top_reactions": [
{
"reaction_type": "like",
"count": 35,
"users": [
{
"username": "jane_smith",
"profile_picture": "https://cdn.linkedup.com/profiles/456.jpg"
}
]
}
],
"poll": {
"poll_id": 555,
"question": "Favorite framework?",
"options": [
{
"id": 1,
"text": "React",
"votes": 45,
"percentage": 65,
"is_voted": true
},
{
"id": 2,
"text": "Vue",
"votes": 18,
"percentage": 26,
"is_voted": false
}
],
"total_votes": 69,
"is_closed": false,
"expires_at": "2026-01-28T10:00:00Z"
}
}
}PUT /posts/{post_uid}/edit/
Description: Edit existing post (only by author)
Authentication: Required ✅
Request Body:
{
"content": "Updated content with more details!",
"visibility": "public"
}Response (200 OK):
{
"success": true,
"data": {
"message": "Post updated successfully",
"post": {
"post_uid": "post_xyz789abc",
"content": "Updated content with more details!",
"is_edited": true,
"edited_at": "2026-01-21T11:45:00Z"
}
}
}DELETE /posts/{post_uid}/delete/
Description: Delete a post
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"message": "Post deleted successfully"
}
}PUT /posts/{post_uid}/visibility/
Description: Change post visibility
Authentication: Required ✅
Request Body:
{
"visibility": "private"
}Visibility Options:
public- Everyone can seeprivate- Only followers can seefriends- Only accepted connections can seehidden- No one except author can see
GET /posts/{post_uid}/stats/
Description: Get detailed post statistics
Authentication: Optional (more data if author)
Response (200 OK):
{
"success": true,
"data": {
"views": 230,
"impressions": 1200,
"engagement_rate": 22.4,
"top_traffic_source": "feed",
"demographics": {
"gender": {
"male": 55,
"female": 40,
"other": 5
},
"age_groups": {
"18-24": 120,
"25-34": 85,
"35-44": 20
}
}
}
}POST /posts/{post_uid}/report/
Description: Report inappropriate post
Authentication: Required ✅
Request Body:
{
"reason": "inappropriate_content",
"description": "This post contains offensive language"
}Report Reasons:
spam- Spam or promotional contentharassment- Harassment or bullyinginappropriate_content- Inappropriate contentcopyright- Copyright violationmisinformation- False or misleading informationother- Other
Base URL: /api/v1/comments/
POST /comments/
Description: Add comment to a post
Authentication: Required ✅
Request Body:
{
"post_uid": "post_xyz789abc",
"content": "This is a great post!",
"parent_comment_uid": null
}Response (201 Created):
{
"success": true,
"data": {
"comment_id": 234,
"comment_uid": "comment_def456ghi",
"post_uid": "post_xyz789abc",
"author": {
"user_id": 123,
"username": "john_doe",
"profile_picture": "https://cdn.linkedup.com/profiles/123.jpg"
},
"content": "This is a great post!",
"created_at": "2026-01-21T10:45:00Z",
"likes_count": 0,
"replies_count": 0
}
}JavaScript Example:
async function addComment(postUid, content) {
try {
const response = await linkedUpAPI.post('/comments/', {
post_uid: postUid,
content,
parent_comment_uid: null,
});
console.log('Comment added:', response.comment_uid);
return response;
} catch (error) {
console.error('Failed to add comment:', error.message);
}
}GET /comments/{comment_uid}/
Description: Get full comment with replies
Response (200 OK):
{
"success": true,
"data": {
"comment_id": 234,
"comment_uid": "comment_def456ghi",
"post_uid": "post_xyz789abc",
"author": {
"user_id": 123,
"username": "john_doe",
"profile_picture": "https://cdn.linkedup.com/profiles/123.jpg"
},
"content": "This is a great post!",
"created_at": "2026-01-21T10:45:00Z",
"updated_at": "2026-01-21T10:45:00Z",
"is_edited": false,
"likes_count": 5,
"replies_count": 2,
"is_liked": false
}
}DELETE /comments/{comment_uid}/delete/
Description: Delete a comment
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"message": "Comment deleted successfully"
}
}POST /comments/{comment_uid}/replies/
Description: Add reply to a comment
Authentication: Required ✅
Request Body:
{
"content": "I agree with you!"
}Response (201 Created):
{
"success": true,
"data": {
"comment_id": 235,
"comment_uid": "comment_jkl789mno",
"parent_comment_uid": "comment_def456ghi",
"content": "I agree with you!",
"created_at": "2026-01-21T11:00:00Z"
}
}Base URL: /api/v1/reactions/
POST /reactions/toggle/
Description: Like/unlike or react to a post/comment
Authentication: Required ✅
Request Body:
{
"target_type": "post",
"target_uid": "post_xyz789abc",
"reaction_type": "like"
}Reaction Types:
like- Likelove- Lovehaha- Funnywow- Wowsad- Sadangry- Angry
Target Types:
post- Reaction on postcomment- Reaction on comment
Response (200 OK):
{
"success": true,
"data": {
"message": "Reaction added",
"is_reacted": true,
"reaction_type": "like",
"total_reactions": 43
}
}JavaScript Example:
async function toggleLike(postUid) {
try {
const response = await linkedUpAPI.post('/reactions/toggle/', {
target_type: 'post',
target_uid: postUid,
reaction_type: 'like',
});
console.log('Like toggled:', response.is_reacted);
updateLikeButton(postUid, response.is_reacted);
} catch (error) {
console.error('Failed to toggle like:', error.message);
}
}GET /reactions/list/
Description: Get all reactions on a post/comment
Authentication: Optional
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| target_type | string | post or comment |
| target_uid | string | UID of target |
| reaction_type | string | Filter by reaction type (optional) |
Response (200 OK):
{
"success": true,
"data": [
{
"reaction_type": "like",
"count": 35,
"users": [
{
"user_id": 456,
"username": "jane_smith",
"profile_picture": "https://cdn.linkedup.com/profiles/456.jpg"
}
]
},
{
"reaction_type": "love",
"count": 8,
"users": [...]
}
]
}Base URL: /api/v1/feed/
GET /feed/
Description: Get personalized main feed
Authentication: Required ✅
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number |
| page_size | integer | 20 | Posts per page (max 50) |
| filter | string | all | Filter: all, connections_only, following |
Response (200 OK):
{
"success": true,
"data": [
{
"post_id": 1001,
"post_uid": "post_xyz789abc",
"author": {
"user_id": 456,
"username": "jane_smith"
},
"content": "Just launched my new project!",
"created_at": "2026-01-20T15:30:00Z",
"engagement": {
"likes_count": 42,
"comments_count": 8
},
"is_liked": false
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total_count": 450,
"has_next": true
}
}JavaScript Example:
async function loadMainFeed(page = 1) {
try {
const response = await linkedUpAPI.get(
`/feed/?page=${page}&page_size=20&filter=all`
);
displayFeedPosts(response.data);
setupInfiniteScroll(response.pagination);
} catch (error) {
console.error('Failed to load feed:', error.message);
}
}
// Infinite scroll implementation
let currentPage = 1;
window.addEventListener('scroll', async () => {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) {
currentPage++;
await loadMainFeed(currentPage);
}
});GET /feed/following/
Description: Feed from users you follow only
Authentication: Required ✅
Query Parameters: Same as main feed
GET /feed/trending/
Description: Trending posts
Authentication: Optional
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| time_range | string | 24h | 1h, 6h, 24h, 7d, 30d |
| page | integer | 1 | Page number |
| page_size | integer | 20 | Posts per page |
GET /feed/user/{username}/
Description: Feed for a specific user's posts
Authentication: Optional
Base URL: /api/v1/notifications/
GET /notifications/
Description: Get user's notifications
Authentication: Required ✅
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number |
| page_size | integer | 20 | Notifications per page |
| unread_only | boolean | false | Show only unread |
| type | string | all | Filter: all, like, comment, follow, message |
Response (200 OK):
{
"success": true,
"data": [
{
"notification_id": 5001,
"notification_uid": "notif_abc123",
"type": "like",
"actor": {
"user_id": 456,
"username": "jane_smith",
"profile_picture": "https://cdn.linkedup.com/profiles/456.jpg"
},
"action": "liked your post",
"target": {
"type": "post",
"target_uid": "post_xyz789abc",
"preview": "Just launched my new project!"
},
"is_read": false,
"created_at": "2026-01-21T10:20:00Z",
"action_url": "/posts/post_xyz789abc"
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total_count": 42,
"total_pages": 3,
"has_next": true
}
}JavaScript Example:
async function loadNotifications() {
try {
const response = await linkedUpAPI.get(
'/notifications/?page=1&page_size=20&unread_only=false'
);
displayNotifications(response.data);
} catch (error) {
console.error('Failed to load notifications:', error.message);
}
}
// Real-time notifications with WebSocket
const ws = new WebSocket('wss://api.linkedup.com/ws/notifications/');
ws.onmessage = (event) => {
const notification = JSON.parse(event.data);
showNotificationToast(notification);
addNotificationToList(notification);
};GET /notifications/unread-count/
Description: Get count of unread notifications
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"unread_count": 5,
"unread_by_type": {
"like": 2,
"comment": 1,
"follow": 1,
"message": 1
}
}
}JavaScript Example:
async function getUnreadCount() {
try {
const response = await linkedUpAPI.get('/notifications/unread-count/');
updateNotificationBadge(response.unread_count);
} catch (error) {
console.error('Failed to get unread count:', error.message);
}
}
// Poll every 30 seconds
setInterval(getUnreadCount, 30000);POST /notifications/{notification_uid}/read/
Description: Mark single notification as read
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"message": "Notification marked as read",
"is_read": true
}
}POST /notifications/mark-all-read/
Description: Mark all notifications as read
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"message": "All notifications marked as read",
"marked_count": 12
}
}GET /notifications/settings/
Description: Get notification preferences
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"email_notifications": {
"likes": true,
"comments": true,
"follows": true,
"messages": true,
"marketing": false
},
"push_notifications": {
"likes": true,
"comments": true,
"follows": false,
"messages": true
},
"frequency": "instant"
}
}PUT /notifications/settings/
Request Body:
{
"email_notifications": {
"likes": false,
"comments": true,
"follows": true,
"messages": true,
"marketing": false
}
}Base URL: /api/v1/search/
GET /search/
Description: Search across all content
Authentication: Optional
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
| q | string | Yes | Search query |
| type | string | No | Filter: all, users, posts, hashtags |
| page | integer | No | Page number (default 1) |
| page_size | integer | No | Results per page (default 20) |
Response (200 OK):
{
"success": true,
"data": {
"users": [
{
"user_id": 456,
"username": "jane_smith",
"profile_picture": "https://cdn.linkedup.com/profiles/456.jpg",
"bio": "Software engineer",
"followers_count": 1250
}
],
"posts": [
{
"post_uid": "post_xyz789abc",
"author": {
"username": "jane_smith"
},
"content": "Search result post content",
"created_at": "2026-01-20T15:30:00Z"
}
],
"hashtags": [
{
"tag": "javascript",
"post_count": 2340,
"trend_position": 5
}
]
},
"pagination": {
"page": 1,
"page_size": 20
}
}JavaScript Example:
async function globalSearch(query, type = 'all') {
try {
const response = await linkedUpAPI.get(
`/search/?q=${encodeURIComponent(query)}&type=${type}&page_size=30`
);
displaySearchResults(response.data);
} catch (error) {
console.error('Search failed:', error.message);
}
}
// Real-time search with debounce
let searchTimeout;
document.getElementById('searchInput').addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
globalSearch(e.target.value);
}, 300);
});GET /search/users/
Description: Search for users
Authentication: Optional
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| q | string | Search query (username, name, bio) |
| page | integer | Page number |
| page_size | integer | Results per page |
Response: Similar to global search users results
GET /search/posts/
Description: Search posts by content
Authentication: Optional
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| q | string | Search query |
| author | string | Filter by author username |
| hashtag | string | Filter by hashtag |
| date_from | string | From date (ISO 8601) |
| date_to | string | To date (ISO 8601) |
GET /search/suggestions/
Description: Get autocomplete suggestions
Authentication: Optional
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| q | string | Partial query |
| type | string | users, posts, hashtags |
Response (200 OK):
{
"success": true,
"data": {
"suggestions": [
{
"type": "user",
"username": "jane_smith",
"profile_picture": "https://cdn.linkedup.com/profiles/456.jpg"
},
{
"type": "hashtag",
"tag": "javascript"
}
]
}
}Base URL: /api/v1/network/
GET /network/
Description: Get network overview
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"connections_count": 248,
"pending_requests_count": 5,
"received_requests_count": 3,
"suggestions_count": 12,
"stats": {
"total_interactions": 450,
"new_connections_this_month": 15
}
}
}GET /network/requests/received/
Description: Get pending connection requests received
Authentication: Required ✅
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number |
| page_size | integer | 20 | Results per page |
| status | string | pending | pending, accepted, ignored |
Response (200 OK):
{
"success": true,
"data": [
{
"request_id": 789,
"from_user": {
"user_id": 789,
"username": "alice_wonder",
"profile_picture": "https://cdn.linkedup.com/profiles/789.jpg",
"bio": "Designer & artist",
"mutual_connections": 5
},
"message": "Let's connect!",
"created_at": "2026-01-20T14:30:00Z",
"status": "pending"
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total_count": 3
}
}GET /network/requests/sent/
Description: Get pending requests you sent
Authentication: Required ✅
POST /network/requests/{username}/accept/
Description: Accept a connection request
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"message": "Connection accepted",
"connection": {
"user_id": 789,
"username": "alice_wonder",
"profile_picture": "https://cdn.linkedup.com/profiles/789.jpg"
}
}
}POST /network/requests/{username}/ignore/
Description: Ignore/decline a connection request
Authentication: Required ✅
GET /network/connections/
Description: Get list of all connections
Authentication: Required ✅
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number |
| page_size | integer | 50 | Results per page |
| sort | string | newest | newest, alphabetical |
| filter | string | all | all, active, inactive |
GET /network/suggestions/
Description: Get suggested connections
Authentication: Required ✅
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
| limit | integer | Number of suggestions (max 50) |
Response (200 OK):
{
"success": true,
"data": [
{
"user_id": 900,
"username": "bob_builder",
"profile_picture": "https://cdn.linkedup.com/profiles/900.jpg",
"bio": "Software architect",
"mutual_connections": 8,
"reason": "5 mutual connections"
}
]
}Base URL: /api/v1/premium/
GET /premium/status/
Description: Get current premium subscription status
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": {
"is_premium": true,
"plan": "pro",
"subscription_id": "sub_12345",
"started_at": "2025-10-15T10:00:00Z",
"renews_at": "2026-10-15T10:00:00Z",
"cancels_at": null,
"status": "active",
"features": [
"analytics",
"advanced_search",
"priority_support",
"custom_domain"
]
}
}POST /premium/upgrade/
Description: Start premium subscription
Authentication: Required ✅
Request Body:
{
"plan": "pro",
"payment_method_id": "pm_1234567890",
"billing_cycle": "monthly"
}Billing Cycles:
monthly- $9.99/monthannual- $99.99/year (save 17%)
Response (201 Created):
{
"success": true,
"data": {
"message": "Premium subscription activated",
"subscription": {
"subscription_id": "sub_new_12345",
"plan": "pro",
"status": "active",
"next_billing_date": "2026-02-21T10:00:00Z",
"price": 9.99,
"currency": "USD"
}
}
}POST /premium/cancel/
Description: Cancel premium subscription
Authentication: Required ✅
Request Body:
{
"reason": "Too expensive",
"feedback": "Good service but doesn't fit my needs"
}Response (200 OK):
{
"success": true,
"data": {
"message": "Subscription will cancel on renewal",
"cancels_at": "2026-10-15T10:00:00Z",
"access_until": "2026-10-15T10:00:00Z"
}
}GET /premium/history/
Description: Get billing history and invoices
Authentication: Required ✅
Response (200 OK):
{
"success": true,
"data": [
{
"invoice_id": "inv_123456",
"amount": 9.99,
"currency": "USD",
"date": "2026-01-15T10:00:00Z",
"status": "paid",
"description": "Premium Pro Monthly Subscription",
"download_url": "https://cdn.linkedup.com/invoices/inv_123456.pdf"
}
]
}Base URL: /api/v1/analytics/
GET /analytics/me/{username}/impressions/
Description: Get profile view impressions over time
Authentication: Required ✅ (Premium required)
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| period | string | 30d | 7d, 30d, 90d, 1y |
| grouping | string | day | day, week, month |
Response (200 OK):
{
"success": true,
"data": {
"total_impressions": 5420,
"average_daily": 180,
"trend": "up",
"trend_percentage": 15,
"chart_data": [
{
"date": "2026-01-21",
"impressions": 230,
"unique_viewers": 180
}
]
}
}GET /analytics/me/{username}/profile-views/
Description: Get profile view analytics
Authentication: Required ✅ (Premium required)
Query Parameters: Same as impressions
Response:
{
"success": true,
"data": {
"total_views": 320,
"unique_viewers": 250,
"returning_rate": 22,
"top_referrers": [
{
"source": "search",
"views": 145
}
]
}
}GET /analytics/me/{username}/post-engagement/
Description: Get engagement analytics for all posts
Authentication: Required ✅ (Premium required)
Query Parameters: Same as impressions
GET /analytics/me/{username}/followers-growth/
Description: Get followers growth over time
Authentication: Required ✅ (Premium required)
// ✅ DO: Store tokens securely
localStorage.setItem('accessToken', token);
localStorage.setItem('refreshToken', refreshToken);
// ❌ DON'T: Store sensitive data in localStorage
localStorage.setItem('password', password); // Never!
// ✅ DO: Always include Authorization header
const headers = {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};
// ✅ DO: Handle token expiration
const response = await makeRequest('/api/endpoint');
if (response.status === 401) {
await refreshToken();
return retryRequest();
}// ✅ DO: Comprehensive error handling
try {
const response = await linkedUpAPI.post('/auth/login/', credentials);
return response;
} catch (error) {
if (error.status_code === 400) {
// Validation error - show specific field errors
displayFieldErrors(error.errors);
} else if (error.status_code === 401) {
// Authentication error
redirectToLogin();
} else if (error.status_code === 429) {
// Rate limited
showRateLimitMessage();
} else {
// Generic error
showErrorMessage(error.message);
}
}
// ✅ DO: Implement retry logic
async function makeRequestWithRetry(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
await delay(1000 * Math.pow(2, i)); // Exponential backoff
}
}
}// ✅ DO: Implement pagination
async function loadUserPosts(username, page = 1) {
const response = await linkedUpAPI.get(
`/users/${username}/posts/?page=${page}&page_size=20`
);
return response;
}
// ✅ DO: Debounce search queries
let searchTimeout;
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
performSearch(e.target.value);
}, 300);
});
// ✅ DO: Cache frequently accessed data
const cache = {};
async function getCachedUserProfile(username) {
if (cache[username]) return cache[username];
const profile = await linkedUpAPI.get(`/users/${username}/`);
cache[username] = profile;
return profile;
}
// ✅ DO: Implement infinite scroll efficiently
let isLoading = false;
window.addEventListener('scroll', async () => {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500) {
if (!isLoading) {
isLoading = true;
await loadMorePosts();
isLoading = false;
}
}
});// ✅ DO: Use WebSocket for real-time notifications
const ws = new WebSocket(
`wss://api.linkedup.com/ws/notifications/?token=${accessToken}`
);
ws.onmessage = (event) => {
const notification = JSON.parse(event.data);
showNotificationToast(notification);
updateNotificationBadge();
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
// Fallback to polling
startPollingNotifications();
};
ws.onclose = () => {
// Attempt reconnect
setTimeout(() => {
console.log('Reconnecting WebSocket...');
initializeWebSocket();
}, 5000);
};
// ✅ DO: Implement reconnection logic
function initializeWebSocket() {
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
function connect() {
ws = new WebSocket(wsUrl);
ws.onopen = () => {
reconnectAttempts = 0;
};
ws.onclose = () => {
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
setTimeout(connect, delay);
}
};
}
connect();
}// ✅ DO: Validate input before sending
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function validatePassword(password) {
// At least 8 chars, uppercase, lowercase, number, special char
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
return passwordRegex.test(password);
}
// ✅ DO: Handle API validation errors
try {
await linkedUpAPI.post('/auth/register/', formData);
} catch (error) {
if (error.errors) {
// Server validation errors
Object.entries(error.errors).forEach(([field, messages]) => {
displayFieldError(field, messages[0]);
});
}
}// ✅ DO: Implement request throttling
class ThrottledAPI {
constructor(maxRequests = 100, timeWindow = 60000) {
this.maxRequests = maxRequests;
this.timeWindow = timeWindow;
this.requests = [];
}
async request(method, url, body) {
const now = Date.now();
this.requests = this.requests.filter(t => now - t < this.timeWindow);
if (this.requests.length >= this.maxRequests) {
const waitTime = this.timeWindow - (now - this.requests[0]);
await new Promise(resolve => setTimeout(resolve, waitTime));
return this.request(method, url, body);
}
this.requests.push(now);
return fetch(url, { method, body }).then(r => r.json());
}
}
const throttledAPI = new ThrottledAPI(100, 60000);// ✅ DO: Use TypeScript for type safety
interface User {
user_id: number;
username: string;
email: string;
profile_picture?: string;
bio: string;
followers_count: number;
is_premium: boolean;
created_at: string;
}
interface Post {
post_id: number;
post_uid: string;
author: User;
content: string;
images: Image[];
created_at: string;
engagement: {
likes_count: number;
comments_count: number;
shares_count: number;
};
is_liked: boolean;
}
interface APIResponse<T> {
success: boolean;
data: T;
error?: {
message: string;
status_code: number;
errors?: Record<string, string[]>;
};
pagination?: {
page: number;
page_size: number;
total_count: number;
};
}
async function getUser(username: string): Promise<User> {
const response: APIResponse<User> = await linkedUpAPI.get(
`/users/${username}/`
);
return response.data;
}Cause: Access token has expired
Solution:
async function makeRequestWithAutoRefresh(method, endpoint, body) {
try {
return await linkedUpAPI.request(method, endpoint, body);
} catch (error) {
if (error.status_code === 401) {
// Refresh token
const refreshResponse = await linkedUpAPI.post('/auth/refresh/', {
refresh_token: localStorage.getItem('refreshToken'),
});
linkedUpAPI.setToken(refreshResponse.access_token);
localStorage.setItem('accessToken', refreshResponse.access_token);
// Retry original request
return linkedUpAPI.request(method, endpoint, body);
}
throw error;
}
}Cause: Browser blocking cross-origin requests
Solution: Use proper CORS headers (must be configured on backend)
// Frontend: Ensure proper headers
const headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
// Backend should return:
// Access-Control-Allow-Origin: https://yourdomain.com
// Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
// Access-Control-Allow-Headers: Content-Type, AuthorizationCause: Request taking too long
Solution:
async function requestWithTimeout(promise, timeoutMs = 10000) {
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), timeoutMs)
);
return Promise.race([promise, timeoutPromise]);
}
const response = await requestWithTimeout(
linkedUpAPI.get('/slow-endpoint/'),
5000
);Cause: Duplicate email in system
Solution:
try {
await linkedUpAPI.post('/auth/register/', {
username: 'newuser',
email: 'existing@example.com',
password: 'Password123!',
});
} catch (error) {
if (error.status_code === 409) {
showErrorMessage('Email already registered. Please login instead.');
}
}Cause: File too large or invalid format
Solution:
async function uploadProfilePicture(file) {
// Validate file
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
showErrorMessage('Image must be less than 5MB');
return;
}
if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {
showErrorMessage('Only JPEG, PNG, and WebP supported');
return;
}
// Upload
const formData = new FormData();
formData.append('profile_picture', file);
try {
const response = await fetch(
`${linkedUpAPI.baseURL}/profile/me/${currentUsername}/`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${linkedUpAPI.token}`,
},
body: formData,
}
);
return response.json();
} catch (error) {
console.error('Upload failed:', error);
}
}// Quick test suite
const apiTests = {
async testAuth() {
console.log('Testing authentication...');
// Register
const registerResp = await linkedUpAPI.post('/auth/register/', {
username: 'testuser_' + Date.now(),
email: `test_${Date.now()}@example.com`,
password: 'TestPassword123!',
});
console.assert(registerResp.user_id, 'Registration failed');
// Login
const loginResp = await linkedUpAPI.post('/auth/login/', {
email: registerResp.email,
password: 'TestPassword123!',
});
console.assert(loginResp.access_token, 'Login failed');
console.log('✅ Auth tests passed');
},
async testPosts() {
console.log('Testing posts...');
// Create post
const postResp = await linkedUpAPI.post('/posts/', {
content: 'Test post ' + Date.now(),
visibility: 'public',
});
console.assert(postResp.post_uid, 'Post creation failed');
// Get post
const getResp = await linkedUpAPI.get(`/posts/${postResp.post_uid}/`);
console.assert(getResp.post_id === postResp.post_id, 'Get post failed');
console.log('✅ Post tests passed');
},
};
// Run tests
await apiTests.testAuth();
await apiTests.testPosts();- Standard: 100 requests per minute per IP
- Premium: 500 requests per minute per IP
- Enterprise: Unlimited (custom SLA)
Rate Limit Headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1642788000
- Documentation: https://docs.linkedup.com
- API Status: https://status.linkedup.com
- Support Email: support@linkedup.com
- Community: https://community.linkedup.com
Last Updated: January 21, 2026
API Version: 1.0.0
Status: Production Ready