{"openapi":"3.1.0","info":{"title":"PickMe API","version":"1.0.0","description":"Travel expert marketplace API. Browse verified local guides, book travel experiences, and interact with a multilingual community.","contact":{"email":"hello@pickme.travel"},"license":{"name":"Proprietary"}},"servers":[{"url":"http://localhost:3000","description":"Production"}],"tags":[{"name":"Public","description":"Unauthenticated read-only endpoints (60 req/min per IP)"},{"name":"Auth","description":"Authentication endpoints (10 req/15min per IP)"},{"name":"Push","description":"Push notification token management"},{"name":"Messaging","description":"Real-time messaging credentials"},{"name":"Expert","description":"Expert-specific endpoints"},{"name":"Actions","description":"Two-step confirmation for high-risk API key operations"}],"paths":{"/api/v1/experts":{"get":{"operationId":"listExperts","summary":"List verified experts","tags":["Public"],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1}},{"name":"pageSize","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"locale","in":"query","schema":{"type":"string","enum":["en","zh","zh-TW","ja","ko"],"default":"en"},"description":"Content language preference"},{"name":"sort","in":"query","schema":{"type":"string","enum":["rating","reviewCount","createdAt"],"default":"rating"}},{"name":"city","in":"query","schema":{"type":"string"},"description":"Filter by city name (partial match)"},{"name":"language","in":"query","schema":{"type":"string"},"description":"Filter by language code"},{"name":"specialty","in":"query","schema":{"type":"string"},"description":"Filter by specialty tag"},{"name":"country","in":"query","schema":{"type":"string","enum":["CN","JP","KR","TW","HK"]}}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ExpertSummary"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/experts/{handle}":{"get":{"operationId":"getExpertByHandle","summary":"Get expert profile by handle","tags":["Public"],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}},{"name":"locale","in":"query","schema":{"type":"string","enum":["en","zh","zh-TW","ja","ko"],"default":"en"},"description":"Content language preference"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ExpertDetail"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/experts/{handle}/packages":{"get":{"operationId":"getExpertPackages","summary":"List an expert's packages","tags":["Public"],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1}},{"name":"pageSize","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"locale","in":"query","schema":{"type":"string","enum":["en","zh","zh-TW","ja","ko"],"default":"en"},"description":"Content language preference"}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PackageSummary"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/experts/{handle}/reviews":{"get":{"operationId":"getExpertReviews","summary":"List an expert's public reviews","tags":["Public"],"parameters":[{"name":"handle","in":"path","required":true,"schema":{"type":"string"}},{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1}},{"name":"pageSize","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"locale","in":"query","schema":{"type":"string","enum":["en","zh","zh-TW","ja","ko"],"default":"en"},"description":"Content language preference"},{"name":"sort","in":"query","schema":{"type":"string","enum":["createdAt","rating"],"default":"createdAt"}}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Review"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/packages":{"get":{"operationId":"listPackages","summary":"List active travel packages","tags":["Public"],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1}},{"name":"pageSize","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"locale","in":"query","schema":{"type":"string","enum":["en","zh","zh-TW","ja","ko"],"default":"en"},"description":"Content language preference"},{"name":"sort","in":"query","schema":{"type":"string","enum":["basePrice","duration","createdAt"],"default":"createdAt"}},{"name":"category","in":"query","schema":{"type":"string"},"description":"e.g. city_tour, food_tour, shopping"},{"name":"country","in":"query","schema":{"type":"string","enum":["CN","JP","KR","TW","HK"]}}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PackageSummary"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/packages/{id}":{"get":{"operationId":"getPackageById","summary":"Get package details","tags":["Public"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"locale","in":"query","schema":{"type":"string","enum":["en","zh","zh-TW","ja","ko"],"default":"en"},"description":"Content language preference"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/PackageDetail"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/travelogues":{"get":{"operationId":"listTravelogues","summary":"List published travelogues","tags":["Public"],"parameters":[{"name":"page","in":"query","schema":{"type":"integer","default":1,"minimum":1}},{"name":"pageSize","in":"query","schema":{"type":"integer","default":20,"minimum":1,"maximum":100}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"locale","in":"query","schema":{"type":"string","enum":["en","zh","zh-TW","ja","ko"],"default":"en"},"description":"Content language preference"},{"name":"sort","in":"query","schema":{"type":"string","enum":["hotScore","score","viewCount","createdAt","publishedAt"],"default":"hotScore"}},{"name":"category","in":"query","schema":{"type":"string"},"description":"e.g. food, adventure, culture, nature"}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TravelogueSummary"}},"meta":{"$ref":"#/components/schemas/PaginationMeta"}}}}}},"429":{"description":"Rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/travelogues/{slug}":{"get":{"operationId":"getTravelogueBySlug","summary":"Get travelogue by slug","tags":["Public"],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"locale","in":"query","schema":{"type":"string","enum":["en","zh","zh-TW","ja","ko"],"default":"en"},"description":"Content language preference"}],"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/TravelogueDetail"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/cities":{"get":{"operationId":"listCities","summary":"List cities with verified experts","tags":["Public"],"responses":{"200":{"description":"List of cities","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/City"}}}}}}}}}},"/api/v1/specialties":{"get":{"operationId":"listSpecialties","summary":"List available expert specialties","tags":["Public"],"responses":{"200":{"description":"List of specialty strings","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"string"}}}}}}}}}},"/api/v1/auth/login":{"post":{"operationId":"loginWithCredentials","summary":"Authenticate with email and password","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8}}}}}},"responses":{"200":{"description":"JWT token and user profile","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/register":{"post":{"operationId":"registerUser","summary":"Create a new user account","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email","password","name","role"],"properties":{"email":{"type":"string","format":"email"},"password":{"type":"string","minLength":8},"name":{"type":"string","minLength":2},"role":{"type":"string","enum":["TOURIST","EXPERT"]}}}}}},"responses":{"201":{"description":"Account created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"409":{"description":"Email already registered"}}}},"/api/v1/auth/google":{"post":{"operationId":"loginWithGoogle","summary":"Exchange Google OAuth ID token for JWT","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["idToken"],"properties":{"idToken":{"type":"string","description":"Google OAuth ID token"}}}}}},"responses":{"200":{"description":"JWT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/apple":{"post":{"operationId":"loginWithApple","summary":"Exchange Apple identity token for JWT","tags":["Auth"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["identityToken"],"properties":{"identityToken":{"type":"string"},"fullName":{"type":"object","nullable":true,"properties":{"givenName":{"type":"string","nullable":true},"familyName":{"type":"string","nullable":true}}}}}}}},"responses":{"200":{"description":"JWT token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/me":{"get":{"operationId":"getCurrentUser","summary":"Get authenticated user profile","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"User profile","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"$ref":"#/components/schemas/UserResponse"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/session-exchange":{"post":{"operationId":"exchangeSessionToken","summary":"Exchange mobile JWT for web session cookie","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Session cookie set","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/api-keys":{"post":{"operationId":"createApiKey","summary":"Create a new API key (requires JWT auth)","tags":["Auth"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","scopes"],"properties":{"name":{"type":"string","description":"Descriptive name for the key","example":"My Travel Agent Bot"},"scopes":{"type":"array","items":{"type":"string","enum":["experts:read","packages:read","travelogues:read","bookings:read","bookings:write","profile:read","profile:write"]},"description":"Permission scopes for this key"},"expiresInDays":{"type":"integer","minimum":1,"maximum":365,"description":"Key expiry in days (optional, no expiry if omitted)"},"rateLimit":{"type":"integer","minimum":1,"maximum":1000,"default":300,"description":"Max requests/min for this key"}}}}}},"responses":{"201":{"description":"API key created (key shown once)","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string","description":"Full API key — store securely, shown once"},"keyPrefix":{"type":"string","example":"pk_live_abc1"},"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}}}},"warning":{"type":"string"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"operationId":"listApiKeys","summary":"List your active API keys","tags":["Auth"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"List of API keys (hash not included)","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"keyPrefix":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"rateLimit":{"type":"integer"},"lastUsedAt":{"type":"string","format":"date-time","nullable":true},"expiresAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"}}}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/auth/api-keys/{id}":{"delete":{"operationId":"revokeApiKey","summary":"Revoke an API key","tags":["Auth"],"security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"API key revoked"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/push/register":{"post":{"operationId":"registerPushToken","summary":"Register FCM push notification token","tags":["Push"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string","description":"FCM device token"},"device":{"type":"string","description":"Device identifier (optional)"}}}}}},"responses":{"200":{"description":"Token registered"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/push/unregister":{"post":{"operationId":"unregisterPushToken","summary":"Remove FCM push notification token","tags":["Push"],"security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token"],"properties":{"token":{"type":"string"}}}}}},"responses":{"200":{"description":"Token removed"},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/agora/token":{"get":{"operationId":"getAgoraToken","summary":"Get Agora Chat SDK credentials","tags":["Messaging"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Agora credentials","content":{"application/json":{"schema":{"type":"object","properties":{"appId":{"type":"string"},"userId":{"type":"string"},"token":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/expert/availability":{"get":{"operationId":"getExpertAvailability","summary":"Get current expert's availability schedule","tags":["Expert"],"security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Availability settings","content":{"application/json":{"schema":{"type":"object","properties":{"expertId":{"type":"string"},"weeklySchedule":{"type":"object"},"timezone":{"type":"string"},"blockedDates":{"type":"array","items":{"type":"object"}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/actions/{id}":{"get":{"operationId":"getActionStatus","summary":"Check the status of a pending action","description":"Returns the current status (pending, confirmed, expired) of a high-risk action that was flagged for confirmation.","tags":["Actions"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Pending action ID"}],"responses":{"200":{"description":"Action status","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"action":{"type":"string","example":"booking.cancel"},"status":{"type":"string","enum":["pending","confirmed","expired"]},"expiresAt":{"type":"string","format":"date-time"},"confirmedAt":{"type":"string","format":"date-time","nullable":true},"createdAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/actions/{id}/confirm":{"post":{"operationId":"confirmAction","summary":"Confirm a pending high-risk action","description":"Confirms a pending action created during a high-risk API key operation. Only the original actor can confirm. Returns the action payload for the client to proceed with execution.","tags":["Actions"],"security":[{"bearerAuth":[]},{"apiKeyAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"},"description":"Pending action ID"}],"responses":{"200":{"description":"Action confirmed","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"actionId":{"type":"string","format":"uuid"},"action":{"type":"string","example":"booking.cancel"},"status":{"type":"string","enum":["confirmed"]},"payload":{"type":"object","description":"Original request payload to re-execute"}}}}}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Action expired or already confirmed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","example":"NOT_FOUND"},"message":{"type":"string","example":"Resource not found"},"details":{"type":"object"},"doc_url":{"type":"string","format":"uri"},"is_retriable":{"type":"boolean"},"retry_after":{"type":"integer"},"alternative_action":{"type":"string"}},"required":["code","message","doc_url","is_retriable"]},"requestId":{"type":"string"}}},"PaginationMeta":{"type":"object","properties":{"page":{"type":"integer","example":1},"pageSize":{"type":"integer","example":20},"total":{"type":"integer","example":150},"hasMore":{"type":"boolean","example":true}},"required":["page","pageSize","total","hasMore"]},"UserResponse":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string","format":"email"},"name":{"type":"string","nullable":true},"image":{"type":"string","nullable":true},"nickname":{"type":"string","nullable":true},"avatar":{"type":"string","nullable":true},"role":{"type":"string","enum":["TOURIST","EXPERT","ADMIN"]}}},"TokenResponse":{"type":"object","properties":{"token":{"type":"string","description":"JWT bearer token (30-day expiry, HS256)"},"user":{"$ref":"#/components/schemas/UserResponse"}},"required":["token","user"]},"ExpertSummary":{"type":"object","properties":{"handle":{"type":"string","example":"sarah-beijing"},"name":{"type":"string","nullable":true},"avatar":{"type":"string","nullable":true},"bio":{"type":"string","nullable":true},"languages":{"type":"array","items":{"type":"string"}},"specialties":{"type":"array","items":{"type":"string"}},"location":{"type":"string","nullable":true},"countryCode":{"type":"string","enum":["CN","JP","KR","TW","HK"]},"currency":{"type":"string"},"profileImage":{"type":"string","nullable":true},"hourlyRate":{"type":"number","nullable":true},"rating":{"type":"number","example":4.8},"reviewCount":{"type":"integer"}}},"ExpertDetail":{"allOf":[{"$ref":"#/components/schemas/ExpertSummary"},{"type":"object","properties":{"coverImage":{"type":"string","nullable":true},"socialLinks":{"type":"object"},"messagingEnabled":{"type":"boolean"},"weeklySchedule":{"type":"object"},"timezone":{"type":"string","example":"Asia/Shanghai"},"verifiedAt":{"type":"string","format":"date-time","nullable":true}}}]},"PackageSummary":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"description":{"type":"string"},"category":{"type":"string"},"countryCode":{"type":"string","enum":["CN","JP","KR","TW","HK"]},"basePrice":{"type":"number"},"currency":{"type":"string"},"duration":{"type":"integer","description":"Duration in hours"},"maxParticipants":{"type":"integer"},"images":{"type":"array","items":{"type":"string"}},"highlights":{"type":"array","items":{"type":"string"}},"included":{"type":"array","items":{"type":"string"}},"meetingPoint":{"type":"string","nullable":true}}},"PackageDetail":{"allOf":[{"$ref":"#/components/schemas/PackageSummary"},{"type":"object","properties":{"excluded":{"type":"array","items":{"type":"string"}}}}]},"TravelogueSummary":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"excerpt":{"type":"string","nullable":true},"coverImage":{"type":"string","nullable":true},"category":{"type":"string"},"locationTags":{"type":"array","items":{"type":"string"}},"score":{"type":"integer"},"viewCount":{"type":"integer"},"commentCount":{"type":"integer"},"publishedAt":{"type":"string","format":"date-time","nullable":true},"author":{"type":"object","properties":{"handle":{"type":"string"},"name":{"type":"string","nullable":true},"avatar":{"type":"string","nullable":true}}}}},"TravelogueDetail":{"allOf":[{"$ref":"#/components/schemas/TravelogueSummary"},{"type":"object","properties":{"content":{"type":"string","description":"Rich text HTML content"},"images":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}}]},"Review":{"type":"object","properties":{"id":{"type":"string"},"rating":{"type":"integer","minimum":1,"maximum":5},"comment":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"author":{"type":"object","properties":{"name":{"type":"string","nullable":true},"avatar":{"type":"string","nullable":true}}}}},"City":{"type":"object","properties":{"name":{"type":"string"},"countryCode":{"type":"string","enum":["CN","JP","KR","TW","HK"]}}}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"JWT token obtained from /api/v1/auth/login, /register, /google, or /apple. Valid for 30 days."},"apiKeyAuth":{"type":"apiKey","in":"header","name":"Authorization","description":"API key with \"Bearer pk_live_...\" or \"Bearer pk_test_...\" prefix. Create via POST /api/v1/auth/api-keys."}}}}