33 KiB
Complete Dating App API Reference
API version:
0.6.12API reference for frontend and backend developers. Version 0.6.12 is deployed to production and verified through the public API.
1. General Information
1.1 Base URL
{{base_url}}
Local testing URL:
http://127.0.0.1:8001
The production URL is supplied by the deployment environment. Configure base_url in the client instead of hard-coding a domain.
1.2 Request Format
- JSON API requests:
Content-Type: application/json - File upload requests:
multipart/form-data - All endpoints require authentication except registration, login, OAuth, and health check.
- Authenticated endpoints use:
Authorization: Bearer <access_token> access_tokenis valid for 15 minutes by default;refresh_tokenis valid for 30 days by default.
1.3 Common Errors
Invalid parameters: 422 Unprocessable Entity
{
"detail": [
{
"loc": ["body", "field_name"],
"msg": "Description text",
"type": "validation_error"
}
]
}
Not logged in or invalid token: 401
{"detail": "需要登录"}
(需要登录 means “Authentication required.”)
Account disabled or insufficient permissions: 403
Resource not found: 404
2. Data Structures
2.1 Registration Request RegisterRequest
Required fields:
| Field | Type | Description |
|---|---|---|
email_or_phone |
string | Email address or phone number. Choose one, using this same field; 3-320 characters |
password |
string | Password, 8-128 characters |
confirm_password |
string | Password confirmation, 8-128 characters; must exactly match password |
relationship_goal |
string | Required single choice: serious_relationship, casual_dates, new_friends, or marriage |
Optional fields: full_name (maximum 120 characters), gender (maximum 30), birth_date (YYYY-MM-DD), city (maximum 120), country (maximum 120), bio (1-2000 characters after trimming), and interests (array of interests).
The user chooses either a written bio or an introduction video. For a written introduction, send a non-empty bio in the registration JSON. For a video introduction, omit bio, register first, then call /api/v1/users/me/video with the access token. A JSON registration request cannot carry the video file itself.
confirm_password is used only for validation during registration. It is not stored in the database and never appears in any response.
The registration response remains token-only. The frontend then calls GET /api/v1/auth/me: profile.profile_image_url or profile.video_url is null before the corresponding upload; in production, uploaded public media is returned as a directly loadable one-hour OSS signed URL. The introduction choices are mutually exclusive: uploading a video clears the existing bio, while submitting a non-null bio through the profile endpoint deletes the introduction video. Registration onboarding should complete only when exactly one of bio or video_url is non-null.
2.2 User UserResponse
{
"id": 1,
"email_or_phone": "user@example.com",
"status": "active",
"profile": {
"full_name": "Test User",
"gender": "female",
"birth_date": "2000-01-01",
"city": "Shanghai",
"county": "Huangpu",
"country": "China",
"relationship_goal": "serious_relationship",
"bio": "I like traveling.",
"profile_image_url": "https://<oss-host>/melove/users/1/avatar.jpg?<signature>",
"video_url": null,
"interests": ["Photography", "Movies"]
},
"posts": [],
"liked_users": [],
"liked_by_users": [],
"matches": [],
"liked_users_count": 0,
"liked_by_users_count": 0,
"matches_count": 0,
"created_at": "2026-09-01T10:00:00"
}
GET /api/v1/auth/me and GET /api/v1/users/me populate these four arrays with the 20 most recent posts, users I liked, users who liked me, and matches. liked_users_count, liked_by_users_count, and matches_count are totals for the three relationships, independent of the 20-item preview limit. Likes include both like and super_like; only active users are counted. Use the dedicated list endpoints below for complete lists. Registration, login, and profile update responses do not include these totals.
2.3 Token TokenResponse
{
"access_token": "<access_token>",
"refresh_token": "<refresh_token>",
"token_type": "bearer",
"user": { "...": "UserResponse" }
}
The registration endpoint is an exception: it returns tokens only and does not return user:
{
"access_token": "<access_token>",
"refresh_token": "<refresh_token>",
"token_type": "bearer"
}
2.4 Public User DiscoveryUserResponse
{
"id": 2,
"full_name": "Bob",
"gender": "male",
"birth_date": "1998-05-20",
"city": "Shanghai",
"county": "Huangpu",
"country": "China",
"relationship_goal": "serious_relationship",
"bio": "Likes hiking",
"profile_image_url": "https://<oss-host>/melove/users/2/avatar.jpg?<signature>",
"video_url": "https://<oss-host>/melove/users/2/intro.mp4?<signature>",
"distance_km": 1.24,
"compatibility_percentage": 85,
"media": [
{"id": 10, "media_type": "profile_image", "url": "https://<oss-host>/melove/users/2/avatar.jpg?<signature>"}
]
}
In a public-user response, profile_image_url is the avatar link and video_url is the introduction-video link. media[] lists those same public profile media items by media_type; it is not the user's posts. The dedicated public profile endpoint also returns interests and the newest 20 public posts, so the profile screen can use one response. Private contact and activity fields such as email_or_phone, likes, and matches are never included. Use GET /api/v1/users/{user_id}/posts when the client needs pagination beyond the embedded 20 posts.
2.5 Interest, Location, Post, and Chat Structures
Interest response: {"interests": ["Photography", "Dogs"]}
Location response: {"latitude": 31.2304, "longitude": 121.4737, "city": "Shanghai", "county": "Huangpu", "updated_at": "2026-09-01T10:00:00"}
Swipe response: {"direction": "like", "matched": true, "match_id": 15}. When there is no match, match_id is null.
Post response fields: id, content, post_type, content_format, feeling, location, author, like_count, comment_count, liked_by_me, liked_by_users, media, and created_at. The post's location is a snapshot of the selected place, separate from the author's current profile location.
Post and comment author objects contain id, full_name, profile_image_url, city, country, and compatibility_percentage. The avatar URL is null before upload; production returns a directly loadable one-hour OSS signed URL. Re-fetch the response after expiry. Every PostResponse (Explore, detail, profile-embedded posts, and user post lists) includes liked_by_users: [{"id": 2, "full_name": "Reader"}]. It is [] without likes, ordered newest like first, and contains active users only; like_count equals its length. Names can be null. This list contains no contact details or full user profiles.
Chat message fields: id, conversation_id, sender_id, content, message_type, is_deleted, and created_at. After a message is recalled, content is null and is_deleted is true.
3. Authentication Endpoints
3.1 Health Check
GET {{base_url}}/health
Authentication: not required.
Frontend sends: no body.
Success: 200
{"status": "ok"}
3.2 Registration
POST {{base_url}}/api/v1/auth/register
Authentication: not required.
Request body: application/json
{
"email_or_phone": "user@example.com",
"password": "password123",
"confirm_password": "password123",
"relationship_goal": "serious_relationship",
"full_name": "Test User",
"gender": "female",
"birth_date": "2000-01-01",
"city": "Shanghai",
"country": "China",
"bio": "I like traveling.",
"interests": ["Photography", "Movies"]
}
For phone registration, put the phone number in email_or_phone. Success: 201, returning the token-only structure in section 2.3.
Errors: 409 email/phone already registered; 422 required field missing, invalid relationship_goal, passwords do not match, or fewer than 2 interests.
The MVP does not use OTP; newly registered accounts are immediately active.
3.3 Login
POST {{base_url}}/api/v1/auth/login
Authentication: not required.
Request body:
{
"email_or_phone": "user@example.com",
"password": "password123"
}
Success: 200, returning TokenResponse. Errors: 401 incorrect account or password; 403 account disabled; 422 invalid parameters.
3.4 OAuth Login/Registration Placeholder
POST {{base_url}}/api/v1/auth/oauth
Authentication: not required.
Request body:
{
"provider": "google",
"provider_user_id": "google-user-id-123",
"email": "user@example.com",
"full_name": "OAuth User"
}
provider can only be wechat or google; email and full_name are optional. Success: 200, returning TokenResponse. Error: 422 invalid parameters.
3.5 Refresh Token
POST {{base_url}}/api/v1/auth/refresh
Authentication: no Bearer token is required, but a valid refresh token must be submitted.
Request body:
{"refresh_token": "<refresh_token>"}
Success: 200, returning a new TokenResponse; the old refresh token becomes invalid. Errors: 401 refresh token invalid, expired, or revoked; 422 invalid parameters.
3.6 Logout
POST {{base_url}}/api/v1/auth/logout
Authentication: no Bearer token is required.
Request body:
{"refresh_token": "<refresh_token>"}
Success: 200
{"message": "已退出登录"}
(已退出登录 means “Logged out.”)
3.7 Get Current User
GET {{base_url}}/api/v1/auth/me
Authentication: Bearer token required.
Frontend sends: no body.
Success: 200, returning UserResponse. The app can call this endpoint at startup to determine whether the token is still valid.
3.8 Get Current User (Compatibility Path)
GET {{base_url}}/api/v1/users/me
Authentication: Bearer token required.
This is exactly the same as section 3.7 and returns UserResponse.
4. User Profile and Media
4.1 Update Profile
PATCH {{base_url}}/api/v1/users/me
Authentication: Bearer token required.
Only fields that need to change have to be included:
{
"full_name": "New Name",
"gender": "female",
"birth_date": "2000-01-01",
"city": "Beijing",
"country": "China",
"relationship_goal": "marriage",
"bio": "Updated bio",
"occupation": "Creative Director at Studio Drift",
"education": "NYU Gallatin School",
"height_cm": 175,
"beliefs": "spiritual_agnostic",
"smoking": "non_smoker",
"drinking": "socially",
"pets": "golden_retriever",
"exercise": "active_4x_week",
"interests": ["Travel", "Music"]
}
Additional optional profile fields: occupation and education (max 120 characters each), height_cm (integer, 100-250), beliefs (max 120), smoking and drinking (max 50 each), pets (max 120), and exercise (max 50). Send null to clear any of them. GET /api/v1/auth/me returns all of these values and a computed age derived from birth_date.
Success: 200, returning the updated UserResponse. If provided, interests must contain at least 2 items. Error: 422 invalid field format or invalid interests.
4.2 Get My Interests
GET {{base_url}}/api/v1/users/me/interests
Authentication: Bearer token required.
Success: 200
{"interests": ["Photography", "Dogs", "My custom interest"]}
4.3 Replace My Interests
PUT {{base_url}}/api/v1/users/me/interests
Authentication: Bearer token required.
Request body:
{"interests": ["Photography", "Dogs", "My custom interest"]}
At least 2 items are required; there is no maximum. Other does not require a special field; submit the custom text directly as an array item. Success: 200, returning the interest array. Errors: 422 fewer than 2 items, an empty string, or an item longer than 120 characters.
4.4 Update Location
PUT {{base_url}}/api/v1/users/me/location
Authentication: Bearer token required.
Request body:
{"latitude": 31.2304, "longitude": 121.4737, "city": "Shanghai", "county": "Huangpu"}
Latitude must be between -90 and 90; longitude must be between -180 and 180. city and county are required, trimmed strings of 1-120 characters. The city updates the profile used for same-city ranking; county is a district, separate from country. Success: 200, returning all four location fields and updated_at. Current-user and public-user profile responses also include county. Error: 422 missing or invalid fields.
4.5 Upload Introduction Video
POST {{base_url}}/api/v1/users/me/video
Authentication: Bearer token required.
Request format: multipart/form-data, field name video; supports video/mp4, video/quicktime, and video/webm; maximum 100 MB.
Each user keeps only the latest introduction video. A successful new upload replaces the previous database record and stored file. If storing the new file fails, the previous video remains unchanged.
Success: 200
{"id": 11, "media_type": "intro_video", "review_status": "pending"}
Errors: 413 file too large; 415 unsupported file type; 422 no file uploaded.
4.6 Upload Profile Image
POST {{base_url}}/api/v1/users/me/image
Authentication: Bearer token required.
Request format: multipart/form-data, field name image; supports JPG, PNG, and WebP; maximum 10 MB.
Each user keeps only the latest profile image. A successful new upload replaces the previous database record and stored file. If storing the new file fails, the previous image remains unchanged.
Success returns the media ID, type, and review status. Errors: 413, 415, 422.
4.7 Upload ID Card/Passport
POST {{base_url}}/api/v1/users/me/identity
Authentication: Bearer token required.
Request format: multipart/form-data, field name document; supports JPG, PNG, and PDF; maximum 10 MB.
Success returns the media ID, type, and review status. This file is accessible only by its owner. Errors: 413, 415, 422.
4.8 Get User Media
GET {{base_url}}/api/v1/media/{media_id}
Authentication: Bearer token required.
Path parameter: media_id is the media ID.
Success: 200, returning the file binary. Public profile images/videos can be read by other active users; ID cards/passports can be read only by the owner. Errors: 403 permission denied; 404 file not found.
In production OSS, profile_image_url, video_url, and media[].url in Meet, Search, Match, and profile responses are complete signed URLs valid for one hour. Image/video widgets can load them directly without another Bearer-token request. Fetch the containing API response again after expiry to obtain fresh URLs. Identity documents are never included in these public URL fields.
4.9 Get My Posts
GET {{base_url}}/api/v1/users/me/posts?limit=20&offset=0
Authentication: Bearer token required. limit is 1-50 and offset is 0-5000. Returns a newest-first PostResponse array.
4.10 Get a Public User Profile
GET {{base_url}}/api/v1/users/{user_id}
Authentication: Bearer token required. Returns PublicUserProfileResponse, which includes the public profile fields, interests, and the newest 20 posts; 404 if the target is missing or inactive. It never returns email_or_phone, account status, likes, or matches.
4.11 Get a Public User's Posts
GET {{base_url}}/api/v1/users/{user_id}/posts?limit=20&offset=0
Authentication: Bearer token required. Returns the user's posts newest first; 404 if the target is missing or inactive.
4.12 Get Users I Liked
GET {{base_url}}/api/v1/users/me/liked
Authentication: Bearer token required. Returns all public profiles for users the current user liked, latest action first. Users the current user has already matched with are excluded. This endpoint does not paginate; legacy limit and offset query parameters are ignored.
4.13 Get Users Who Liked Me
GET {{base_url}}/api/v1/users/me/likes
Authentication: Bearer token required. Returns all public profiles for users who liked the current user, latest action first. Users the current user has already matched with are excluded. This endpoint does not paginate; legacy limit and offset query parameters are ignored.
4.14 Get My Matches
GET {{base_url}}/api/v1/users/me/matches
Authentication: Bearer token required. Returns the same complete MatchResponse array as /api/v1/matches, for profile-section loading. The endpoint does not paginate; legacy limit and offset query parameters are ignored.
- Meet, Swipe, Match, and Search
5.1 Get Recommended Users
GET {{base_url}}/api/v1/meet?limit=20
Authentication: Bearer token required.
Query parameter: optional limit, 1-50, default 20.
Success: 200, returning an array of DiscoveryUserResponse; only active users are returned, and users already swiped on are not returned again. Users who sent the current user an active super_like appear first. The rest are ordered by same city, same country, and then other countries; higher compatibility_percentage comes first within each group.
5.2 Swipe Left, Right, or Super Like
POST {{base_url}}/api/v1/meet/swipes
Authentication: Bearer token required.
Request body:
{"target_user_id": 2, "direction": "like"}
direction is pass, like, or super_like. A Super Like counts as a like for matching and keeps the sender at the top of the target's Meet list until canceled or changed, unless the target has already swiped the sender. Success: 200, returning SwipeResponse; when both users choose like or super_like, matched=true and a match_id is generated. Errors: 400 cannot operate on yourself; 404 target user not found; 422 invalid parameters.
5.3 Unlike a User
DELETE {{base_url}}/api/v1/meet/likes/{target_user_id}
Authentication: Bearer token required.
Success: 200 {"message":"已取消喜欢"} ("Like removed"), and the corresponding match is dissolved. This legacy endpoint accepts like and super_like; use the undo endpoint for the Cancel button. Error: 404 no like record to remove.
5.4 Cancel Button: Undo Any Swipe
DELETE {{base_url}}/api/v1/meet/swipes/{target_user_id}
Authentication: Bearer token required. Removes the current user's saved pass, like, or super_like, allowing that target to appear in Meet again. Undoing a Super Like immediately removes its priority in the target's Meet list. Undoing a matched like or Super Like also dissolves the match. Success: 200, for example {"message":"已撤销滑动","previous_direction":"super_like"}. Error: 404 no swipe to undo.
5.5 Get Matches
GET {{base_url}}/api/v1/matches
Authentication: Bearer token required.
The endpoint does not paginate and returns every active match, most recent first; legacy limit and offset query parameters are ignored. Success: 200, returning an array of MatchResponse; each item contains id, matched_at, and the other user's public profile in user. The number of items equals matches_count from GET /api/v1/auth/me.
5.6 Search Users
GET {{base_url}}/api/v1/search
Authentication: Bearer token required.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
q |
string | Optional; searches name, bio, city, and country; maximum 200 characters |
city |
string | Optional city filter; can be combined with country |
country |
string | Optional country filter; can be combined with city |
latitude |
number | Optional; must be supplied together with longitude |
longitude |
number | Optional; must be supplied together with latitude |
max_distance_km |
number | Optional, maximum 500; without coordinates, the user's own location is used |
limit |
integer | Optional, 1-50, default 20 |
Success: 200, returning an array of DiscoveryUserResponse with country and compatibility_percentage. Errors: 422 only one coordinate provided, or distance filtering requested without coordinates or the user's own location.
- Explore Feed
6.1 Create a Post
POST {{base_url}}/api/v1/explore/posts
Authentication: Bearer token required.
Request body:
{
"content": "Feeling **happy** and *relaxed* today",
"post_type": "text",
"content_format": "markdown",
"feeling": "happy",
"location": {"name": "Blue Bottle Coffee, Soho NYC", "latitude": 40.723, "longitude": -73.998}
}
post_type is text, image, or video (default text). content is at most 5,000 characters; a text post needs non-empty content, a feeling, or a location. Optional feeling is a 1-50 character label or code such as happy. Optional location.name is 1-255 characters; latitude and longitude may be omitted together or provided together within their normal geographic ranges. This place is stored on the post and does not change the author's profile or current location. The selected place and submitted coordinates are visible to users who can view the post.
content_format is plain by default. For the editor's bold and italic buttons, submit Markdown text such as **bold** and *italic* with content_format: "markdown". The API returns the Markdown source, not HTML; the frontend must render it safely. Existing posts remain plain. Music is not supported yet.
For an image or video post, create the post first, then upload its files through section 6.5 using the returned post ID. A post supports up to nine images and one video together in one post; when both are present the post's post_type becomes mixed. Success: 201, returning PostResponse with the new fields.
6.2 Get Explore Feed
GET {{base_url}}/api/v1/explore/feed?limit=20&offset=0
Authentication: Bearer token required.
Parameters: limit 1-50, default 20; use offset for pagination with the new ranking. The legacy before_id remains available as a chronological ID cutoff and cannot be combined with a nonzero offset. Success: 200, with content_format, feeling, and location on every post; authors in the same city are first, followed by the same country and other countries, with higher compatibility first and recency breaking remaining ties.
6.3 Get Post Details
GET {{base_url}}/api/v1/explore/posts/{post_id}
Authentication: Bearer token required.
Success: 200, returning PostResponse; error: 404 post not found or author unavailable.
6.4 Delete a Post
DELETE {{base_url}}/api/v1/explore/posts/{post_id}
Authentication: Bearer token required. Only the post author can delete it. Success: 200 {"message":"帖子已删除"} ("Post deleted"); errors: 403 permission denied; 404 not found.
6.5 Upload Post Media
POST {{base_url}}/api/v1/explore/posts/{post_id}/media?position=0
Authentication: Bearer token required, and the caller must be the post author.
Request format: multipart/form-data, field name media; optional position, 0-8. Supports JPG, PNG, WebP, MP4, MOV, and WebM.
Each post supports up to 9 images and 1 video in one post; images and video can now be mixed. When both are present, post_type is mixed. Success: 201, returning PostMediaResponse (id, media_type, mime_type, size_bytes, position, url). In production OSS, url is a complete one-hour signed URL that can be loaded without a Bearer header. Errors: 403, 404, 409 media type/count conflict, 413, 415.
6.6 Get Post Media
GET {{base_url}}/api/v1/explore/media/{media_id}
Authentication: Bearer token required.
Success: 200, returning the file binary. Error: 404 post or media not found.
6.7 Like a Post
POST {{base_url}}/api/v1/explore/posts/{post_id}/like
Authentication: Bearer token required.
Success: 200, returning compact PostLikeResponse, without the full post content, author, or media:
{
"message": "like successful",
"like_count": 1,
"liked_by_me": true,
"liked_by_users": [{"id": 2, "full_name": "Reader"}]
}
Repeated likes do not increase the count. Update the existing post using the returned count, state, and list. Errors: 401 unauthenticated; 404 post not found.
Query Post Likers Separately
GET {{base_url}}/api/v1/explore/posts/{post_id}/like
Authentication: Bearer token required. No body or pagination parameters. Success: 200, directly returning [{"id": 2, "full_name": "Reader"}], or [] without likes. Only active users appear, newest like first. Errors: 401 unauthenticated; 404 post not found.
6.8 Unlike a Post
DELETE {{base_url}}/api/v1/explore/posts/{post_id}/like
Authentication: Bearer token required.
Success: 200, returning a compact PostUnlikeResponse — {"message":"unlike successful","like_count":0,"liked_by_me":false,"liked_by_users":[...]} — instead of the full post. Repeated requests keep the post unliked.
6.9 Get Comments
GET {{base_url}}/api/v1/explore/posts/{post_id}/comments
Authentication: Bearer token required.
Success: 200, returning an array of CommentResponse, including author.profile_image_url (or null without an avatar). Each comment also contains parent_comment_id, reply_count, like_count, and liked_by_me. Comments are returned as a flat list; group replies by parent_comment_id on the frontend. Error: 404 post not found.
6.10 Add a Comment
POST {{base_url}}/api/v1/explore/posts/{post_id}/comments
Authentication: Bearer token required.
Request body:
{"content": "Looks great!"}
Content must be 1-2,000 characters and cannot consist only of whitespace. To reply to an existing comment, also send parent_comment_id:
{"content": "I agree", "parent_comment_id": 12}
Success: 201, returning CommentResponse with author.profile_image_url; errors: 404 post or parent comment not found; 422 invalid content.
6.11 Delete a Comment
DELETE {{base_url}}/api/v1/explore/comments/{comment_id}
Authentication: Bearer token required. Only the comment author can delete it. Success: 200 {"message":"评论已删除"} ("Comment deleted"); errors: 403, 404.
6.12 Edit a Post
PATCH {{base_url}}/api/v1/explore/posts/{post_id}
Authentication: Bearer token required; only the post author can edit it.
Request body (send only the fields to change):
{"content": "Updated text", "content_format": "markdown", "feeling": "happy", "location": {"name": "New place"}}
Editable fields: content (max 5000 characters), content_format (plain/markdown), feeling (1-50 characters), and location. Omitted fields stay unchanged; send null to clear feeling or location. A text post must keep at least one of non-empty content, feeling, or location. Success: 200, returning the full updated PostResponse. Errors: 403 not the author; 404 post not found; 422 invalid fields.
6.13 Edit a Comment
PATCH {{base_url}}/api/v1/explore/comments/{comment_id}
Authentication: Bearer token required; only the comment author can edit it.
Request body:
{"content": "Updated comment"}
content must be 1-2,000 characters and cannot be blank. Success: 200, returning the updated CommentResponse. Errors: 403 not the author; 404 comment not found; 422 invalid content.
6.14 Like a Comment
POST {{base_url}}/api/v1/explore/comments/{comment_id}/like
Authentication: Bearer token required. No request body.
Success: 200
{"message": "like successful", "like_count": 1, "liked_by_me": true}
Repeated likes do not increase the count. Errors: 401 unauthenticated; 404 comment not found or post author inactive.
6.15 Unlike a Comment
DELETE {{base_url}}/api/v1/explore/comments/{comment_id}/like
Authentication: Bearer token required. No request body.
Success: 200
{"message": "unlike successful", "like_count": 0, "liked_by_me": false}
Repeated requests keep the comment unliked. Errors: 401 unauthenticated; 404 comment not found or post author inactive.
7. Feed Questions and Answers
7.1 Get Feed Questions
GET {{base_url}}/api/v1/feed/questions?unanswered_only=false
Authentication: Bearer token required.
Optional unanswered_only, default false; set to true to return only questions the current user has not answered. Success: 200, returning an array of questions. Each item contains id, code, the question in Chinese and English, answer_type (boolean or text), the current answer, and the answer time.
7.2 Get My Feed Answers
GET {{base_url}}/api/v1/feed/answers/me
Authentication: Bearer token required.
Success: 200, returning an array of questions answered by the current user.
7.3 Create or Update a Feed Answer
PUT {{base_url}}/api/v1/feed/questions/{question_id}/answer
Authentication: Bearer token required.
Example request body for a Boolean question:
{"answer": true}
For a text question:
{"answer": "Coffee and a walk"}
Success: 200
{"question_id": 1, "answer": true, "answered_at": "2026-09-01T10:00:00"}
Errors: 404 question not found or disabled; 422 answer type does not match the question or content is empty.
7.4 Delete a Feed Answer
DELETE {{base_url}}/api/v1/feed/questions/{question_id}/answer
Authentication: Bearer token required.
Success: 200 {"message":"回答已删除"} ("Answer deleted"); error: 404 answer not found.
8. Chat
Chat is available only to users who have matched each other.
8.1 Get Conversation List
GET {{base_url}}/api/v1/chat/conversations
Authentication: Bearer token required.
Success: 200, returning an array of conversations. Each item contains id, match_id, other_user, last_message, created_at, and updated_at.
8.2 Create or Get a Conversation
POST {{base_url}}/api/v1/chat/conversations/{match_id}
Authentication: Bearer token required; the caller must be one of the users in the match.
Frontend sends: no body.
Success: 201, returning ConversationResponse; if the conversation already exists, the existing conversation is returned. Errors: 404 match not found, caller is not one of the matched users, or the other user is unavailable.
8.3 Get Chat Messages
GET {{base_url}}/api/v1/chat/conversations/{conversation_id}/messages?limit=50&before_id=100
Authentication: Bearer token required; the caller must be one of the conversation participants.
Parameters: limit 1-100, default 50; optional before_id for pagination. Success: 200, returning a ChatMessageResponse array in chronological order.
8.4 Send a Chat Message
POST {{base_url}}/api/v1/chat/conversations/{conversation_id}/messages
Authentication: Bearer token required; the caller must be one of the conversation participants.
Request body:
{
"content": "Hello, nice to meet you!",
"message_type": "text"
}
content must be 1-5,000 characters; message_type currently can only be text. Success: 201, returning ChatMessageResponse; errors: 404 conversation not found or access denied; 422 empty or malformed content.
8.5 Recall a Chat Message
DELETE {{base_url}}/api/v1/chat/messages/{message_id}
Authentication: Bearer token required. Only the sender can recall a message. Success: 200, returning the recalled ChatMessageResponse (content=null, is_deleted=true); errors: 403 message was not sent by the caller; 404 message not found or access denied.
9. Frontend Registration and Login Flow
- The registration page displays email/phone, password, password confirmation, and optional profile fields.
- The frontend first checks
password === confirm_password, then submits both password fields to the registration endpoint. - After successful registration, save the returned
access_tokenandrefresh_token. Do not save the confirmation password. - Do not call an OTP endpoint; this version has no
/api/v1/auth/request-codeor/api/v1/auth/verify. - At app startup, call
/api/v1/auth/me. A200response enters the home page; a401response clears the tokens and returns to the login page. - When the access token expires, call
/api/v1/auth/refreshwith the refresh token and save the new token pair. If refreshing fails, require the user to log in again.
10. Postman Import and Testing
- Open Postman and click
Import->Files. - Import
docs/openapi-0.6.12.jsonand choose to import it as a Postman Collection. - In Collection Variables, set:
base_url = http://127.0.0.1:8001. - First send
POST /api/v1/auth/registerorPOST /api/v1/auth/login. - Copy the
access_tokenfrom the response. For requests requiring authentication, selectAuthorization->Bearer Tokenand paste it. - For file-upload tests, choose
Body->form-data; the file field name must match the field name specified in this document.
Recommended minimum test order: health -> register -> login -> auth/me -> update profile -> meet -> swipe -> matches -> explore -> feed questions -> chat.
11. Specification Files
Complete machine-readable API definition: docs/openapi-0.6.12.json. If this document and the OpenAPI definition differ, follow the current backend /openapi.json and the latest OpenAPI file.