Download OpenAPI specification:
Onboarding-first API for a Laravel 12 + MariaDB backend using Sanctum tokens. All endpoints are JSON and Bearer token authenticated unless otherwise noted.
Authentication endpoints for registration, OTP verification, and login.
Creates a user and sends an OTP to the provided email.
| email required | string <email> <= 191 characters |
| password required | string >= 8 characters |
{- "email": "jane@gmail.com",
- "password": "Secret12345"
}{- "message": "OTP sent to email for verification.",
- "success": true,
- "code": 201
}| channel required | string Default: "email" Enum: "email" "sms" "whatsapp" |
| destination required | string |
| purpose required | string Enum: "password_reset" "register" |
{- "channel": "email",
- "destination": "jane@gmail.com",
- "purpose": "register"
}{- "message": "OTP resent to jane@gmail.com",
- "success": true,
- "code": 201
}| channel required | string Default: "email" Enum: "email" "sms" "whatsapp" |
| destination required | string |
| code required | string^\d{4}$ |
| device_name | string |
| device_os | string |
| os_version | string |
{- "channel": "email",
- "destination": "jane@gmail.com",
- "code": "1234",
- "device_name": "Pixel 9",
- "device_os": "android | ios",
- "os_version": "android 14 | ios 18"
}{- "message": "Verification successful.",
- "success": true,
- "code": 200,
- "results": {
- "token_type": "Bearer",
- "token": "2|5c0f9...long_plain_text_token"
}
}| email required | string <email> |
| password required | string |
| device_name required | string |
| device_os | string |
| os_version | string |
{- "email": "jane@gmail.com",
- "password": "Password1234567",
- "device_name": "Pixel 9",
- "device_os": "android | ios",
- "os_version": "android 14 | ios 18"
}{- "message": "Login Success",
- "success": true,
- "code": 200,
- "results": {
- "token_type": "Bearer",
- "token": "2|5c0f9...long_plain_text_token"
}
}| channel required | string Default: "email" Enum: "email" "sms" "whatsapp" |
| email required | string |
{- "channel": "email",
- "email": "jane@gmail.com"
}{- "message": "OTP sent to email for password reset.",
- "success": true,
- "code": 201
}| email required | string |
| code required | string^\d{4}$ |
{- "email": "jane@gmail.com",
- "code": "1234"
}{- "message": "OTP verified.",
- "success": true,
- "code": 200,
- "results": {
- "reset_token": "01K76KN4MBPDBS97J9G9VTCW33",
- "expires_in": 900
}
}| email required | string |
| reset_token required | string |
| password required | string |
| password_confirmation required | string |
{- "email": "jane@gmail.com",
- "reset_token": "01K76KN4MBPDBS97J9G9VTCW33",
- "password": "Jane1234567890",
- "password_confirmation": "Jane1234567890"
}{- "message": "Password updated. You can now log in.",
- "success": true,
- "code": 200
}Profile setup endpoints used in the mobile onboarding flow: nickname, DOB, gender, relationship goals, distance preference, and interests.
| nickname | string or null <= 50 characters |
| date_of_birth | string or null <date> |
| gender | string or null Enum: "male" "female" "other" |
| relationship_goal | string or null Enum: "dating" "friendship" "casual" "serious" |
object (distance-pref-input) |
{- "nickname": "jane_",
- "date_of_birth": "1997-07-21",
- "gender": "female",
- "relationship_goal": "serious",
- "distance_pref": {
- "value": 30,
- "unit": "km"
}
}{- "message": "Profile updated.",
- "success": true,
- "code": 200,
- "results": {
- "nickname": "jane_",
- "date_of_birth": "1997-07-21",
- "gender": "female",
- "relationship_goal": "dating",
- "distance_pref_meters": {
- "value": 30,
- "unit": "km"
}
}
}| interest_ids required | Array of strings (ulid) <= 10 items |
{- "interest_ids": [
- "01J8Z2A0CJ6EJ2S2H4Q8GQJ0ZC",
- "01J8Z2A3DGTN0Q0ZX8Q6Y7Q5MS"
]
}{- "message": "Interests updated.",
- "success": true,
- "code": 200
}| photo required | string <binary> |
| position | integer [ 1 .. 6 ] |
curl -X POST "{{BASE}}/onboarding/photos" \ -H "Accept: application/json" -H "Authorization: Bearer {{TOKEN}}" \ -F "photo=@/path/to/photo.jpg" -F "position=1"
{- "message": "Photo created.",
- "success": true,
- "code": 201,
- "results": {
- "id": "01J8Z1TB9KVFQ0Y7ZP6F8F1K3V",
- "position": 1,
- "width": "200,",
- "height": "500,",
- "mime": "jpeg"
}
}| photoId required | string (ulid) Example: 01J8Z1TB9KVFQ0Y7ZP6F8F1K3V ULID identifier as string (26 chars). |
| position required | integer [ 1 .. 6 ] |
{- "position": 2
}{- "message": "Photo reordered successfully.",
- "success": true,
- "code": 200,
- "results": {
- "id": "01J8Z1TB9KVFQ0Y7ZP6F8F1K3V",
- "position": 1,
- "width": "200,",
- "height": "500,",
- "mime": "jpeg"
}
}| photoId required | string (ulid) Example: 01J8Z1TB9KVFQ0Y7ZP6F8F1K3V ULID identifier as string (26 chars). |
curl -X DELETE "{{BASE}}/onboarding/photos/01J8Z1TZ9MB5ZP7VQ9CQP6K0TE" \ -H "Accept: application/json" -H "Authorization: Bearer {{TOKEN}}"
{- "message": "Photo removed.",
- "status": true,
- "code": 200
}Endpoints for the currently authenticated user, returning a consolidated snapshot: user, profile, interests, and photos.
curl "{{BASE}}/me" -H "Accept: application/json" -H "Authorization: Bearer {{TOKEN}}"
{- "user": {
- "id": 123,
- "f_name": "string",
- "l_name": "string",
- "email": "user@example.com",
- "email_verified": true
}, - "profile": {
- "message": "Profile updated.",
- "success": true,
- "code": 200,
- "results": {
- "nickname": "jane_",
- "date_of_birth": "1997-07-21",
- "gender": "female",
- "relationship_goal": "dating",
- "distance_pref_meters": {
- "value": 30,
- "unit": "km"
}
}
}, - "interests": [
- {
- "message": "Interest List.",
- "success": true,
- "code": 200,
- "results": {
- "id": "01J8Z1TB9KVFQ0Y7ZP6F8F1K3V",
- "category": "Aktivitas Sosial",
- "sub_category": "Suka Berkumpul dengan Teman",
- "name": "Hangout",
- "slug": "suka-berkumpul-dengan-teman-hangout",
- "status": "Active"
}
}
], - "photos": [
- {
- "id": "01J8Z1TB9KVFQ0Y7ZP6F8F1K3V",
- "position": 1,
- "width": 0,
- "height": 0,
- "mime": "string"
}
]
}| userId required | integer (id) Example: 123 UserId of the user you matched |
curl "{{BASE}}/me/01J8Z1TZ9MB5ZP7VQ9CQP6K0TE" -H "Accept: application/json" -H "Authorization: Bearer {{TOKEN}}"
{- "user": {
- "id": 123,
- "f_name": "string",
- "l_name": "string",
- "email": "user@example.com",
- "email_verified": true
}, - "profile": {
- "message": "Profile updated.",
- "success": true,
- "code": 200,
- "results": {
- "nickname": "jane_",
- "date_of_birth": "1997-07-21",
- "gender": "female",
- "relationship_goal": "dating",
- "distance_pref_meters": {
- "value": 30,
- "unit": "km"
}
}
}, - "interests": [
- {
- "message": "Interest List.",
- "success": true,
- "code": 200,
- "results": {
- "id": "01J8Z1TB9KVFQ0Y7ZP6F8F1K3V",
- "category": "Aktivitas Sosial",
- "sub_category": "Suka Berkumpul dengan Teman",
- "name": "Hangout",
- "slug": "suka-berkumpul-dengan-teman-hangout",
- "status": "Active"
}
}
], - "photos": [
- {
- "id": "01J8Z1TB9KVFQ0Y7ZP6F8F1K3V",
- "position": 1,
- "width": 0,
- "height": 0,
- "mime": "string"
}
]
}Saves or updates the authenticated user's location for discovery. Requires Bearer token (Sanctum).
| lat required | number [ -90 .. 90 ] |
| lng required | number [ -180 .. 180 ] |
| city | string or null <= 120 characters |
| country | string or null <= 120 characters |
{- "lat": -6.2,
- "lng": 106.8167,
- "city": "Jakarta",
- "country": "ID"
}{- "message": "Location updated.",
- "location": {
- "lat": -6.2,
- "lng": 106.8167,
- "city": "Jakarta",
- "country": "ID",
- "updated_at": "2025-08-29T08:15:30Z"
}
}Candidate discovery, reactions (like/pass), and match creation/listing. Powers the swipe deck and βIt's a match!β flow.
curl -s "{{BASE}}/me/preferences" \ -H "Accept: application/json" -H "Authorization: Bearer {{TOKEN}}"
{- "gender_pref": "female",
- "age_min": 21,
- "age_max": 35,
- "max_distance_meters": 30000,
- "discoverable": true
}Partial update. Any omitted field keeps its current value.
Server enforces age_max >= age_min and bounds.
| gender_pref | string or null Enum: "male" "female" "other" "everyone" null |
| age_min | integer [ 18 .. 100 ] |
| age_max | integer [ 18 .. 100 ] |
| max_distance_meters | integer [ 1000 .. 200000 ] |
| discoverable | boolean |
{- "age_min": 19,
- "age_max": 45,
- "max_distance_meters": 60000
}{- "gender_pref": "female",
- "age_min": 21,
- "age_max": 35,
- "max_distance_meters": 30000,
- "discoverable": true
}Returns a paginated list of candidates filtered by the user's preferences and location. Requires the user to have a saved location.
| limit | integer [ 1 .. 50 ] Default: 20 Page size (max 50). |
| page | integer >= 1 Default: 1 Page number. |
curl -s "{{BASE}}/discover?limit=20" \ -H "Accept: application/json" -H "Authorization: Bearer {{TOKEN}}"
{- "data": [
- {
- "id": "01...",
- "nickname": "Jane",
- "age": 28,
- "shared_interests_count": 2,
- "photos": [ ],
- "interests": [ ]
}
], - "next_page": 2
}| userId required | integer (id) Example: 123 UserId of the user you react to |
| action required | string Enum: "like" "pass" |
{- "action": "like"
}{- "message": "Recorded.",
- "liked": true,
- "matched": true,
- "match_id": "01J8Z9M4D4C9V5E4K4P5C9S3NQ"
}curl -s "{{BASE}}/matches" \ -H "Accept: application/json" -H "Authorization: Bearer {{TOKEN}}"
[- {
- "match_id": "01J8Z9M4D4C9V5E4K4P5C9S3NQ",
- "matched_at": "2025-08-29T08:30:00Z",
- "partner": {
- "id": 123,
- "name": "Jane Doe",
- "nickname": "Jane",
- "gender": "female",
}
}
]1:1 conversations automatically created when two users match.
last_message_at.Returns conversations for the current user ordered by last_message_at (desc),
with partner info, last message, and unread_count.
| limit | integer [ 1 .. 50 ] Default: 20 Page size (max 50). |
| page | integer >= 1 Default: 1 Page number. |
curl -s "{{BASE}}/conversations?limit=20" \ -H "Accept: application/json" -H "Authorization: Bearer {{TOKEN}}"
[- {
- "id": "01M0SG6J6N9Z6DJ6Z4JHH6X8H5",
- "last_message_at": "2025-08-29T09:57:00Z",
- "unread_count": 2,
- "partner": {
- "id": 123,
- "name": "Jane Doe",
- "nickname": "Jane",
}, - "last_message": {
- "id": "01M0SG6J7β¦",
- "body": "hi there π",
- "sender_id": "01J8Z1TB9KVFQ0Y7ZP6F8F1K3V",
- "mine": false,
- "created_at": "2025-08-29T09:57:00Z",
- "read_at": null
}
}
]Returns messages in ascending order. Partner messages are marked as read when fetched.
| conversationId required | string (ulid) Example: 01J8Z1TB9KVFQ0Y7ZP6F8F1K3V ULID identifier as string (26 chars). |
| limit | integer [ 1 .. 100 ] Default: 30 Page size (max 100). |
| page | integer >= 1 Default: 1 Page number. |
curl -s "{{BASE}}/conversations/01M0SG6J6N9Z6DJ6Z4JHH6X8H5/messages?limit=30" \ -H "Accept: application/json" -H "Authorization: Bearer {{TOKEN}}"
[- {
- "id": "01M0SG6J6N9Z6DJ6Z4JHH6X8H5",
- "body": "hi there π",
- "sender_id": 123,
- "mine": true,
- "created_at": "2025-08-29T09:57:00Z",
- "read_at": null
}
]| conversationId required | string (ulid) Example: 01J8Z1TB9KVFQ0Y7ZP6F8F1K3V ULID identifier as string (26 chars). |
| body required | string [ 1 .. 2000 ] characters |
{- "body": "hi there π"
}{- "id": "01M0SG6J6N9Z6DJ6Z4JHH6X8H5",
- "body": "hi there π",
- "sender_id": 123,
- "mine": true,
- "created_at": "2025-08-29T09:57:00Z",
- "read_at": null
}Marks partner messages as read.
If up_to_message_id is provided, marks reads up to and including that message.
| conversationId required | string (ulid) Example: 01J8Z1TB9KVFQ0Y7ZP6F8F1K3V ULID identifier as string (26 chars). |
| up_to_message_id | string (ulid) ULID of the last message to mark as read. |
{ }{- "message": "Read state updated."
}curl "{{BASE}}/meta/interests" -H "Accept: application/json"
{- "message": "Interest List.",
- "success": true,
- "code": 200,
- "results": {
- "id": "01J8Z1TB9KVFQ0Y7ZP6F8F1K3V",
- "category": "Aktivitas Sosial",
- "sub_category": "Suka Berkumpul dengan Teman",
- "name": "Hangout",
- "slug": "suka-berkumpul-dengan-teman-hangout",
- "status": "Active"
}
}