# 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 ```text {{base_url}} ``` Local testing URL: ```text 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` 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` ```json { "detail": [ { "loc": ["body", "field_name"], "msg": "Description text", "type": "validation_error" } ] } ``` Not logged in or invalid token: `401` ```json {"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` ```json { "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:///melove/users/1/avatar.jpg?", "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` ```json { "access_token": "", "refresh_token": "", "token_type": "bearer", "user": { "...": "UserResponse" } } ``` The registration endpoint is an exception: it returns tokens only and does not return `user`: ```json { "access_token": "", "refresh_token": "", "token_type": "bearer" } ``` ### 2.4 Public User `DiscoveryUserResponse` ```json { "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:///melove/users/2/avatar.jpg?", "video_url": "https:///melove/users/2/intro.mp4?", "distance_km": 1.24, "compatibility_percentage": 85, "media": [ {"id": 10, "media_type": "profile_image", "url": "https:///melove/users/2/avatar.jpg?"} ] } ``` 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` ```json {"status": "ok"} ``` ### 3.2 Registration **POST** `{{base_url}}/api/v1/auth/register` Authentication: not required. Request body: `application/json` ```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: ```json { "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: ```json { "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: ```json {"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: ```json {"refresh_token": ""} ``` Success: `200` ```json {"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: ```json { "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` ```json {"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: ```json {"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: ```json {"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` ```json {"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. 5. 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: ```json {"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. 6. Explore Feed ### 6.1 Create a Post **POST** `{{base_url}}/api/v1/explore/posts` Authentication: Bearer token required. Request body: ```json { "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: ```json { "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: ```json {"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`: ```json {"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): ```json {"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: ```json {"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` ```json {"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` ```json {"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: ```json {"answer": true} ``` For a text question: ```json {"answer": "Coffee and a walk"} ``` Success: `200` ```json {"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: ```json { "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.