蜜恋后端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

33 KiB

Complete Dating App API Reference

API version: 0.6.12 API 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_token is valid for 15 minutes by default; refresh_token is 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.

  1. Meet, Swipe, Match, and Search

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.

  1. 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

  1. The registration page displays email/phone, password, password confirmation, and optional profile fields.
  2. The frontend first checks password === confirm_password, then submits both password fields to the registration endpoint.
  3. After successful registration, save the returned access_token and refresh_token. Do not save the confirmation password.
  4. Do not call an OTP endpoint; this version has no /api/v1/auth/request-code or /api/v1/auth/verify.
  5. At app startup, call /api/v1/auth/me. A 200 response enters the home page; a 401 response clears the tokens and returns to the login page.
  6. When the access token expires, call /api/v1/auth/refresh with the refresh token and save the new token pair. If refreshing fails, require the user to log in again.

10. Postman Import and Testing

  1. Open Postman and click Import -> Files.
  2. Import docs/openapi-0.6.12.json and choose to import it as a Postman Collection.
  3. In Collection Variables, set: base_url = http://127.0.0.1:8001.
  4. First send POST /api/v1/auth/register or POST /api/v1/auth/login.
  5. Copy the access_token from the response. For requests requiring authentication, select Authorization -> Bearer Token and paste it.
  6. 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.