Games API Documentation
Overview
The Games API provides a comprehensive set of endpoints for managing users, games, game categories, voting functionality, friend connections, and real-time chat system. This RESTful API supports multilingual content through translation support and includes authentication via Laravel Sanctum.
Base URL: https://your-domain.com/api
API Version: v1
Authentication: Bearer Token (Laravel Sanctum)
Authentication Flow
The API uses a simplified phone-based OTP (One-Time Password) authentication system:
- OTP Request: Users request an OTP by providing their phone number. If the phone number is not registered, a new user account is created automatically.
- OTP Verification: Users verify the OTP to receive an authentication token. New users may need to complete their profile after authentication.
- Profile Completion: New users should update their profile with first name, last name, and other optional information.
- API Access: Use the Bearer token for authenticated endpoints
Note: The OTP is currently hardcoded to "123456" for testing purposes. Traditional username/password authentication has been completely replaced with the more secure and streamlined OTP-based flow.
Table of Contents
- Authentication
- Games
- Game Categories
- Game Comments
- Game Voting
- User Management
- Friend Requests
- Stories
- Real-Time Chat System
- Messages
- Leaderboard
- Payment System
- Subscription System
- Error Handling
- Headers & Language Support
Authentication
Send OTP
Send an OTP (One-Time Password) to a phone number for authentication. If the phone number is not registered, a new user account will be created automatically.
Endpoint: POST /send-otp
Authentication: None required
Request Body
{
"phone": "string (required)"
}
Response (200 OK)
{
"success": true,
"message": "OTP sent successfully to your phone number",
"phone": "+1234567890",
"is_new_user": true
}
Response Fields
success: Boolean indicating if the request was successfulmessage: Descriptive message about the operationphone: The phone number the OTP was sent tois_new_user: Boolean indicating if this is a new user who needs to complete their profile
Error Responses
401 Unauthorized (inactive account):
{
"success": false,
"message": "Account is inactive"
}
Verify OTP
Verify the OTP code and authenticate the user.
Endpoint: POST /verify-otp
Authentication: None required
Request Body
{
"phone": "string (required)",
"otp": "string (required, 6 digits)"
}
Response (200 OK)
{
"success": true,
"message": "Login successful",
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"phone": "+1234567890",
"email": "john@example.com",
"birth_date": "1990-01-01",
"photo": "http://domain.com/storage/users/photos/photo.jpg",
"is_active": true,
"phone_verified": true,
"last_login": "2025-07-19T12:00:00.000000Z",
"created_at": "2025-07-19T00:00:00.000000Z"
},
"token": "1|token_string_here",
"token_type": "Bearer",
"profile_complete": true
}
Response Fields
success: Boolean indicating if the request was successfulmessage: Descriptive message about the operationuser: User object with complete profile informationtoken: Authentication token for subsequent API callstoken_type: Type of token (always "Bearer")profile_complete: Boolean indicating if the user has completed their profile (has first_name and last_name)
Error Responses
404 Not Found (phone number not found):
{
"success": false,
"message": "Phone number not found"
}
401 Unauthorized (inactive account):
{
"success": false,
"message": "Account is inactive"
}
401 Unauthorized (invalid OTP):
{
"success": false,
"message": "Invalid OTP"
}
401 Unauthorized (expired OTP):
{
"success": false,
"message": "OTP has expired"
}
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"phone": ["The phone field is required."],
"otp": ["The otp field is required.", "The otp must be 6 characters."]
}
}
Login User (Deprecated)
Note: The traditional username/password login is deprecated. Please use the OTP-based authentication flow instead.
Authenticate a user and return an access token.
Endpoint: POST /login
Authentication: None required
Request Body
{
"username": "string (required)",
"password": "string (required)"
}
Response (200 OK)
{
"success": true,
"message": "Login successful",
"user": {
"id": 1,
"username": "john_doe",
"first_name": "John",
"last_name": "Doe",
"phone": "+1234567890",
"email": "john@example.com",
"birth_date": "1990-01-01",
"photo": "http://domain.com/storage/users/photos/photo.jpg",
"is_active": true,
"last_login": "2025-07-19T12:00:00.000000Z",
"created_at": "2025-07-19T00:00:00.000000Z"
},
"token": "1|token_string_here",
"token_type": "Bearer"
}
Logout User
Revoke the current access token.
Endpoint: POST /logout
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"message": "Logged out successfully"
}
Games
Get Home Content
Retrieve featured content for the home page, including featured games and categories.
Endpoint: GET /home
Authentication: None required
Headers
language(string, optional): Language code for translations (default: 'en')
Response (200 OK)
{
"featured_games": [
{
"id": 1,
"slug": "puzzle-game",
"name": "Amazing Puzzle Game",
"description": "An exciting puzzle game with challenging levels",
"category": {
"id": 1,
"name": "Puzzle"
},
"source": "GameStudio",
"thumb_1": "http://domain.com/thumbs/game1_large.jpg",
"thumb_2": "http://domain.com/thumbs/game1_medium.jpg",
"thumb_small": "http://domain.com/thumbs/game1_small.jpg",
"require_subscription": false,
"upvotes_count": 150,
"downvotes_count": 12,
"net_votes": 138,
"user_vote": null
}
],
"featured_categories": [
{
"id": 1,
"slug": "puzzle",
"name": "Puzzle",
"description": "Brain-challenging puzzle games",
"image": "http://domain.com/images/puzzle-category.jpg",
"games_count": 25
}
]
}
Get All Games
Retrieve a paginated list of all games with voting information.
Endpoint: GET /games
Authentication: None required
Query Parameters
per_page(integer, optional): Number of games per page (default: 15)category_id(integer, optional): Filter by category IDpage(integer, optional): Page number
Headers
language(string, optional): Language code for translations (default: 'en')
Response (200 OK)
{
"current_page": 1,
"data": [
{
"id": 1,
"slug": "puzzle-game",
"name": "Amazing Puzzle Game",
"description": "An exciting puzzle game with challenging levels",
"category": {
"id": 1,
"name": "Puzzle"
},
"source": "GameStudio",
"thumb_1": "http://domain.com/thumbs/game1_large.jpg",
"thumb_2": "http://domain.com/thumbs/game1_medium.jpg",
"thumb_small": "http://domain.com/thumbs/game1_small.jpg",
"require_subscription": false,
"upvotes_count": 150,
"downvotes_count": 12,
"net_votes": 138,
"user_vote": null
}
],
"first_page_url": "http://domain.com/api/games?page=1",
"from": 1,
"last_page": 5,
"last_page_url": "http://domain.com/api/games?page=5",
"next_page_url": "http://domain.com/api/games?page=2",
"path": "http://domain.com/api/games",
"per_page": 15,
"prev_page_url": null,
"to": 15,
"total": 75
}
Get Free Games
Retrieve a paginated list of games that don't require subscription.
Endpoint: GET /games/free
Authentication: None required
Query Parameters
Same as /games endpoint.
Response
Same structure as /games endpoint, but only includes games where require_subscription is false.
Get Single Game
Retrieve detailed information about a specific game.
Endpoint: GET /games/{id}
Authentication: Required for subscription games
Headers
language(string, optional): Language code for translations (default: 'en')
Response (200 OK)
{
"id": 1,
"slug": "puzzle-game",
"name": "Amazing Puzzle Game",
"description": "An exciting puzzle game with challenging levels",
"instructions": "Use arrow keys to move, space to jump",
"category": {
"id": 1,
"name": "Puzzle",
"description": "Brain-challenging puzzle games"
},
"source": "GameStudio",
"thumb_1": "http://domain.com/thumbs/game1_large.jpg",
"thumb_2": "http://domain.com/thumbs/game1_medium.jpg",
"thumb_small": "http://domain.com/thumbs/game1_small.jpg",
"url": "http://domain.com/games/puzzle-game",
"width": 800,
"height": 600,
"require_subscription": false,
"upvotes_count": 150,
"downvotes_count": 12,
"net_votes": 138,
"user_vote": "upvote"
}
Error Responses
401 Unauthorized (for subscription games without authentication):
{
"error": "Authentication required",
"message": "This game requires an active subscription. Please log in to access it."
}
403 Forbidden (for inactive users accessing subscription games):
{
"error": "Subscription required",
"message": "This game requires an active subscription. Please activate your subscription to access it."
}
Game Categories
Get All Categories
Retrieve a list of all game categories with game counts.
Endpoint: GET /categories
Authentication: None required
Headers
language(string, optional): Language code for translations (default: 'en')
Response (200 OK)
[
{
"id": 1,
"slug": "puzzle",
"name": "Puzzle",
"description": "Brain-challenging puzzle games",
"image": "http://domain.com/images/puzzle-category.jpg",
"games_count": 25
},
{
"id": 2,
"slug": "action",
"name": "Action",
"description": "Fast-paced action games",
"image": "http://domain.com/images/action-category.jpg",
"games_count": 30
}
]
Get Category with Games
Retrieve a specific category with its associated games.
Endpoint: GET /categories/{category}
Authentication: None required
Headers
language(string, optional): Language code for translations (default: 'en')
Response (200 OK)
{
"id": 1,
"name": "Puzzle",
"description": "Brain-challenging puzzle games",
"image": "http://domain.com/images/puzzle-category.jpg",
"games": [
{
"id": 1,
"slug": "puzzle-game",
"name": "Amazing Puzzle Game",
"description": "An exciting puzzle game",
"thumb_small": "http://domain.com/thumbs/game1_small.jpg",
"require_subscription": false
}
]
}
Game Comments
Get Game Comments
Retrieve comments for a specific game with pagination.
Endpoint: GET /games/{game}/comments
Authentication: None required
Query Parameters
page(integer, optional): Page number for paginationper_page(integer, optional): Number of comments per page (default: 15)
Response (200 OK)
{
"current_page": 1,
"data": [
{
"id": 1,
"content": "This is an amazing game! Love the graphics.",
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"photo": "http://domain.com/storage/users/photos/photo.jpg"
},
"created_at": "2025-07-19T12:00:00.000000Z",
"updated_at": "2025-07-19T12:00:00.000000Z"
}
],
"first_page_url": "http://domain.com/api/games/1/comments?page=1",
"from": 1,
"last_page": 3,
"last_page_url": "http://domain.com/api/games/1/comments?page=3",
"next_page_url": "http://domain.com/api/games/1/comments?page=2",
"path": "http://domain.com/api/games/1/comments",
"per_page": 15,
"prev_page_url": null,
"to": 15,
"total": 45
}
Create Game Comment
Add a new comment to a game.
Endpoint: POST /games/{game}/comments
Authentication: Required (Bearer Token)
Request Body
{
"content": "string (required, max:1000 characters)"
}
Response (201 Created)
{
"success": true,
"message": "Comment added successfully",
"data": {
"id": 1,
"content": "This is an amazing game! Love the graphics.",
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"photo": "http://domain.com/storage/users/photos/photo.jpg"
},
"created_at": "2025-07-19T12:00:00.000000Z",
"updated_at": "2025-07-19T12:00:00.000000Z"
}
}
Error Responses
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"content": [
"The content field is required.",
"The content may not be greater than 1000 characters."
]
}
}
Update Game Comment
Update an existing comment (only by the comment owner).
Endpoint: PUT /games/{game}/comments/{comment}
Authentication: Required (Bearer Token)
Request Body
{
"content": "string (required, max:1000 characters)"
}
Response (200 OK)
{
"success": true,
"message": "Comment updated successfully",
"data": {
"id": 1,
"content": "Updated comment content",
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"photo": "http://domain.com/storage/users/photos/photo.jpg"
},
"created_at": "2025-07-19T12:00:00.000000Z",
"updated_at": "2025-07-19T12:30:00.000000Z"
}
}
Error Responses
403 Forbidden (not comment owner):
{
"success": false,
"message": "You can only edit your own comments"
}
404 Not Found (comment not found):
{
"success": false,
"message": "Comment not found"
}
Delete Game Comment
Delete an existing comment (only by the comment owner).
Endpoint: DELETE /games/{game}/comments/{comment}
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"message": "Comment deleted successfully"
}
Error Responses
403 Forbidden (not comment owner):
{
"success": false,
"message": "You can only delete your own comments"
}
404 Not Found (comment not found):
{
"success": false,
"message": "Comment not found"
}
Game Voting
Vote on Game
Cast or update a vote (upvote/downvote) on a game.
Endpoint: POST /games/{id}/vote
Authentication: Required (Bearer Token)
Request Body
{
"vote_type": "upvote" // or "downvote"
}
Response (200 OK)
{
"message": "Vote added successfully", // or "Vote updated successfully" or "Vote removed successfully"
"upvotes_count": 151,
"downvotes_count": 12,
"user_vote": "upvote"
}
Error Responses
404 Not Found (game not found):
{
"message": "Game not found"
}
422 Unprocessable Entity (validation error):
{
"message": "The selected vote type is invalid.",
"errors": {
"vote_type": [
"The vote type field is required.",
"The selected vote type is invalid."
]
}
}
Notes
- If user votes with the same vote type twice, the vote is removed (toggle off)
- If user changes vote type, the existing vote is updated
- Vote counts are returned in real-time
Get User Votes
Retrieve all games voted by the authenticated user.
Endpoint: GET /user/votes
Authentication: Required (Bearer Token)
Response (200 OK)
{
"upvoted_games": [
{
"id": 1,
"name": "Amazing Puzzle Game",
"slug": "puzzle-game",
"thumb_small": "http://domain.com/thumbs/game1_small.jpg"
}
],
"downvoted_games": [
{
"id": 2,
"name": "Boring Game",
"slug": "boring-game",
"thumb_small": "http://domain.com/thumbs/game2_small.jpg"
}
]
}
User Management
Get Current User
Retrieve the authenticated user's profile information.
Endpoint: GET /user
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"phone": "+1234567890",
"email": "john@example.com",
"birth_date": "1990-01-01",
"photo": "http://domain.com/storage/users/photos/photo.jpg",
"is_active": true,
"phone_verified": true,
"last_login": "2025-07-19T12:00:00.000000Z",
"created_at": "2025-07-19T00:00:00.000000Z"
}
}
Update User Profile
Update the authenticated user's profile information.
Endpoint: PUT /profile
Authentication: Required (Bearer Token)
Request Body
{
"first_name": "string (optional, required if provided, max:255)",
"last_name": "string (optional, required if provided, max:255)",
"phone": "string (optional, required if provided, max:20, unique)",
"email": "string (optional, nullable, email, max:255, unique)",
"birth_date": "date (optional, nullable, before today)",
"photo": "file (optional, nullable, image: jpeg,jpg,png, max:2MB)"
}
Response (200 OK)
{
"success": true,
"message": "Profile updated successfully",
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"phone": "+1234567890",
"email": "john@example.com",
"birth_date": "1990-01-01",
"photo": "http://domain.com/storage/users/photos/photo.jpg",
"is_active": true,
"phone_verified": true,
"last_login": "2025-07-19T12:00:00.000000Z",
"created_at": "2025-07-19T00:00:00.000000Z"
}
}
Error Responses
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"email": ["The email has already been taken."],
"photo": ["The photo may not be greater than 2048 kilobytes."],
"birth_date": ["The birth date must be a date before today."]
}
}
Update User Photo
Update only the authenticated user's profile photo.
Endpoint: POST /profile/photo
Authentication: Required (Bearer Token)
Request Body (multipart/form-data)
photo: file (required, image: jpeg,jpg,png, max:2MB)
Response (200 OK)
{
"success": true,
"message": "Photo updated successfully",
"photo": "http://domain.com/storage/users/photos/new_photo.jpg",
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"phone": "+1234567890",
"email": "john@example.com",
"birth_date": "1990-01-01",
"photo": "http://domain.com/storage/users/photos/new_photo.jpg",
"is_active": true,
"phone_verified": true,
"last_login": "2025-07-19T12:00:00.000000Z",
"created_at": "2025-07-19T00:00:00.000000Z"
}
}
Error Responses
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"photo": [
"The photo field is required.",
"The photo may not be greater than 2048 kilobytes."
]
}
}
Delete User Photo
Remove the authenticated user's profile photo.
Endpoint: DELETE /profile/photo
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"message": "Photo deleted successfully",
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"phone": "+1234567890",
"email": "john@example.com",
"birth_date": "1990-01-01",
"photo": null,
"is_active": true,
"phone_verified": true,
"last_login": "2025-07-19T12:00:00.000000Z",
"created_at": "2025-07-19T00:00:00.000000Z"
}
}
Error Response (404 Not Found)
{
"success": false,
"message": "No photo to delete"
}
Friend Requests
The friend request system allows users to connect with each other by sending, accepting, or rejecting friend requests.
Friend Request Statuses
- pending: Friend request has been sent but not yet responded to
- accepted: Friend request has been accepted, users are now friends
- rejected: Friend request has been rejected
Get Pending Friend Requests
Retrieve pending friend requests received by the authenticated user.
Endpoint: GET /friend-requests
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"data": [
{
"id": 2,
"sender_id": 3,
"receiver_id": 1,
"status": "pending",
"created_at": "2025-07-19T11:00:00.000000Z",
"updated_at": "2025-07-19T11:00:00.000000Z",
"sender": {
"id": 3,
"first_name": "Bob",
"last_name": "Smith",
"phone": "+1234567892",
"photo": null
}
}
]
}
Send Friend Request
Send a friend request to another user.
Endpoint: POST /friend-requests
Authentication: Required (Bearer Token)
Request Body
{
"receiver_id": "integer (required, exists in users table)"
}
Response (201 Created)
{
"success": true,
"message": "Friend request sent successfully.",
"data": {
"id": 1,
"sender_id": 1,
"receiver_id": 2,
"status": "pending",
"created_at": "2025-07-19T10:00:00.000000Z",
"updated_at": "2025-07-19T10:00:00.000000Z",
"receiver": {
"id": 2,
"first_name": "Jane",
"last_name": "Doe",
"phone": "+1234567891",
"photo": "http://domain.com/storage/users/photos/jane.jpg"
}
}
}
Error Responses
400 Bad Request (trying to send request to yourself):
{
"success": false,
"message": "You cannot send a friend request to yourself."
}
400 Bad Request (already friends):
{
"success": false,
"message": "You are already friends with this user."
}
400 Bad Request (pending request exists):
{
"success": false,
"message": "A friend request is already pending between you and this user."
}
500 Internal Server Error (failed to send request):
{
"success": false,
"message": "Failed to send friend request."
}
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"receiver_id": [
"The receiver id field is required.",
"The selected receiver id is invalid."
]
}
}
Respond to Friend Request
Accept or reject a received friend request.
Endpoint: PUT /friend-requests/{friendRequest}
Authentication: Required (Bearer Token)
Request Body
{
"action": "accept" // or "reject"
}
Response (200 OK)
{
"success": true,
"message": "Friend request accepted.", // or "Friend request rejected."
"data": {
"id": 2,
"sender_id": 3,
"receiver_id": 1,
"status": "accepted", // or "rejected"
"created_at": "2025-07-19T11:00:00.000000Z",
"updated_at": "2025-07-19T12:00:00.000000Z",
"sender": {
"id": 3,
"first_name": "Bob",
"last_name": "Smith",
"phone": "+1234567892",
"photo": null
}
}
}
Error Responses
403 Forbidden (not the receiver):
{
"success": false,
"message": "You can only respond to friend requests sent to you."
}
400 Bad Request (already responded):
{
"success": false,
"message": "This friend request has already been responded to."
}
404 Not Found (friend request not found):
{
"success": false,
"message": "Friend request not found."
}
500 Internal Server Error (failed to process request):
{
"success": false,
"message": "Failed to accept friend request." // or "Failed to reject friend request."
}
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"action": [
"The action field is required.",
"The selected action is invalid."
]
}
}
Get Friends List
Retrieve the list of all friends for the authenticated user.
Endpoint: GET /friends
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"data": [
{
"id": 2,
"first_name": "Jane",
"last_name": "Doe",
"phone": "+1234567891",
"email": "jane@example.com",
"birth_date": "1992-05-15",
"photo": "http://domain.com/storage/users/photos/jane.jpg",
"is_active": true,
"phone_verified": true,
"last_login": "2025-07-19T11:30:00.000000Z",
"created_at": "2025-07-18T08:00:00.000000Z"
}
]
}
Delete Friend Request
Delete a friend request (either cancel a sent request or remove a friendship).
Endpoint: DELETE /friend-requests/{friendRequest}
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"message": "Friend request deleted successfully."
}
Error Responses
404 Not Found (friend request not found):
{
"success": false,
"message": "Friend request not found."
}
403 Forbidden (not authorized):
{
"success": false,
"message": "You can only delete your own friend requests."
}
Stories
The Stories feature allows users to share temporary content (text and/or images) with their friends that automatically expires after 24 hours. Stories are only visible to users who are friends with the story creator.
Story Lifecycle
- Creation: Stories are created with content and/or an image
- Expiration: Stories automatically expire 24 hours after creation
- Visibility: Only friends can view active stories
- Privacy: Expired stories are only visible to the story owner
Get Friends' Stories
Retrieve active stories from the authenticated user's friends.
Endpoint: GET /stories
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"data": [
{
"id": 1,
"content": "Having a great day at the beach!",
"image_url": "http://domain.com/storage/stories/beach_photo.jpg",
"created_at": "2025-07-20T10:00:00.000000Z",
"expires_at": "2025-07-21T10:00:00.000000Z",
"user": {
"id": 2,
"first_name": "Jane",
"last_name": "Doe",
"phone": "+1234567891",
"photo": "http://domain.com/storage/users/photos/jane.jpg"
}
}
]
}
Get My Stories
Retrieve all stories created by the authenticated user (including expired ones).
Endpoint: GET /stories/my
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"data": [
{
"id": 3,
"content": "My first story!",
"image_url": null,
"created_at": "2025-07-19T15:00:00.000000Z",
"expires_at": "2025-07-20T15:00:00.000000Z",
"is_active": false,
"is_expired": true
},
{
"id": 4,
"content": "Current story",
"image_url": "http://domain.com/storage/stories/current.jpg",
"created_at": "2025-07-20T12:00:00.000000Z",
"expires_at": "2025-07-21T12:00:00.000000Z",
"is_active": true,
"is_expired": false
}
]
}
Create Story
Create a new story with content and/or an image.
Endpoint: POST /stories
Authentication: Required (Bearer Token)
Request Body (multipart/form-data)
content: string (optional, max:1000 characters)
image: file (optional, image: jpeg,png,jpg,gif, max:10MB)
Validation Rules
- Either
contentorimagemust be provided (at least one is required) - If both are provided, they will both be saved
- Stories automatically expire 24 hours after creation
Response (201 Created)
{
"success": true,
"message": "Story created successfully.",
"data": {
"id": 5,
"content": "Just created my new story!",
"image_url": "http://domain.com/storage/stories/new_story.jpg",
"created_at": "2025-07-20T14:00:00.000000Z",
"expires_at": "2025-07-21T14:00:00.000000Z",
"is_active": true
}
}
Error Responses
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"content": ["Either content or image must be provided."],
"image": ["The image may not be greater than 10MB."]
}
}
Get Single Story
Retrieve a specific story by ID.
Endpoint: GET /stories/{id}
Authentication: Required (Bearer Token)
Access Rules
- Users can always view their own stories (active or expired)
- Users can only view friends' active stories
- Expired stories are only visible to the story owner
Response (200 OK)
{
"success": true,
"data": {
"id": 1,
"content": "Having a great day at the beach!",
"image_url": "http://domain.com/storage/stories/beach_photo.jpg",
"created_at": "2025-07-20T10:00:00.000000Z",
"expires_at": "2025-07-21T10:00:00.000000Z",
"is_active": true,
"is_expired": false,
"user": {
"id": 2,
"first_name": "Jane",
"last_name": "Doe",
"phone": "+1234567891",
"photo": "http://domain.com/storage/users/photos/jane.jpg"
}
}
}
Error Responses
403 Forbidden (insufficient permissions):
{
"success": false,
"message": "You do not have permission to view this story."
}
404 Not Found (story doesn't exist):
{
"success": false,
"message": "Story not found"
}
Update Story
Update the content of an existing story. Note: Images cannot be updated, only text content.
Endpoint: PUT /stories/{id}
Authentication: Required (Bearer Token)
Request Body
{
"content": "string (required, max:1000 characters)"
}
Access Rules
- Only the story owner can update their own stories
- Both active and expired stories can be updated
Response (200 OK)
{
"success": true,
"message": "Story updated successfully.",
"data": {
"id": 5,
"content": "Updated story content!",
"image_url": "http://domain.com/storage/stories/original_image.jpg",
"created_at": "2025-07-20T14:00:00.000000Z",
"expires_at": "2025-07-21T14:00:00.000000Z",
"is_active": true
}
}
Error Responses
403 Forbidden (not the story owner):
{
"success": false,
"message": "You can only update your own stories."
}
Delete Story
Delete a story and its associated image (if any).
Endpoint: DELETE /stories/{id}
Authentication: Required (Bearer Token)
Access Rules
- Only the story owner can delete their own stories
- Both active and expired stories can be deleted
- Associated images are automatically removed from storage
Response (200 OK)
{
"success": true,
"message": "Story deleted successfully."
}
Error Responses
403 Forbidden (not the story owner):
{
"success": false,
"message": "You can only delete your own stories."
}
404 Not Found (story doesn't exist):
{
"success": false,
"message": "Story not found"
}
Real-Time Chat System
The Real-Time Chat System provides comprehensive messaging functionality using Pusher for WebSocket broadcasting. Users can engage in private conversations with their friends through secure, real-time messaging.
Features
- Private Messaging: Users can chat with their friends only
- Real-time Broadcasting: Messages are broadcasted instantly using Pusher
- Message History: Paginated message history
- Read Receipts: Track which messages have been read
- Message Editing/Deletion: Users can edit their own messages or delete them
- Unread Message Count: Track unread messages per chat
Database Schema
The chat system uses three main tables:
- chats: Stores chat metadata (type, name, creator, etc.)
- chat_participants: Pivot table for users in chats (with read receipts)
- messages: Stores all messages with content and metadata
Broadcasting Authentication
Authenticate Pusher Channel
Authenticate private Pusher channels for real-time chat functionality.
Endpoint: POST /api/broadcasting/auth
Authentication: Required (Bearer Token)
Request Body
{
"socket_id": "string (required)",
"channel_name": "string (required)"
}
Response (200 OK)
{
"auth": "generated_auth_signature"
}
Error Responses
401 Unauthorized (invalid or missing token):
{
"message": "Unauthenticated."
}
403 Forbidden (not authorized for channel):
{
"message": "This action is unauthorized."
}
Usage Example
This endpoint is typically called automatically by Laravel Echo when subscribing to private channels:
// This is handled automatically by Laravel Echo
echo.private(`chat.${chatId}`).listen(".message.sent", (e) => {
// Handle incoming message
});
Real-time Events
Message Events
message.sent
Broadcasted when a new message is sent.
Channel: private-chat.{chatId}
Payload:
{
"id": 1,
"chat_id": 1,
"user_id": 2,
"content": "Hello!",
"type": "text",
"is_edited": false,
"created_at": "2025-07-21T06:42:19.000000Z",
"user": {
"id": 2,
"first_name": "John",
"last_name": "Doe",
"photo": "photo.jpg"
}
}
message.deleted
Broadcasted when a message is deleted.
Channel: private-chat.{chatId}
Payload:
{
"message_id": 1,
"chat_id": 1
}
Chat Endpoints
Get All Chats
Get all chats for the authenticated user.
Endpoint: GET /api/chats
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"data": [
{
"id": 1,
"type": "private",
"name": "John Doe",
"photo": "user_photo.jpg",
"last_message_at": "2025-07-21T06:42:19.000000Z",
"unread_count": 3,
"latest_message": {
"id": 5,
"content": "Hello there!",
"created_at": "2025-07-21T06:42:19.000000Z",
"user": {
"id": 2,
"first_name": "John",
"last_name": "Doe"
}
},
"participants": [
{
"id": 1,
"first_name": "Current",
"last_name": "User",
"photo": "current_user_photo.jpg"
},
{
"id": 2,
"first_name": "John",
"last_name": "Doe",
"photo": "john_photo.jpg"
}
]
}
]
}
Create Private Chat
Create a new private chat with a friend.
Endpoint: POST /api/chats/private
Authentication: Required (Bearer Token)
Request Body
{
"friend_id": 2
}
Response (201 Created)
{
"success": true,
"message": "Private chat created successfully",
"data": {
"id": 1,
"type": "private",
"name": "John Doe",
"photo": "john_photo.jpg",
"last_message_at": null,
"unread_count": 0,
"latest_message": null,
"participants": [
{
"id": 1,
"first_name": "Current",
"last_name": "User",
"photo": "current_user_photo.jpg"
},
{
"id": 2,
"first_name": "John",
"last_name": "Doe",
"photo": "john_photo.jpg"
}
]
}
}
Error Responses
400 Bad Request (not friends):
{
"success": false,
"message": "You can only create private chats with your friends"
}
409 Conflict (chat already exists):
{
"success": true,
"message": "Private chat already exists",
"data": {
"id": 1,
"type": "private",
"name": "John Doe"
}
}
Get Specific Chat
Get a specific chat with its details.
Endpoint: GET /api/chats/{chat}
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"data": {
"id": 1,
"type": "private",
"name": "John Doe",
"photo": "john_photo.jpg",
"last_message_at": "2025-07-21T06:42:19.000000Z",
"unread_count": 3,
"participants": [
{
"id": 1,
"first_name": "Current",
"last_name": "User",
"photo": "current_user_photo.jpg"
},
{
"id": 2,
"first_name": "John",
"last_name": "Doe",
"photo": "john_photo.jpg"
}
]
}
}
Error Responses
403 Forbidden (not a participant):
{
"success": false,
"message": "You are not a participant in this chat"
}
Mark Chat as Read
Mark all messages in a chat as read.
Endpoint: POST /api/chats/{chat}/mark-read
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"message": "Chat marked as read"
}
Leave Chat
Leave a chat.
Endpoint: POST /api/chats/{chat}/leave
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"message": "Left chat successfully"
}
Delete Chat
Delete a chat (only for admins/creators).
Endpoint: DELETE /api/chats/{chat}
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"message": "Chat deleted successfully"
}
Error Responses
403 Forbidden (insufficient permissions):
{
"success": false,
"message": "You don't have permission to delete this chat"
}
Message Endpoints
Get Chat Messages
Get messages for a chat (paginated).
Endpoint: GET /api/chats/{chat}/messages
Authentication: Required (Bearer Token)
Query Parameters
page: Page number (default: 1)per_page: Messages per page (default: 50)
Response (200 OK)
{
"success": true,
"data": {
"current_page": 1,
"data": [
{
"id": 1,
"chat_id": 1,
"user_id": 2,
"content": "Hello, how are you?",
"type": "text",
"is_edited": false,
"created_at": "2025-07-21T06:42:19.000000Z",
"updated_at": "2025-07-21T06:42:19.000000Z",
"user": {
"id": 2,
"first_name": "John",
"last_name": "Doe",
"photo": "john_photo.jpg"
}
}
],
"first_page_url": "http://domain.com/api/chats/1/messages?page=1",
"from": 1,
"last_page": 3,
"last_page_url": "http://domain.com/api/chats/1/messages?page=3",
"next_page_url": "http://domain.com/api/chats/1/messages?page=2",
"path": "http://domain.com/api/chats/1/messages",
"per_page": 50,
"prev_page_url": null,
"to": 50,
"total": 150
}
}
Send Message
Send a message to a chat.
Endpoint: POST /api/chats/{chat}/messages
Authentication: Required (Bearer Token)
Request Body
{
"content": "Hello, how are you?",
"type": "text"
}
Response (201 Created)
{
"success": true,
"message": "Message sent successfully",
"data": {
"id": 1,
"chat_id": 1,
"user_id": 1,
"content": "Hello, how are you?",
"type": "text",
"is_edited": false,
"created_at": "2025-07-21T06:42:19.000000Z",
"updated_at": "2025-07-21T06:42:19.000000Z",
"user": {
"id": 1,
"first_name": "Current",
"last_name": "User",
"photo": "current_user_photo.jpg"
}
}
}
Error Responses
403 Forbidden (not a participant):
{
"success": false,
"message": "You are not a participant in this chat"
}
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"content": ["The content field is required."],
"type": ["The selected type is invalid."]
}
}
Update Message
Update a message (only your own messages).
Endpoint: PUT /api/messages/{message}
Authentication: Required (Bearer Token)
Request Body
{
"content": "Updated message content"
}
Response (200 OK)
{
"success": true,
"message": "Message updated successfully",
"data": {
"id": 1,
"chat_id": 1,
"user_id": 1,
"content": "Updated message content",
"type": "text",
"is_edited": true,
"created_at": "2025-07-21T06:42:19.000000Z",
"updated_at": "2025-07-21T06:45:30.000000Z",
"user": {
"id": 1,
"first_name": "Current",
"last_name": "User",
"photo": "current_user_photo.jpg"
}
}
}
Error Responses
403 Forbidden (not message owner):
{
"success": false,
"message": "You can only edit your own messages"
}
Delete Message
Delete a message.
Endpoint: DELETE /api/messages/{message}
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"message": "Message deleted successfully"
}
Error Responses
403 Forbidden (not message owner):
{
"success": false,
"message": "You can only delete your own messages"
}
Frontend Integration
The chat system can be integrated into React applications using Pusher for real-time messaging. Below is a complete implementation guide.
1. Install Required Packages
npm install pusher-js laravel-echo axios
2. Setup Echo and Pusher
Create src/utils/echo.js:
import Echo from "laravel-echo";
import Pusher from "pusher-js";
window.Pusher = Pusher;
const echo = new Echo({
broadcaster: "pusher",
key: process.env.REACT_APP_PUSHER_APP_KEY,
cluster: process.env.REACT_APP_PUSHER_APP_CLUSTER,
forceTLS: true,
authorizer: (channel, options) => {
return {
authorize: (socketId, callback) => {
// Your auth token from Laravel Sanctum
const token = localStorage.getItem("auth_token");
axios
.post(
"/api/broadcasting/auth",
{
socket_id: socketId,
channel_name: channel.name,
},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
)
.then((response) => {
callback(false, response.data);
})
.catch((error) => {
callback(true, error);
});
},
};
},
});
export default echo;
3. Environment Variables
Add to your .env file in React:
REACT_APP_PUSHER_APP_KEY=your_pusher_app_key
REACT_APP_PUSHER_APP_CLUSTER=mt1
REACT_APP_API_URL=http://localhost:8000/api
4. Chat Component Example
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import echo from "../utils/echo";
const ChatComponent = ({ chatId, authToken }) => {
const [messages, setMessages] = useState([]);
const [newMessage, setNewMessage] = useState("");
const [loading, setLoading] = useState(true);
const messagesEndRef = useRef(null);
useEffect(() => {
loadMessages();
subscribeToChat();
return () => {
echo.leaveChannel(`private-chat.${chatId}`);
};
}, [chatId]);
const loadMessages = async () => {
try {
const response = await axios.get(`/api/chats/${chatId}/messages`, {
headers: { Authorization: `Bearer ${authToken}` },
});
setMessages(response.data.data.data);
setLoading(false);
} catch (error) {
console.error("Error loading messages:", error);
setLoading(false);
}
};
const subscribeToChat = () => {
echo.private(`chat.${chatId}`)
.listen(".message.sent", (e) => {
setMessages((prev) => [...prev, e]);
scrollToBottom();
})
.listen(".message.deleted", (e) => {
setMessages((prev) =>
prev.filter((msg) => msg.id !== e.message_id)
);
});
};
const sendMessage = async (e) => {
e.preventDefault();
if (!newMessage.trim()) return;
try {
const response = await axios.post(
`/api/chats/${chatId}/messages`,
{
content: newMessage,
type: "text",
},
{
headers: { Authorization: `Bearer ${authToken}` },
}
);
setMessages((prev) => [...prev, response.data.data]);
setNewMessage("");
scrollToBottom();
} catch (error) {
console.error("Error sending message:", error);
}
};
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
if (loading) return <div>Loading messages...</div>;
return (
<div className="chat-container">
<div className="messages-container">
{messages.map((message) => (
<div key={message.id} className="message">
<div className="message-header">
<span className="sender-name">
{message.user.first_name}{" "}
{message.user.last_name}
</span>
<span className="message-time">
{new Date(
message.created_at
).toLocaleTimeString()}
</span>
</div>
<div className="message-content">
{message.content}
{message.is_edited && (
<span className="edited-indicator">
(edited)
</span>
)}
</div>
</div>
))}
<div ref={messagesEndRef} />
</div>
<form onSubmit={sendMessage} className="message-form">
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="Type a message..."
className="message-input"
/>
<button type="submit" className="send-button">
Send
</button>
</form>
</div>
);
};
export default ChatComponent;
5. Chat List Component
import React, { useState, useEffect } from "react";
import axios from "axios";
const ChatList = ({ authToken, onSelectChat }) => {
const [chats, setChats] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadChats();
}, []);
const loadChats = async () => {
try {
const response = await axios.get("/api/chats", {
headers: { Authorization: `Bearer ${authToken}` },
});
setChats(response.data.data);
setLoading(false);
} catch (error) {
console.error("Error loading chats:", error);
setLoading(false);
}
};
const createPrivateChat = async (friendId) => {
try {
const response = await axios.post(
"/api/chats/private",
{
friend_id: friendId,
},
{
headers: { Authorization: `Bearer ${authToken}` },
}
);
// Add new chat to list or select existing one
const newChat = response.data.data;
setChats((prev) => {
const exists = prev.find((chat) => chat.id === newChat.id);
if (exists) return prev;
return [newChat, ...prev];
});
onSelectChat(newChat.id);
} catch (error) {
console.error("Error creating chat:", error);
}
};
if (loading) return <div>Loading chats...</div>;
return (
<div className="chat-list">
{chats.map((chat) => (
<div
key={chat.id}
className="chat-item"
onClick={() => onSelectChat(chat.id)}
>
<div className="chat-info">
<h4>{chat.name}</h4>
{chat.latest_message && (
<p className="last-message">
{chat.latest_message.content}
</p>
)}
</div>
{chat.unread_count > 0 && (
<span className="unread-badge">
{chat.unread_count}
</span>
)}
</div>
))}
</div>
);
};
export default ChatList;
Backend Setup
1. Environment Configuration
Add the following to your .env file:
BROADCAST_CONNECTION=pusher
PUSHER_APP_ID=your_pusher_app_id
PUSHER_APP_KEY=your_pusher_app_key
PUSHER_APP_SECRET=your_pusher_app_secret
PUSHER_APP_CLUSTER=mt1
2. Database Schema
The chat system uses three main tables:
- chats: Stores chat metadata (type, name, creator, etc.)
- chat_participants: Pivot table for users in chats (with read receipts)
- messages: Stores all messages with content and metadata
Testing the Implementation
- Make sure you have valid Pusher credentials in your
.env - Run migrations:
php artisan migrate - Ensure broadcasting is enabled:
BROADCAST_CONNECTION=pusher - Test API endpoints with authenticated requests
- Test real-time functionality with multiple browser tabs
Performance Considerations
- Messages are paginated (50 per page by default)
- Indexes are added for better query performance
- Consider implementing message caching for frequently accessed chats
- Use queue workers for broadcasting in production
Next Steps
- Add file/image sharing capabilities
- Implement typing indicators
- Add message reactions/emojis
- Implement group chat functionality
- Add push notifications for mobile apps
Security Considerations
- Authentication: All routes are protected with Sanctum authentication
- Authorization: Users can only access chats they're participants in
- Friend Verification: Private chats can only be created between friends
- Channel Authorization: Pusher channels are authorized via Laravel
- Message Ownership: Users can only edit/delete their own messages
Messages
Get Chat Messages
Retrieve messages for a specific chat with pagination.
Endpoint: GET /api/chats/{chat}/messages
Authentication: Required (Bearer Token)
Query Parameters
page(integer, optional): Page number for paginationper_page(integer, optional): Number of messages per page (default: 20)
Response (200 OK)
{
"success": true,
"data": {
"current_page": 1,
"data": [
{
"id": 1,
"content": "Hello! How are you?",
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"photo": "http://domain.com/storage/users/photos/photo.jpg"
},
"chat_id": 1,
"created_at": "2025-07-19T12:00:00.000000Z",
"updated_at": "2025-07-19T12:00:00.000000Z"
}
],
"first_page_url": "http://domain.com/api/chats/1/messages?page=1",
"from": 1,
"last_page": 3,
"last_page_url": "http://domain.com/api/chats/1/messages?page=3",
"next_page_url": "http://domain.com/api/chats/1/messages?page=2",
"path": "http://domain.com/api/chats/1/messages",
"per_page": 20,
"prev_page_url": null,
"to": 20,
"total": 60
}
}
Error Responses
403 Forbidden (not a chat participant):
{
"success": false,
"message": "You are not a participant in this chat"
}
Send Message
Send a new message in a chat.
Endpoint: POST /api/chats/{chat}/messages
Authentication: Required (Bearer Token)
Request Body
{
"content": "string (required, max:1000 characters)"
}
Response (201 Created)
{
"success": true,
"message": "Message sent successfully",
"data": {
"id": 1,
"content": "Hello! How are you?",
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"photo": "http://domain.com/storage/users/photos/photo.jpg"
},
"chat_id": 1,
"created_at": "2025-07-19T12:00:00.000000Z",
"updated_at": "2025-07-19T12:00:00.000000Z"
}
}
Error Responses
403 Forbidden (not a chat participant):
{
"success": false,
"message": "You are not a participant in this chat"
}
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"content": [
"The content field is required.",
"The content may not be greater than 1000 characters."
]
}
}
Update Message
Update an existing message (only by the message sender).
Endpoint: PUT /api/messages/{message}
Authentication: Required (Bearer Token)
Request Body
{
"content": "string (required, max:1000 characters)"
}
Response (200 OK)
{
"success": true,
"message": "Message updated successfully",
"data": {
"id": 1,
"content": "Updated message content",
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"photo": "http://domain.com/storage/users/photos/photo.jpg"
},
"chat_id": 1,
"created_at": "2025-07-19T12:00:00.000000Z",
"updated_at": "2025-07-19T12:30:00.000000Z"
}
}
Error Responses
403 Forbidden (not message owner):
{
"success": false,
"message": "You can only edit your own messages"
}
404 Not Found (message not found):
{
"success": false,
"message": "Message not found"
}
Delete Message
Delete an existing message (only by the message sender).
Endpoint: DELETE /api/messages/{message}
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"message": "Message deleted successfully"
}
Error Responses
403 Forbidden (not message owner):
{
"success": false,
"message": "You can only delete your own messages"
}
404 Not Found (message not found):
{
"success": false,
"message": "Message not found"
}
Leaderboard
The Leaderboard system tracks user gaming activity and provides ranking functionality based on daily gaming hours.
Get Leaderboard
Retrieve the main leaderboard showing top users ranked by their gaming activity.
Endpoint: GET /api/leaderboard
Authentication: None required
Query Parameters
limit(integer, optional): Number of users to return (default: 50)
Response (200 OK)
{
"success": true,
"data": [
{
"rank": 1,
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"photo": "http://domain.com/storage/users/photos/photo.jpg"
},
"total_hours": 145.5,
"days_active": 30,
"average_daily_hours": 4.85
},
{
"rank": 2,
"user": {
"id": 2,
"first_name": "Jane",
"last_name": "Smith",
"photo": "http://domain.com/storage/users/photos/photo2.jpg"
},
"total_hours": 132.0,
"days_active": 28,
"average_daily_hours": 4.71
}
],
"current_user_rank": 15,
"total_users": 500
}
Get Weekly Leaderboard
Retrieve the weekly leaderboard showing top users for the current week.
Endpoint: GET /api/leaderboard/weekly
Authentication: None required
Query Parameters
limit(integer, optional): Number of users to return (default: 50)
Response (200 OK)
{
"success": true,
"data": [
{
"rank": 1,
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"photo": "http://domain.com/storage/users/photos/photo.jpg"
},
"weekly_hours": 28.5,
"days_active": 6
}
],
"week_start": "2025-07-14",
"week_end": "2025-07-20",
"current_user_rank": 8,
"total_users": 120
}
Update Daily Hours
Update the authenticated user's daily gaming hours.
Endpoint: POST /api/leaderboard/daily-hours
Authentication: Required (Bearer Token)
Request Body
{
"date": "2025-07-19",
"hours": 4.5
}
Response (200 OK)
{
"success": true,
"message": "Daily hours updated successfully",
"data": {
"date": "2025-07-19",
"hours": 4.5,
"user_id": 1,
"created_at": "2025-07-19T12:00:00.000000Z",
"updated_at": "2025-07-19T12:00:00.000000Z"
}
}
Error Responses
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"date": [
"The date field is required.",
"The date must be a valid date."
],
"hours": [
"The hours field is required.",
"The hours must be a number between 0 and 24."
]
}
}
Get User History
Retrieve gaming hours history for a specific user.
Endpoint: GET /api/leaderboard/users/{user}/history
Authentication: Required (Bearer Token)
Query Parameters
days(integer, optional): Number of days to retrieve (default: 30, max: 365)start_date(date, optional): Start date for history (YYYY-MM-DD format)end_date(date, optional): End date for history (YYYY-MM-DD format)
Response (200 OK)
{
"success": true,
"user": {
"id": 1,
"first_name": "John",
"last_name": "Doe",
"photo": "http://domain.com/storage/users/photos/photo.jpg"
},
"data": [
{
"date": "2025-07-19",
"hours": 4.5
},
{
"date": "2025-07-18",
"hours": 3.2
},
{
"date": "2025-07-17",
"hours": 5.1
}
],
"total_hours": 145.5,
"average_daily_hours": 4.85,
"days_with_activity": 30,
"period": {
"start_date": "2025-06-19",
"end_date": "2025-07-19",
"total_days": 30
}
}
Error Responses
404 Not Found (user not found):
{
"success": false,
"message": "User not found"
}
403 Forbidden (privacy restriction):
{
"success": false,
"message": "You can only view your own gaming history or users you are friends with"
}
Payment System
The Payment System provides comprehensive Stripe integration for handling payments through both hosted checkout sessions and custom payment flows. All payment data is securely stored in the database with full audit trails.
Features
- Stripe Checkout Integration: Hosted payment pages for quick implementation
- Payment Intent Support: Custom payment flows with Stripe Elements
- Webhook Handling: Automatic status updates via Stripe webhooks
- Payment History: Complete transaction history for users
- Multiple Currencies: Support for USD, EUR, and GBP
- Comprehensive Validation: Input validation and error handling
- Security: Webhook signature verification and encrypted data storage
Database Tables
The payment system uses two main tables:
- payments: Stores completed payment records from Stripe
- payment_sessions: Stores Stripe Checkout Session data
Payment Endpoints
Create Checkout Session
Create a Stripe Checkout Session for hosted payment page.
Endpoint: POST /payments/create-checkout-session
Authentication: Required (Bearer Token)
Request Body
{
"amount": 29.99,
"currency": "usd",
"description": "Game Purchase",
"metadata": {
"game_id": "123",
"plan": "premium"
},
"success_url": "https://yourapp.com/payment/success",
"cancel_url": "https://yourapp.com/payment/cancel"
}
Validation Rules
amount: Required, numeric, minimum $0.50, maximum $999,999.99currency: Optional, must be one of: usd, eur, gbp (default: usd)description: Optional, string, maximum 255 charactersmetadata: Optional, object with string valuessuccess_url: Required, valid URL, maximum 2048 characterscancel_url: Required, valid URL, maximum 2048 characters
Response (200 OK)
{
"success": true,
"data": {
"session_id": "cs_test_a1b2c3d4e5f6g7h8i9j0",
"session_url": "https://checkout.stripe.com/c/pay/cs_test_...",
"payment_session": {
"id": 1,
"user_id": 1,
"stripe_session_id": "cs_test_a1b2c3d4e5f6g7h8i9j0",
"amount": "29.99",
"currency": "usd",
"status": "open",
"payment_status": "unpaid",
"mode": "payment",
"expires_at": "2025-09-01T18:02:12.000000Z",
"created_at": "2025-08-31T18:02:12.000000Z",
"updated_at": "2025-08-31T18:02:12.000000Z"
}
}
}
Error Responses
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"amount": [
"Payment amount is required.",
"Payment amount must be at least $0.50."
],
"success_url": [
"Success URL is required.",
"Success URL must be a valid URL."
]
}
}
500 Internal Server Error (Stripe error):
{
"success": false,
"message": "Failed to create payment session",
"error": "Stripe API error details"
}
Create Payment Intent
Create a Stripe Payment Intent for custom payment flows.
Endpoint: POST /payments/create-payment-intent
Authentication: Required (Bearer Token)
Request Body
{
"amount": 19.99,
"currency": "usd",
"description": "Premium Upgrade",
"metadata": {
"upgrade_type": "premium",
"duration": "monthly"
}
}
Validation Rules
amount: Required, numeric, minimum $0.50, maximum $999,999.99currency: Optional, must be one of: usd, eur, gbp (default: usd)description: Optional, string, maximum 255 charactersmetadata: Optional, object with string values
Response (200 OK)
{
"success": true,
"data": {
"client_secret": "pi_test_1234567890_secret_abcdefghij",
"payment_intent_id": "pi_test_1234567890",
"payment": {
"id": 1,
"user_id": 1,
"stripe_payment_intent_id": "pi_test_1234567890",
"amount": "19.99",
"currency": "usd",
"status": "requires_payment_method",
"created_at": "2025-08-31T18:02:12.000000Z",
"updated_at": "2025-08-31T18:02:12.000000Z"
}
}
}
Get Session Details
Retrieve details of a payment session.
Endpoint: GET /payments/session
Authentication: Required (Bearer Token)
Query Parameters
session_id(required): Stripe Session ID
Response (200 OK)
{
"success": true,
"data": {
"session": {
"id": 1,
"user_id": 1,
"stripe_session_id": "cs_test_a1b2c3d4e5f6g7h8i9j0",
"amount": "29.99",
"currency": "usd",
"status": "complete",
"payment_status": "paid",
"mode": "payment"
},
"stripe_session": {
"id": "cs_test_a1b2c3d4e5f6g7h8i9j0",
"object": "checkout.session",
"status": "complete",
"payment_status": "paid"
}
}
}
Error Responses
404 Not Found (session not found):
{
"success": false,
"message": "Payment session not found"
}
Get Payment Intent Details
Retrieve details of a payment intent.
Endpoint: GET /payments/payment-intent
Authentication: Required (Bearer Token)
Query Parameters
payment_intent_id(required): Stripe Payment Intent ID
Response (200 OK)
{
"success": true,
"data": {
"payment": {
"id": 1,
"user_id": 1,
"stripe_payment_intent_id": "pi_test_1234567890",
"amount": "19.99",
"currency": "usd",
"status": "succeeded",
"payment_method": "pm_test_card",
"payment_date": "2025-08-31T18:02:12.000000Z"
},
"payment_intent": {
"id": "pi_test_1234567890",
"object": "payment_intent",
"status": "succeeded",
"amount": 1999
}
}
}
Get Payment History
Retrieve user's payment history with pagination.
Endpoint: GET /payments/history
Authentication: Required (Bearer Token)
Query Parameters
per_page(optional): Number of items per page (default: 15)
Response (200 OK)
{
"success": true,
"data": {
"payments": {
"data": [
{
"id": 1,
"user_id": 1,
"stripe_payment_intent_id": "pi_test_1234567890",
"amount": "29.99",
"currency": "usd",
"status": "succeeded",
"payment_date": "2025-08-31T18:02:12.000000Z",
"description": "Game Purchase"
}
],
"current_page": 1,
"per_page": 15,
"total": 5
},
"sessions": {
"data": [
{
"id": 1,
"user_id": 1,
"stripe_session_id": "cs_test_a1b2c3d4e5f6g7h8i9j0",
"amount": "29.99",
"currency": "usd",
"status": "complete",
"payment_status": "paid"
}
],
"current_page": 1,
"per_page": 15,
"total": 3
}
}
}
Webhook Endpoint
Handle Stripe Webhooks
Process Stripe webhook events for payment status updates.
Endpoint: POST /stripe/webhook
Authentication: None (signature verification required)
Headers
Stripe-Signature: Required webhook signature from Stripe
Webhook Events Handled
- checkout.session.completed: Updates session status when checkout is complete
- payment_intent.succeeded: Updates payment status when payment succeeds
- payment_intent.payment_failed: Updates payment status when payment fails
Response (200 OK)
{
"success": true
}
Error Responses
400 Bad Request (invalid payload or signature):
{
"error": "Invalid payload"
}
{
"error": "Invalid signature"
}
Frontend Integration
Using Stripe Checkout (Recommended)
- Create a checkout session by calling
/api/payments/create-checkout-session - Redirect user to the
session_urlreturned in the response - Handle success/cancel redirects in your frontend
- Verify payment status by calling
/api/payments/sessionwith the session ID
JavaScript Example
// Create checkout session
const response = await fetch("/api/payments/create-checkout-session", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
amount: 29.99,
currency: "usd",
description: "Game Purchase",
success_url: `${window.location.origin}/payment/success`,
cancel_url: `${window.location.origin}/payment/cancel`,
}),
});
const data = await response.json();
if (data.success) {
// Redirect to Stripe Checkout
window.location.href = data.data.session_url;
}
Using Stripe Elements (Custom UI)
- Create a payment intent by calling
/api/payments/create-payment-intent - Use the client_secret with Stripe Elements to collect payment details
- Confirm payment using Stripe.js
- Verify payment status by calling
/api/payments/payment-intent
JavaScript Example
// Create payment intent
const response = await fetch("/api/payments/create-payment-intent", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
amount: 19.99,
currency: "usd",
description: "Premium Upgrade",
}),
});
const data = await response.json();
if (data.success) {
const clientSecret = data.data.client_secret;
// Use clientSecret with Stripe Elements
const stripe = Stripe("pk_test_your_publishable_key");
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: cardElement,
billing_details: {
name: "Customer Name",
},
},
});
if (result.error) {
// Handle payment error
console.error(result.error.message);
} else {
// Payment succeeded
console.log("Payment successful:", result.paymentIntent);
}
}
cURL Examples
# Create checkout session
curl -X POST http://your-domain.com/api/payments/create-checkout-session \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{
"amount": 29.99,
"currency": "usd",
"description": "Game Purchase",
"metadata": {"game_id": "123"},
"success_url": "https://yourapp.com/success",
"cancel_url": "https://yourapp.com/cancel"
}'
# Create payment intent
curl -X POST http://your-domain.com/api/payments/create-payment-intent \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{
"amount": 19.99,
"currency": "usd",
"description": "Premium Upgrade"
}'
# Get payment history
curl -X GET http://your-domain.com/api/payments/history \
-H "Authorization: Bearer your_token_here"
# Get session details
curl -X GET "http://your-domain.com/api/payments/session?session_id=cs_test_123" \
-H "Authorization: Bearer your_token_here"
Setup Instructions
- Get Stripe API Keys: Register at Stripe Dashboard
- Configure Environment Variables:
STRIPE_KEY=pk_test_your_publishable_key_here STRIPE_SECRET=sk_test_your_secret_key_here STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here - Set up Webhook: Create webhook endpoint in Stripe Dashboard
- URL:
https://yourdomain.com/api/stripe/webhook - Events:
checkout.session.completed,payment_intent.succeeded,payment_intent.payment_failed
- URL:
Testing
Use Stripe's test card numbers for testing:
- Success:
4242424242424242 - Declined:
4000000000000002 - Requires authentication:
4000002500003155
Subscription System
The Subscription System provides comprehensive subscription management with Stripe integration, allowing users to subscribe to premium packages with different durations and features. All subscription data is securely stored with full audit trails and automatic renewal handling.
Features
- Package Management: Flexible subscription packages with different periods and pricing
- Stripe Integration: Complete Stripe subscription and checkout session integration
- Multiple Payment Types: Support for both one-time and recurring subscriptions
- Automatic Renewals: Webhook-driven subscription renewals and status updates
- Subscription History: Complete subscription history tracking for users
- Multiple Currencies: Support for USD, EUR, and GBP
- Comprehensive Validation: Input validation and error handling
- Security: Webhook signature verification and encrypted data storage
Database Tables
The subscription system uses two main tables:
- subscription_packages: Stores available subscription packages with pricing and features
- subscriptions: Stores user subscription records with Stripe integration data
Subscription Endpoints
Get Subscription Packages
Retrieve all available subscription packages (public access).
Endpoint: GET /subscription-packages
Authentication: None required
Response (200 OK)
{
"success": true,
"data": [
{
"id": 1,
"name": "Daily Pass",
"description": "Access to premium games for 1 day",
"price": "2.99",
"currency": "usd",
"period_days": 1,
"period_type": "one_time",
"stripe_price_id": null,
"features": [
"Access to all premium games",
"No ads",
"Priority support"
],
"is_active": true,
"sort_order": 1,
"created_at": "2025-09-02T10:35:27.000000Z",
"updated_at": "2025-09-02T10:35:27.000000Z"
},
{
"id": 2,
"name": "Monthly Premium",
"description": "Full access to all premium features for 30 days",
"price": "9.99",
"currency": "usd",
"period_days": 30,
"period_type": "recurring",
"stripe_price_id": null,
"features": [
"Access to all premium games",
"No ads",
"Priority support",
"Exclusive monthly content",
"Early access to new games"
],
"is_active": true,
"sort_order": 2,
"created_at": "2025-09-02T10:35:27.000000Z",
"updated_at": "2025-09-02T10:35:27.000000Z"
}
]
}
Error Response (500 Internal Server Error)
{
"success": false,
"message": "Failed to retrieve subscription packages",
"error": "Database connection error"
}
Get User Subscriptions
Retrieve user's subscription history with pagination.
Endpoint: GET /subscriptions
Authentication: Required (Bearer Token)
Query Parameters
per_page(optional): Number of items per page (default: 15)
Response (200 OK)
{
"success": true,
"data": {
"current_page": 1,
"data": [
{
"id": 1,
"user_id": 1,
"subscription_package_id": 2,
"stripe_customer_id": "cus_test_123",
"stripe_subscription_id": "sub_test_456",
"status": "active",
"starts_at": "2025-09-01T00:00:00.000000Z",
"ends_at": "2025-10-01T00:00:00.000000Z",
"amount": "9.99",
"currency": "usd",
"metadata": {
"package_name": "Monthly Premium"
},
"created_at": "2025-09-01T10:00:00.000000Z",
"updated_at": "2025-09-01T10:00:00.000000Z",
"package": {
"id": 2,
"name": "Monthly Premium",
"description": "Full access to all premium features for 30 days",
"price": "9.99",
"period_days": 30,
"features": [
"Access to all premium games",
"No ads",
"Priority support"
]
}
}
],
"first_page_url": "http://domain.com/api/subscriptions?page=1",
"from": 1,
"last_page": 1,
"last_page_url": "http://domain.com/api/subscriptions?page=1",
"next_page_url": null,
"path": "http://domain.com/api/subscriptions",
"per_page": 15,
"prev_page_url": null,
"to": 1,
"total": 1
}
}
Error Response (401 Unauthorized)
{
"success": false,
"message": "Unauthenticated."
}
Get Current Subscription
Retrieve user's current active subscription.
Endpoint: GET /subscriptions/current
Authentication: Required (Bearer Token)
Response (200 OK)
{
"success": true,
"data": {
"id": 1,
"user_id": 1,
"subscription_package_id": 2,
"stripe_customer_id": "cus_test_123",
"stripe_subscription_id": "sub_test_456",
"status": "active",
"starts_at": "2025-09-01T00:00:00.000000Z",
"ends_at": "2025-10-01T00:00:00.000000Z",
"amount": "9.99",
"currency": "usd",
"package": {
"id": 2,
"name": "Monthly Premium",
"description": "Full access to all premium features for 30 days",
"price": "9.99",
"period_days": 30,
"features": [
"Access to all premium games",
"No ads",
"Priority support"
]
}
}
}
Error Response (404 Not Found)
{
"success": false,
"message": "No active subscription found"
}
Subscribe to Package
Create a new subscription to a package with Stripe integration.
Endpoint: POST /subscriptions/subscribe
Authentication: Required (Bearer Token)
Request Body
{
"package_id": 2,
"success_url": "https://yourapp.com/subscription/success",
"cancel_url": "https://yourapp.com/subscription/cancel"
}
Validation Rules
package_id: Required, integer, must exist in subscription_packages tablesuccess_url: Required, valid URLcancel_url: Required, valid URL
Response (200 OK)
{
"success": true,
"data": {
"session_id": "cs_test_a1DTLRPULHUWRh5Gpc2ltZGv8gWaPfyf4sX5zIO5UQg3AJSGN3LwH9nMDk",
"session_url": "https://checkout.stripe.com/c/pay/cs_test_...",
"subscription": {
"id": 1,
"user_id": 1,
"subscription_package_id": 2,
"stripe_customer_id": "cus_SypE2sZIPeiRfb",
"status": "pending",
"starts_at": "2025-09-02T10:37:08.000000Z",
"ends_at": "2025-10-02T10:37:08.000000Z",
"amount": "9.99",
"currency": "usd",
"metadata": {
"stripe_session_id": "cs_test_a1DTLRPULHUWRh5Gpc2ltZGv8gWaPfyf4sX5zIO5UQg3AJSGN3LwH9nMDk",
"package_name": "Monthly Premium"
},
"updated_at": "2025-09-02T10:37:08.000000Z",
"created_at": "2025-09-02T10:37:08.000000Z"
}
}
}
Error Responses
400 Bad Request (already subscribed):
{
"success": false,
"message": "You already have an active subscription to this package"
}
422 Unprocessable Entity (validation error):
{
"success": false,
"message": "Validation failed",
"errors": {
"package_id": ["The selected package does not exist"],
"success_url": [
"Success URL is required.",
"Success URL must be a valid URL."
],
"cancel_url": [
"Cancel URL is required.",
"Cancel URL must be a valid URL."
]
}
}
500 Internal Server Error (Stripe error):
{
"success": false,
"message": "Failed to create subscription",
"error": "Stripe API error details"
}
Cancel Subscription
Cancel an existing subscription.
Endpoint: POST /subscriptions/cancel
Authentication: Required (Bearer Token)
Request Body
{
"subscription_id": 1
}
Response (200 OK)
{
"success": true,
"message": "Subscription canceled successfully",
"data": {
"id": 1,
"user_id": 1,
"subscription_package_id": 2,
"status": "canceled",
"starts_at": "2025-09-01T00:00:00.000000Z",
"ends_at": "2025-10-01T00:00:00.000000Z",
"canceled_at": "2025-09-15T12:00:00.000000Z"
}
}
Error Responses
400 Bad Request (already canceled):
{
"success": false,
"message": "Subscription is already canceled"
}
404 Not Found (subscription not found or not owned by user):
{
"success": false,
"message": "Subscription not found"
}
500 Internal Server Error (cancellation failed):
{
"success": false,
"message": "Failed to cancel subscription",
"error": "Error details"
}
Webhook Endpoint
Handle Subscription Webhooks
Process Stripe webhook events for subscription status updates.
Endpoint: POST /subscriptions/webhook
Authentication: None (signature verification required)
Headers
Stripe-Signature: Required webhook signature from Stripe
Webhook Events Handled
- checkout.session.completed: Activates subscription when checkout is complete
- invoice.payment_succeeded: Extends subscription when recurring payment succeeds
- customer.subscription.created: Updates subscription with Stripe subscription ID
- customer.subscription.updated: Updates subscription status
- customer.subscription.deleted: Cancels subscription when deleted in Stripe
Response (200 OK)
{
"success": true
}
Error Responses
400 Bad Request (invalid payload or signature):
{
"error": "Invalid webhook"
}
Package Types
One-time Subscriptions
- Daily Pass: $2.99 for 1 day access
- Weekly Pass: $4.99 for 7 days access
Recurring Subscriptions
- Monthly Premium: $9.99/month with automatic renewal
- Yearly Premium: $99.99/year with automatic renewal
Frontend Integration
Using Stripe Checkout (Recommended)
- Get available packages by calling
/api/subscription-packages - Create a subscription by calling
/api/subscriptions/subscribe - Redirect user to the
session_urlreturned in the response - Handle success/cancel redirects in your frontend
- Verify subscription status by calling
/api/subscriptions/current
JavaScript Example
// Get available packages
const getPackages = async () => {
const response = await fetch("/api/subscription-packages", {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
});
const data = await response.json();
return data.data; // Array of packages
};
// Subscribe to a package
const subscribeToPackage = async (packageId, token) => {
const response = await fetch("/api/subscriptions/subscribe", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
package_id: packageId,
success_url: `${window.location.origin}/subscription/success`,
cancel_url: `${window.location.origin}/subscription/cancel`,
}),
});
const data = await response.json();
if (data.success) {
// Redirect to Stripe Checkout
window.location.href = data.data.session_url;
}
return data;
};
// Get current subscription
const getCurrentSubscription = async (token) => {
const response = await fetch("/api/subscriptions/current", {
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
return data;
};
// Get subscription history
const getSubscriptionHistory = async (token) => {
const response = await fetch("/api/subscriptions", {
headers: {
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
return data.data; // Paginated subscription data
};
// Cancel subscription
const cancelSubscription = async (subscriptionId, token) => {
const response = await fetch("/api/subscriptions/cancel", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
subscription_id: subscriptionId,
}),
});
const data = await response.json();
return data;
};
cURL Examples
# Get subscription packages (public access)
curl -X GET http://your-domain.com/api/subscription-packages \
-H "Content-Type: application/json" \
-H "Accept: application/json"
# Subscribe to a package
curl -X POST http://your-domain.com/api/subscriptions/subscribe \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{
"package_id": 2,
"success_url": "https://yourapp.com/subscription/success",
"cancel_url": "https://yourapp.com/subscription/cancel"
}'
# Get user subscriptions
curl -X GET http://your-domain.com/api/subscriptions \
-H "Authorization: Bearer your_token_here"
# Get current subscription
curl -X GET http://your-domain.com/api/subscriptions/current \
-H "Authorization: Bearer your_token_here"
# Cancel subscription
curl -X POST http://your-domain.com/api/subscriptions/cancel \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{
"subscription_id": 1
}'
Setup Instructions
- Configure Stripe Subscription Settings: Set up products and prices in Stripe Dashboard
- Configure Environment Variables:
STRIPE_KEY=pk_test_your_publishable_key_here STRIPE_SECRET=sk_test_your_secret_key_here STRIPE_WEBHOOK_SECRET=whsec_your_subscription_webhook_secret_here - Set up Subscription Webhook: Create webhook endpoint in Stripe Dashboard
- URL:
https://yourdomain.com/api/subscriptions/webhook - Events:
checkout.session.completed,invoice.payment_succeeded,customer.subscription.created,customer.subscription.updated,customer.subscription.deleted
- URL:
Testing
Test Packages Available
The system comes with four pre-configured packages:
- Daily Pass - $2.99 for 1 day (one-time)
- Weekly Pass - $4.99 for 7 days (one-time)
- Monthly Premium - $9.99 for 30 days (recurring)
- Yearly Premium - $99.99 for 365 days (recurring)
Test Card Numbers
Use Stripe's test card numbers for testing subscriptions:
- Success:
4242424242424242 - Declined:
4000000000000002 - Requires authentication:
4000002500003155
Test Scenarios
- Subscribe to one-time package: User gets access for specified period
- Subscribe to recurring package: User gets access with automatic renewals
- Cancel subscription: User loses access but retains until end of period
- Payment failure: Subscription status updates to failed
- Webhook processing: Automatic status updates from Stripe events
Error Handling
HTTP Status Codes
- 200 OK: Successful request
- 201 Created: Resource created successfully
- 401 Unauthorized: Authentication required or invalid credentials
- 403 Forbidden: Access denied (e.g., inactive subscription)
- 404 Not Found: Resource not found
- 422 Unprocessable Entity: Validation errors
Error Response Format
{
"success": false,
"message": "Error description",
"errors": {
"field_name": ["Validation error message"]
}
}
Common Error Examples
Validation Error (422)
{
"success": false,
"message": "Validation failed",
"errors": {
"username": ["The username has already been taken."],
"password": ["The password must be at least 8 characters."]
}
}
Authentication Error (401)
{
"success": false,
"message": "Invalid credentials"
}
Inactive Account Error (401)
{
"success": false,
"message": "Account is inactive"
}
Headers & Language Support
Required Headers
For authenticated endpoints:
Authorization: Bearer {your_token_here}
Content-Type: application/json
For file uploads:
Authorization: Bearer {your_token_here}
Content-Type: multipart/form-data
Language Support
The API supports multiple languages through the language header:
language: en // English (default)
language: ar // Arabic
language: fr // French
// Add other supported languages
When the language header is provided, all translatable fields (game names, descriptions, category names, etc.) will be returned in the requested language. If a translation is not available, the default language (English) will be used as fallback.
Translatable Fields
- Game:
name,description,instructions - Category:
name,description
Pagination
List endpoints return paginated results with the following structure:
{
"current_page": 1,
"data": [...],
"first_page_url": "...",
"from": 1,
"last_page": 5,
"last_page_url": "...",
"next_page_url": "...",
"path": "...",
"per_page": 15,
"prev_page_url": null,
"to": 15,
"total": 75
}
SDKs & Examples
JavaScript/Fetch Example
// Send OTP for authentication (creates user automatically if not exists)
const sendOtp = async (phone) => {
const response = await fetch("/api/send-otp", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ phone }),
});
const result = await response.json();
return result;
};
// Verify OTP and get authentication token
const verifyOtp = async (phone, otp) => {
const response = await fetch("/api/verify-otp", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ phone, otp }),
});
const result = await response.json();
return result;
};
// Get games with authentication
const getGames = async (token, language = "en") => {
const response = await fetch("/api/games", {
headers: {
Authorization: `Bearer ${token}`,
language: language,
},
});
const games = await response.json();
return games;
};
// Vote on a game
const voteOnGame = async (gameId, voteType, token) => {
const response = await fetch(`/api/games/${gameId}/vote`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ vote_type: voteType }),
});
const result = await response.json();
return result;
};
// Send a friend request
const sendFriendRequest = async (receiverId, token) => {
const response = await fetch("/api/friend-requests", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ receiver_id: receiverId }),
});
const result = await response.json();
return result;
};
// Accept or reject a friend request
const respondToFriendRequest = async (requestId, action, token) => {
const response = await fetch(`/api/friend-requests/${requestId}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ action }),
});
const result = await response.json();
return result;
};
// Get friends list
const getFriends = async (token) => {
const response = await fetch("/api/friends", {
headers: {
Authorization: `Bearer ${token}`,
},
});
const result = await response.json();
return result;
};
// Get friends' stories
const getFriendsStories = async (token) => {
const response = await fetch("/api/stories", {
headers: {
Authorization: `Bearer ${token}`,
},
});
const result = await response.json();
return result;
};
// Get my stories
const getMyStories = async (token) => {
const response = await fetch("/api/stories/my", {
headers: {
Authorization: `Bearer ${token}`,
},
});
const result = await response.json();
return result;
};
// Create a story with content only
const createStoryWithContent = async (content, token) => {
const response = await fetch("/api/stories", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ content }),
});
const result = await response.json();
return result;
};
// Create a story with image
const createStoryWithImage = async (content, imageFile, token) => {
const formData = new FormData();
if (content) formData.append("content", content);
if (imageFile) formData.append("image", imageFile);
const response = await fetch("/api/stories", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: formData,
});
const result = await response.json();
return result;
};
// Get a specific story
const getStory = async (storyId, token) => {
const response = await fetch(`/api/stories/${storyId}`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
const result = await response.json();
return result;
};
// Update a story
const updateStory = async (storyId, content, token) => {
const response = await fetch(`/api/stories/${storyId}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ content }),
});
const result = await response.json();
return result;
};
// Delete a story
const deleteStory = async (storyId, token) => {
const response = await fetch(`/api/stories/${storyId}`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`,
},
});
const result = await response.json();
return result;
};
cURL Examples
# Send OTP for authentication (creates user automatically if not exists)
curl -X POST http://your-domain.com/api/send-otp \
-H "Content-Type: application/json" \
-d '{
"phone": "+1234567890"
}'
# Verify OTP and get authentication token
curl -X POST http://your-domain.com/api/verify-otp \
-H "Content-Type: application/json" \
-d '{
"phone": "+1234567890",
"otp": "123456"
}'
# Get games with language preference
curl -X GET http://your-domain.com/api/games \
-H "language: ar"
# Vote on a game
curl -X POST http://your-domain.com/api/games/1/vote \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{"vote_type": "upvote"}'
# Send a friend request
curl -X POST http://your-domain.com/api/friend-requests \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{"receiver_id": 2}'
# Accept a friend request
curl -X PUT http://your-domain.com/api/friend-requests/1 \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{"action": "accept"}'
# Get friends list
curl -X GET http://your-domain.com/api/friends \
-H "Authorization: Bearer your_token_here"
# Get friends' stories
curl -X GET http://your-domain.com/api/stories \
-H "Authorization: Bearer your_token_here"
# Get my stories
curl -X GET http://your-domain.com/api/stories/my \
-H "Authorization: Bearer your_token_here"
# Create a story with content only
curl -X POST http://your-domain.com/api/stories \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{"content": "Having a great day!"}'
# Create a story with image
curl -X POST http://your-domain.com/api/stories \
-H "Authorization: Bearer your_token_here" \
-F "content=Amazing sunset!" \
-F "image=@/path/to/image.jpg"
# Get a specific story
curl -X GET http://your-domain.com/api/stories/1 \
-H "Authorization: Bearer your_token_here"
# Update a story
curl -X PUT http://your-domain.com/api/stories/1 \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{"content": "Updated story content"}'
# Delete a story
curl -X DELETE http://your-domain.com/api/stories/1 \
-H "Authorization: Bearer your_token_here"
Last Updated: September 2, 2025
API Version: 1.5
Major Changes in v1.5:
- Added comprehensive Subscription System with Stripe integration
- Support for both one-time and recurring subscription packages
- Subscription management with automatic renewals and cancellation
- Complete Stripe subscription webhook integration
- Subscription history tracking with full audit trails
- Support for multiple subscription periods (daily, weekly, monthly, yearly)
- Flexible package management with features and pricing
- Complete subscription documentation with setup instructions and examples
Major Changes in v1.4:
- Added comprehensive Payment System with Stripe integration
- Support for both Stripe Checkout Sessions and Payment Intents
- Payment history tracking with full audit trails
- Webhook integration for automatic payment status updates
- Support for multiple currencies (USD, EUR, GBP)
- Secure payment processing with signature verification
- Complete payment documentation with setup instructions and examples
Major Changes in v1.3:
- Added comprehensive Stories API section with full CRUD operations
- Stories feature supports temporary content sharing with automatic 24-hour expiration
- Added support for text and/or image content in stories
- Implemented friend-based visibility for stories
- Added JavaScript and cURL examples for all story endpoints
- Removed non-existent
/friend-requests/pendingendpoint from documentation
Major Changes in v1.2:
- Removed manual user registration endpoint - users are now created automatically during OTP flow
- Enhanced OTP authentication to create users automatically if they don't exist
- Added
is_new_userflag to/send-otpresponse to indicate new user registration - Added
profile_completeflag to/verify-otpresponse to indicate if profile needs completion - Streamlined authentication flow for better user experience
- Removed "phone number not found" errors as users are auto-created
Major Changes in v1.1:
- Replaced username/password authentication with OTP-based phone authentication
- Added comprehensive friend request system
- Enhanced user profile with phone verification status
- Deprecated traditional login in favor of secure OTP flow