openapi: 3.0.3 info: title: YuXinGu API version: 0.2.0 description: | 愈心谷 P1 API 契约(冻结草案)。 用户可见文案遵循 .ai/product/lexicon.md。 统一信封:{ code, message, data };code=0 成功。 用户自由文字不合规(昵称/档案名/问答/备注等):HTTP 400 · code 40060 · message「文案不合规:…」 (见 feature-spec/input-compliance;后端 textsafe 权威校验)。 servers: - url: http://127.0.0.1:8080 tags: - name: system - name: profile - name: portrait - name: scale - name: relation - name: report - name: ask - name: companion - name: commerce - name: admin paths: /api/v1/healthz: get: tags: [system] summary: Liveness responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/Envelope' /api/v1/ping: get: tags: [system] summary: Ping responses: '200': description: OK /api/v1/auth/me: get: tags: [system] summary: Current account responses: '200': description: OK patch: tags: [system] summary: Update account nickname requestBody: required: true content: application/json: schema: type: object required: [nickname] properties: nickname: { type: string, maxLength: 16 } responses: '200': description: OK /api/v1/auth/me/avatar: post: tags: [system] summary: Upload account avatar (multipart file) description: jpeg/png/webp · max 2MB · field name `file` · returns AuthUser with avatar_url requestBody: required: true content: multipart/form-data: schema: type: object required: [file] properties: file: { type: string, format: binary } responses: '200': description: OK · AuthUser with avatar_url '400': description: bad type / too large / empty (code 40114) '401': description: unauthorized /api/v1/media/avatars/{file}: get: tags: [system] summary: Serve account avatar image description: Public read; `{uuid}.{jpg|jpeg|png|webp}` only; path traversal rejected parameters: - name: file in: path required: true schema: { type: string } responses: '200': description: image bytes '404': description: missing or invalid name /api/v1/admin/auth/login: post: tags: [admin] summary: Admin login requestBody: required: true content: application/json: schema: type: object required: [username, password] properties: username: { type: string } password: { type: string } responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/Envelope' /api/v1/admin/auth/logout: post: tags: [admin] summary: Admin logout responses: '200': description: OK /api/v1/admin/me: get: tags: [admin] summary: Current admin (role + permissions[]) responses: '200': description: OK (id, username, role, permissions) content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: $ref: '#/components/schemas/AdminMe' /api/v1/admin/roles: get: tags: [admin] summary: List admin roles description: Requires admin.roles.read responses: '200': description: OK '403': description: Forbidden /api/v1/admin/roles/{id}: get: tags: [admin] summary: Role detail with permissions description: Requires admin.roles.read parameters: - $ref: '#/components/parameters/Id' responses: '200': description: OK '403': description: Forbidden '404': description: Not found /api/v1/admin/roles/{id}/permissions: put: tags: [admin] summary: Replace role permission set description: Requires admin.roles.write; writes AuditLog parameters: - $ref: '#/components/parameters/Id' requestBody: required: true content: application/json: schema: type: object required: [permissions] properties: permissions: type: array items: { type: string } responses: '200': description: OK '400': description: Invalid permission code '403': description: Forbidden '404': description: Not found /api/v1/admin/users: get: tags: [admin] summary: List users parameters: - in: query name: q schema: { type: string } - in: query name: limit schema: { type: integer } - in: query name: offset schema: { type: integer } responses: '200': description: OK /api/v1/admin/users/{id}: get: tags: [admin] summary: User detail parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK /api/v1/admin/users/{id}/membership/grant: post: tags: [admin] summary: Grant or extend membership parameters: - in: path name: id required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: type: object required: [plan] properties: plan: { type: string, enum: [month, quarter, year] } responses: '200': description: OK '403': description: Forbidden (ops role) /api/v1/admin/users/{id}/ban: post: tags: [admin] summary: Ban user (Ops-E) parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK /api/v1/admin/users/{id}/unban: post: tags: [admin] summary: Unban user (Ops-E) parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK /api/v1/admin/admins: get: tags: [admin] summary: List admin accounts (super only) responses: '200': description: OK '403': description: Forbidden /api/v1/admin/admins/{id}: patch: tags: [admin] summary: Update admin role (super only) parameters: - in: path name: id required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: type: object required: [role] properties: role: { type: string, enum: [super, ops] } responses: '200': description: OK '409': description: Cannot demote last super /api/v1/admin/push-jobs: get: tags: [admin] summary: List push job stubs (Ops-E) responses: '200': description: OK post: tags: [admin] summary: Create push draft (no dispatch) requestBody: required: true content: application/json: schema: type: object required: [title] properties: title: { type: string } body: { type: string } audience: { type: string } responses: '200': description: OK /api/v1/admin/push-jobs/{id}: patch: tags: [admin] summary: Update push stub (draft or cancelled) parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK /api/v1/admin/users/{id}/status: post: tags: [admin] summary: Transition UserStatus description: Requires admin.users.status.write; writes transition + AuditLog parameters: - in: path name: id required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: type: object required: [status, reason] properties: status: { type: string, enum: [active, disabled, banned, suspended] } reason: { type: string } responses: '200': description: OK '400': description: Invalid edge or missing reason '403': description: Forbidden /api/v1/admin/users/{id}/status-transitions: get: tags: [admin] summary: List UserStatus transitions description: Requires admin.users.read parameters: - in: path name: id required: true schema: { type: string, format: uuid } - in: query name: limit schema: { type: integer } responses: '200': description: OK '401': description: Unauthorized /api/v1/admin/users/{id}/insight: get: tags: [admin] summary: UserIntelligence insight (read-only aggregate) description: Requires admin.users.read; no schema write parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden '404': description: User not found /api/v1/admin/users/{id}/entitlements: get: tags: [admin] summary: User Entitlement aggregate (read-only) description: Requires admin.users.read; Membership ∪ DeepAccess ∪ ask quotas parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '401': description: Unauthorized '404': description: User not found /api/v1/admin/membership-plans: get: tags: [admin] summary: List membership plans description: Requires admin.membership.plans.read responses: '200': description: OK '403': description: Forbidden /api/v1/admin/membership-plans/{code}: get: tags: [admin] summary: Get membership plan parameters: - in: path name: code required: true schema: { type: string } responses: '200': description: OK '404': description: Not found put: tags: [admin] summary: Update membership plan description: Requires admin.membership.plans.write parameters: - in: path name: code required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [title, duration_days, amount_cents] properties: title: { type: string } duration_days: { type: integer } amount_cents: { type: integer } active: { type: boolean } responses: '200': description: OK '400': description: Invalid '403': description: Forbidden /api/v1/admin/redemption-batches: get: tags: [admin] summary: List redemption batches responses: { '200': { description: OK } } post: tags: [admin] summary: Create redemption batch requestBody: required: true content: application/json: schema: type: object required: [label, plan_code, quantity] properties: label: { type: string } plan_code: { type: string } quantity: { type: integer, minimum: 1, maximum: 100 } responses: { '200': { description: OK } } /api/v1/admin/redemption-batches/{id}/codes: get: tags: [admin] summary: List codes in batch parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: { '200': { description: OK } } /api/v1/admin/redemption-codes/{id}/disable: post: tags: [admin] summary: Disable unused code parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: { '200': { description: OK } } /api/v1/admin/ask/threads: get: tags: [admin] summary: List AskSessionView (read-only) description: Requires admin.ask.read parameters: - in: query name: user_id schema: { type: string, format: uuid } - in: query name: limit schema: { type: integer } - in: query name: offset schema: { type: integer } responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/ask/threads/{id}: get: tags: [admin] summary: Ask session meta (no message bodies) description: Requires admin.ask.read; transcript via /messages parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/ask/threads/{id}/messages: get: tags: [admin] summary: Ask session transcript (audited) description: Requires admin.ask.transcript.read; writes ask.transcript.read audit parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden '404': description: Not found /api/v1/admin/content-safety/filter-rules: get: tags: [admin] summary: List FilterRule description: Requires admin.content_safety.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/content-safety/filter-rules/{id}: get: tags: [admin] summary: Get FilterRule parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/content-safety/evaluate: post: tags: [admin] summary: Preview FilterRule matches (no ModerationCase write) requestBody: required: true content: application/json: schema: type: object required: [text] properties: text: { type: string } responses: '200': description: OK /api/v1/admin/ask/feedback: get: tags: [admin] summary: List QualityFeedback description: Requires admin.ask.read responses: '200': description: OK '401': description: Unauthorized /api/v1/admin/ask/threads/{id}/feedback: post: tags: [admin] summary: Create QualityFeedback (ops) description: Requires admin.ask.feedback.write; writes AuditLog parameters: - in: path name: id required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: type: object required: [rating] properties: rating: { type: integer, minimum: 1, maximum: 5 } tag: { type: string, enum: [helpful, off_topic, unsafe, other] } note: { type: string } message_id: { type: string, format: uuid } responses: '200': description: OK '400': description: Invalid '403': description: Forbidden /api/v1/ask/threads/{id}/feedback: post: tags: [ask] summary: User QualityFeedback on owned thread parameters: - in: path name: id required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: type: object required: [rating] properties: rating: { type: integer, minimum: 1, maximum: 5 } tag: { type: string } note: { type: string } message_id: { type: string, format: uuid } responses: '200': description: OK /api/v1/admin/ai/system-prompts: get: tags: [admin] summary: List SystemPrompt catalog description: Requires admin.ai_config.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/ai/system-prompts/{id}: get: tags: [admin] summary: Get SystemPrompt parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/ai/knowledge-sources: get: tags: [admin] summary: List KnowledgeSource catalog description: Requires admin.ai_config.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/ai/knowledge-sources/{id}: get: tags: [admin] summary: Get KnowledgeSource parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/cms/banners: get: tags: [admin] summary: List OpsCMS Banner catalog description: Requires admin.cms.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden post: tags: [admin] summary: Create Banner description: Requires admin.cms.write · ECR-041 responses: '200': description: OK '400': description: Invalid '401': description: Unauthorized '403': description: Forbidden '409': description: Code conflict /api/v1/admin/cms/banners/{id}: get: tags: [admin] summary: Get Banner parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found put: tags: [admin] summary: Update Banner description: Requires admin.cms.write · deactivate via active=false parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '400': description: Invalid '403': description: Forbidden '404': description: Not found '409': description: Code conflict /api/v1/admin/cms/feed-slots: get: tags: [admin] summary: List OpsCMS FeedSlot catalog description: Requires admin.cms.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden post: tags: [admin] summary: Create FeedSlot description: Requires admin.cms.write · ECR-042 responses: '200': description: OK '400': description: Invalid '403': description: Forbidden '409': description: Conflict /api/v1/admin/cms/feed-slots/{id}: get: tags: [admin] summary: Get FeedSlot parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found put: tags: [admin] summary: Update FeedSlot description: Requires admin.cms.write · deactivate via active=false parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '400': description: Invalid '403': description: Forbidden '404': description: Not found '409': description: Conflict /api/v1/admin/cms/publications: get: tags: [admin] summary: List ScheduledPublication catalog description: Requires admin.cms.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/cms/publications/{id}: get: tags: [admin] summary: Get ScheduledPublication parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/ai/knowledge-chunks: get: tags: [admin] summary: List KnowledgeChunk catalog description: Requires admin.ai_config.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/ai/knowledge-chunks/{id}: get: tags: [admin] summary: Get KnowledgeChunk parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/ai/tools: get: tags: [admin] summary: List ToolDefinition catalog description: Requires admin.ai_config.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/ai/tools/{id}: get: tags: [admin] summary: Get ToolDefinition parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/content-safety/block-policies: get: tags: [admin] summary: List BlockPolicy catalog description: Requires admin.content_safety.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/content-safety/block-policies/{id}: get: tags: [admin] summary: Get BlockPolicy parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/content-safety/cases: get: tags: [admin] summary: List ModerationCase catalog description: Requires admin.content_safety.read; TYPE CATALOG only — not live moderation tickets responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/content-safety/cases/{id}: get: tags: [admin] summary: Get ModerationCase parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/crisis/events: get: tags: [admin] summary: List CrisisEvent catalog description: Requires admin.crisis.read; TYPE CATALOG only — not user crisis cases responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/crisis/events/{id}: get: tags: [admin] summary: Get CrisisEvent parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/crisis/interventions: get: tags: [admin] summary: List InterventionOutcome catalog description: Requires admin.crisis.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/crisis/interventions/{id}: get: tags: [admin] summary: Get InterventionOutcome parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/ask/handoffs: get: tags: [admin] summary: List HandoffCase catalog description: Requires admin.ask.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/ask/handoffs/{id}: get: tags: [admin] summary: Get HandoffCase parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/privacy/requests: get: tags: [admin] summary: List PrivacyRequest catalog description: Requires admin.privacy.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/privacy/requests/{id}: get: tags: [admin] summary: Get PrivacyRequest parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/explore/scales: get: tags: [admin] summary: List ScaleDefinition read projection description: Requires admin.explore.read · reuses scales table responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/explore/scales/{id}: get: tags: [admin] summary: Get ScaleDefinition parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/explore/star-configs: get: tags: [admin] summary: List StarConfig catalog description: Requires admin.explore.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden post: tags: [admin] summary: Create StarConfig description: Requires admin.explore.write · ECR-043 responses: '200': description: OK '400': description: Invalid '401': description: Unauthorized '403': description: Forbidden '409': description: Code conflict /api/v1/admin/explore/star-configs/{id}: get: tags: [admin] summary: Get StarConfig parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found put: tags: [admin] summary: Update StarConfig description: Requires admin.explore.write · deactivate via active=false · ECR-043 parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '400': description: Invalid '403': description: Forbidden '404': description: Not found '409': description: Code conflict /api/v1/admin/explore/rhythm-configs: get: tags: [admin] summary: List RhythmConfig catalog description: Requires admin.explore.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden post: tags: [admin] summary: Create RhythmConfig description: Requires admin.explore.write · ECR-044 responses: '200': description: OK '400': description: Invalid '401': description: Unauthorized '403': description: Forbidden '409': description: Code conflict /api/v1/admin/explore/rhythm-configs/{id}: get: tags: [admin] summary: Get RhythmConfig parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found put: tags: [admin] summary: Update RhythmConfig description: Requires admin.explore.write · deactivate via active=false · ECR-044 parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '400': description: Invalid '403': description: Forbidden '404': description: Not found '409': description: Code conflict /api/v1/admin/explore/image-card-decks: get: tags: [admin] summary: List ImageCardDeck catalog description: Requires admin.explore.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/explore/image-card-decks/{id}: get: tags: [admin] summary: Get ImageCardDeck parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/growth/report-templates: get: tags: [admin] summary: List ReportTemplate catalog description: Requires admin.growth.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/growth/report-templates/{id}: get: tags: [admin] summary: Get ReportTemplate parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/analytics/funnel-definitions: get: tags: [admin] summary: List FunnelDefinition catalog description: Requires admin.growth.read; catalog only responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/analytics/funnel-definitions/{id}: get: tags: [admin] summary: Get FunnelDefinition parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/crisis/policies: get: tags: [admin] summary: List CrisisPolicy description: Requires admin.crisis.read responses: '200': description: OK '401': description: Unauthorized '403': description: Forbidden /api/v1/admin/crisis/policies/{id}: get: tags: [admin] summary: Get CrisisPolicy parameters: - in: path name: id required: true schema: { type: string, format: uuid } responses: '200': description: OK '404': description: Not found /api/v1/admin/crisis/evaluate: post: tags: [admin] summary: Preview CrisisPolicy matches (no CrisisEvent write) requestBody: required: true content: application/json: schema: type: object required: [text] properties: text: { type: string } responses: '200': description: OK /api/v1/admin/orders: get: tags: [admin] summary: List orders (Ops-D filters) parameters: - in: query name: status schema: { type: string } - in: query name: kind schema: { type: string } - in: query name: from schema: { type: string, format: date } - in: query name: to schema: { type: string, format: date } - in: query name: user_id schema: { type: string, format: uuid } responses: '200': description: OK (includes refund_status) /api/v1/admin/membership/plan-prices: get: tags: [admin] summary: List membership display prices responses: '200': description: OK put: tags: [admin] summary: Upsert membership display prices responses: '200': description: OK /api/v1/admin/audit-logs: get: tags: [admin] summary: List audit logs responses: '200': description: OK /api/v1/admin/analytics/overview: get: tags: [admin] summary: Behavior analytics overview (DAU / sessions) parameters: - in: query name: from schema: { type: string, format: date } - in: query name: to schema: { type: string, format: date } responses: '200': description: OK /api/v1/admin/analytics/pages: get: tags: [admin] summary: Page PV/UV/dwell/exit aggregates parameters: - in: query name: from schema: { type: string, format: date } - in: query name: to schema: { type: string, format: date } responses: '200': description: OK /api/v1/admin/analytics/exits: get: tags: [admin] summary: Exit page ranking parameters: - in: query name: from schema: { type: string, format: date } - in: query name: to schema: { type: string, format: date } responses: '200': description: OK /api/v1/admin/analytics/clicks: get: tags: [admin] summary: UI click ranking parameters: - in: query name: from schema: { type: string, format: date } - in: query name: to schema: { type: string, format: date } responses: '200': description: OK /api/v1/admin/analytics/funnel: get: tags: [admin] summary: Core funnel step counts parameters: - in: query name: from schema: { type: string, format: date } - in: query name: to schema: { type: string, format: date } responses: '200': description: OK /api/v1/analytics/events: post: tags: [system] summary: Batch ingest behavior events (DeviceAuth) requestBody: required: true content: application/json: schema: type: object required: [items] properties: items: type: array maxItems: 100 items: type: object required: [name, session_id, client_ts] properties: name: { type: string } session_id: { type: string } page_path: { type: string } client_ts: {} props: { type: object } responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/Envelope' /api/v1/home/tools: get: tags: [system] summary: Homepage grid tools (enabled only) responses: '200': description: OK /api/v1/home/banners: get: tags: [system] summary: Homepage active banners (ECR-041) parameters: - in: query name: placement schema: { type: string, default: home, enum: [home, explore, ask] } responses: '200': description: OK /api/v1/home/feed-slots: get: tags: [system] summary: Homepage active feed slots (ECR-042) parameters: - in: query name: placement schema: { type: string, default: home, enum: [home, explore, ask] } responses: '200': description: OK /api/v1/star/configs: get: tags: [system] summary: Active StarConfig list for C-end (ECR-043) description: DeviceAuth · only active=true · ordered by code responses: '200': description: OK /api/v1/rhythm/configs: get: tags: [system] summary: Active RhythmConfig list for C-end (ECR-044) description: DeviceAuth · only active=true · ordered by code responses: '200': description: OK /api/v1/home/daily-tips: get: tags: [system] summary: Homepage self-card tips by 时辰 (clothing / colors / wellness) description: > Cached per user × Chinese double-hour (时辰). Returns immediately from cache/fallback; LLM warm runs in background. Includes shichen, shichen_name, valid_until for client refresh. responses: '200': description: OK · tips + valid_until content: application/json: schema: $ref: '#/components/schemas/EnvelopeHomeDailyTips' /api/v1/admin/home/tools: get: tags: [admin] summary: List all homepage tools responses: '200': description: OK put: tags: [admin] summary: Replace homepage tools requestBody: required: true content: application/json: schema: type: object required: [items] properties: items: { type: array } responses: '200': description: OK /api/v1/admin/scales: get: tags: [admin] summary: List all scales (incl. draft) responses: '200': description: OK /api/v1/admin/scales/{id}: patch: tags: [admin] summary: Set scale status published|draft parameters: - in: path name: id required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: type: object required: [status] properties: status: { type: string, enum: [published, draft] } responses: '200': description: OK /api/v1/profiles: get: tags: [profile] summary: 列出个人档案 responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/EnvelopeProfileList' post: tags: [profile] summary: 创建个人档案 requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateProfileRequest' responses: '200': description: OK content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: { $ref: '#/components/schemas/Profile' } '409': description: Second self profile (code 40902) patch: tags: [profile] summary: 更新个人档案 parameters: - $ref: '#/components/parameters/Id' responses: '200': description: OK content: application/json: schema: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: { $ref: '#/components/schemas/Profile' } delete: tags: [profile] summary: 删除个人档案 parameters: - $ref: '#/components/parameters/Id' responses: '200': description: OK /api/v1/reports/portrait: post: tags: [portrait] summary: 生成个人画像成长报告 requestBody: required: true content: application/json: schema: type: object required: [profile_id] properties: profile_id: { type: string, format: uuid } responses: '200': description: 返回 GrowthReport(detail 按权益裁剪) content: application/json: schema: $ref: '#/components/schemas/EnvelopeGrowthReport' /api/v1/explore/catalog: get: tags: [explore] summary: 探索三级目录(L1 分类 → L3 工具) responses: '200': description: categories[] /api/v1/explore/catalog/{key}: get: tags: [explore] summary: 单个探索分类 parameters: - name: key in: path required: true schema: { type: string } responses: '200': description: Category '404': description: not found /api/v1/growth/plans: get: tags: [growth] summary: 成长计划列表 responses: '200': description: OK post: tags: [growth] summary: 创建成长计划 responses: '200': description: OK /api/v1/growth/plans/{id}/checkin: post: tags: [growth] summary: 今日打卡 parameters: - $ref: '#/components/parameters/Id' responses: '200': description: OK /api/v1/growth/plans/{id}/checkins: get: tags: [growth] summary: 打卡记录 parameters: - $ref: '#/components/parameters/Id' responses: '200': description: OK /api/v1/moods/recent: get: tags: [companion] summary: 近七日心情轨迹 responses: '200': description: OK /api/v1/reports/star: post: tags: [star] summary: 生成本命排盘 / 运势成长报告 requestBody: required: true content: application/json: schema: type: object required: [profile_id] properties: profile_id: { type: string, format: uuid } responses: '200': description: GrowthReport type=star(含 chart/planets/aspects_preview/outlook/transits;summary.as_of/valid_until 按自然日;无 fortune/lucky 键;detail 按权益裁剪) content: application/json: schema: $ref: '#/components/schemas/EnvelopeGrowthReport' /api/v1/reports/synastry: post: tags: [star] summary: 生成双人合盘报告(五主盘 + 次限推运) requestBody: required: true content: application/json: schema: type: object required: [profile_id_a, profile_id_b] properties: profile_id_a: { type: string, format: uuid } profile_id_b: { type: string, format: uuid } as_of: { type: string, format: date, description: 次限推运日期 YYYY-MM-DD } responses: '200': description: GrowthReport type=synastry(三指数 + charts 五主盘/推运) /api/v1/synastry/nearby: get: tags: [star] summary: 附近可合盘档案 parameters: - in: query name: lat required: true schema: { type: number } - in: query name: lng required: true schema: { type: number } - in: query name: radius_km schema: { type: number, default: 50 } responses: '200': description: items[{profile, distance_km}] /api/v1/synastry/invites: post: tags: [star] summary: 创建合盘邀请 requestBody: required: true content: application/json: schema: type: object required: [profile_id] properties: profile_id: { type: string, format: uuid } responses: '200': description: token / path / expires_at /api/v1/synastry/invites/{token}: get: tags: [star] summary: 邀请元数据 parameters: - in: path name: token required: true schema: { type: string } responses: '200': description: host_name / expires_at / already_accepted /api/v1/synastry/invites/{token}/accept: post: tags: [star] summary: 接受合盘邀请并生成报告 parameters: - in: path name: token required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [birth_date] properties: display_name: { type: string } birth_date: { type: string, format: date } birth_time: { type: string } birth_place: { type: string } responses: '200': description: GrowthReport type=synastry content: application/json: schema: $ref: '#/components/schemas/EnvelopeGrowthReport' /api/v1/reports/rhythm: post: tags: [rhythm] summary: 生成身心节律成长报告 requestBody: required: true content: application/json: schema: type: object required: [profile_id] properties: profile_id: { type: string, format: uuid } responses: '200': description: GrowthReport type=rhythm(summary.as_of/valid_until 按自然日;GET 换日原地刷新今日/本周建议) content: application/json: schema: $ref: '#/components/schemas/EnvelopeGrowthReport' /api/v1/image-cards/scenes: get: tags: [image-card] summary: 意象卡片场景列表 responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/EnvelopeImageCardScenes' /api/v1/image-cards/quota: get: tags: [image-card] summary: 今日抽卡剩余次数 responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/EnvelopeImageCardQuota' /api/v1/image-cards/draw: post: tags: [image-card] summary: 抽意象卡片(沉淀为 GrowthReport type=image_card) requestBody: required: true content: application/json: schema: type: object required: [profile_id] properties: profile_id: { type: string, format: uuid } scene: { type: string } depth: { type: boolean, description: 请求三卡组合(无权益则 detail 剥离) } responses: '200': description: report + cards + quota_left '402': description: 免费次数用尽 /api/v1/relation/insight: post: tags: [relation] summary: 生成关系理解 requestBody: required: true content: application/json: schema: type: object required: [profile_a_id, profile_b_id] properties: profile_a_id: { type: string, format: uuid } profile_b_id: { type: string, format: uuid } responses: '200': description: RelationInsight + 可选 report_id /api/v1/reports: get: tags: [report] summary: 列出当前用户成长报告 responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/EnvelopeReportList' /api/v1/reports/{id}: get: tags: [report] summary: 获取成长报告(权益裁剪) parameters: - $ref: '#/components/parameters/Id' responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/EnvelopeGrowthReport' /api/v1/scale-bank/catalog: get: tags: [scale] summary: curated 题库精选宫格 + 分类卡片(A 集非临床) responses: '200': description: OK content: application/json: schema: type: object properties: code: { type: integer } message: { type: string } data: type: object properties: featured: type: array items: type: object properties: slug: { type: string } title: { type: string } description: { type: string } category_key: { type: string } icon: { type: string } path: { type: string } categories: type: array items: type: object properties: key: { type: string } title: { type: string } description: { type: string } icon: { type: string } count: { type: integer } path: { type: string } /api/v1/scale-bank/categories/{key}: get: tags: [scale] summary: curated 题库某一分类下的量表列表 parameters: - name: key in: path required: true schema: { type: string } responses: '200': description: OK '404': description: category not found /api/v1/scales: get: tags: [scale] summary: 探索测试列表 responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/EnvelopeScaleList' /api/v1/scales/{slug}: get: tags: [scale] summary: 探索测试题目 description: > `mbti-full` 未开通成长会员时返回 access=membership · locked=true 且 questions 为空。 curated scalebank slug 从 embed 返回题目。 parameters: - name: slug in: path required: true schema: { type: string } responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/EnvelopeScaleDetail' /api/v1/scales/{slug}/result: get: tags: [scale] summary: 当前用户该量表最近一次结果 parameters: - name: slug in: path required: true schema: { type: string } responses: '200': description: ScaleSubmitResult '404': description: 尚无结果 post: tags: [scale] summary: 提交探索测试并计分 parameters: - name: slug in: path required: true schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [profile_id, answers] properties: profile_id: { type: string, format: uuid } answers: { type: object } responses: '200': description: 探索结果 '403': description: mbti-full 需要成长会员(code 40310) get: tags: [ask] summary: 当前问答剩余次数(免费额度或成长会员) responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/EnvelopeAskQuota' /api/v1/ask/threads: post: tags: [ask] summary: 创建问答线程(须绑定 profile) requestBody: required: true content: application/json: schema: type: object required: [profile_id] properties: profile_id: { type: string, format: uuid } scene: { type: string, description: self|relation|career|emotion|life } responses: '200': description: OK /api/v1/ask/threads/{id}/messages: get: tags: [ask] summary: 拉取线程消息历史 parameters: - $ref: '#/components/parameters/Id' responses: '200': description: OK post: tags: [ask] summary: 发送消息(扣额度;P1 规则引擎回复) parameters: - $ref: '#/components/parameters/Id' requestBody: required: true content: application/json: schema: type: object required: [content] properties: content: { type: string } responses: '200': description: user + assistant + quota '402': description: 额度用尽 /api/v1/solar-terms/today: get: tags: [companion] summary: 今日节气生活建议 responses: '200': description: OK /api/v1/moods: post: tags: [companion] summary: 记录/更新当日心情 requestBody: required: true content: application/json: schema: type: object properties: score: { type: integer, minimum: 1, maximum: 5 } note: { type: string, maxLength: 200 } day: { type: string, format: date } responses: '200': description: Mood /api/v1/moods/today: get: tags: [companion] summary: 今日心情 responses: '200': description: '{ mood: Mood | null }' /api/v1/membership/me: get: tags: [commerce] summary: 当前成长会员权益 responses: '200': description: OK content: application/json: schema: $ref: '#/components/schemas/EnvelopeMembershipMe' /api/v1/membership/redeem: post: tags: [commerce] summary: 兑换会员码 requestBody: required: true content: application/json: schema: type: object required: [code] properties: code: { type: string } responses: '200': description: OK '400': description: Invalid or used code '401': description: Unauthorized /api/v1/orders: post: tags: [commerce] summary: 创建订单(membership | deep_access | ask_pack) requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateOrderRequest' responses: '200': description: OK /api/v1/orders/{id}/pay-mock: post: tags: [commerce] summary: 模拟支付成功 parameters: - $ref: '#/components/parameters/Id' responses: '200': description: OK components: parameters: Id: name: id in: path required: true schema: { type: string, format: uuid } schemas: Envelope: type: object required: [code, message] properties: code: { type: integer, example: 0 } message: { type: string, example: success } data: { type: object } AdminMe: type: object required: [id, username, permissions] properties: id: { type: string, format: uuid } username: { type: string } role: { type: string } permissions: type: array items: { type: string } CreateProfileRequest: type: object required: [relation, birth_date] properties: relation: { type: string, enum: [self, other] } display_name: { type: string } birth_date: { type: string, format: date } birth_time: { type: string, nullable: true } birth_place: { type: string, nullable: true } gender: { type: string, nullable: true } relation_type: { type: string, nullable: true } CreateOrderRequest: type: object required: [kind] properties: kind: { type: string, enum: [membership, deep_access, ask_pack] } plan: { type: string, description: month|quarter|year when membership; pack10|pack30|pack100 when ask_pack } report_id: { type: string, format: uuid, description: when deep_access } # --- Aligned with packages/types (ECR-004) --- Profile: type: object required: [id, user_id, relation, display_name, birth_date, created_at] properties: id: { type: string, format: uuid } user_id: { type: string, format: uuid } relation: { type: string, enum: [self, other] } display_name: { type: string } birth_date: { type: string, format: date } birth_time: { type: string, nullable: true } birth_place: { type: string, nullable: true } relation_type: { type: string, nullable: true } geo_lat: { type: number, nullable: true } geo_lng: { type: number, nullable: true } geo_visible: { type: boolean } created_at: { type: string, format: date-time } GrowthReport: type: object required: [id, user_id, profile_id, type, summary, has_deep_access, created_at] properties: id: { type: string, format: uuid } user_id: { type: string, format: uuid } profile_id: { type: string, format: uuid } type: { type: string } summary: type: object additionalProperties: true description: > For type=star|rhythm include as_of (YYYY-MM-DD CST) and valid_until (next midnight CST RFC3339). GET latest/id refreshes day-scoped outlook in place when as_of is stale; report id unchanged. detail: { type: object, nullable: true, additionalProperties: true } has_deep_access: { type: boolean } created_at: { type: string, format: date-time } HomeDailyTips: type: object required: [clothing_index, clothing, color_note, palette, wellness, source, shichen, shichen_name, valid_until] properties: clothing_index: { type: integer } clothing: { type: string } color_note: { type: string } palette: type: array items: type: object properties: name: { type: string } hex: { type: string } wellness: { type: string } source: { type: string, description: llm | fallback } gua_name: { type: string } day_part: { type: string } as_of: { type: string, format: date } shichen: { type: integer, minimum: 0, maximum: 11 } shichen_name: { type: string } valid_until: { type: string, format: date-time, description: Next 时辰 start RFC3339 } need_birth: { type: boolean } MembershipMe: type: object required: [active, status] properties: active: { type: boolean } plan: { type: string } status: { type: string } expires_at: { type: string, format: date-time, nullable: true } ask_quota_left: { type: integer } ScaleSummary: type: object required: [slug, title, description] properties: slug: { type: string } title: { type: string } description: { type: string } ScaleQuestionOption: type: object required: [key, text] properties: key: { type: string } text: { type: string } ScaleQuestion: type: object required: [id, sort, body] properties: id: { type: string, format: uuid } sort: { type: integer } body: type: object required: [prompt, options] properties: prompt: { type: string } options: type: array items: { $ref: '#/components/schemas/ScaleQuestionOption' } ScaleDetail: type: object required: [slug, title, description, questions] properties: slug: { type: string } title: { type: string } description: { type: string } access: { type: string, description: free | membership } locked: { type: boolean, description: true when membership required and inactive } questions: type: array items: { $ref: '#/components/schemas/ScaleQuestion' } ScaleSubmitResult: type: object required: [id, result] properties: id: { type: string, format: uuid } result: { type: object, additionalProperties: true } AskQuota: type: object required: [active_membership, remaining, free_limit, source] properties: active_membership: { type: boolean } remaining: { type: integer } free_limit: { type: integer } source: { type: string } AskThread: type: object required: [id, user_id, profile_id, created_at] properties: id: { type: string, format: uuid } user_id: { type: string, format: uuid } profile_id: { type: string, format: uuid } scene: { type: string, nullable: true } created_at: { type: string, format: date-time } AskMessage: type: object required: [id, thread_id, role, content, created_at] properties: id: { type: string, format: uuid } thread_id: { type: string, format: uuid } role: { type: string, enum: [user, assistant] } content: { type: string } created_at: { type: string, format: date-time } ImageCardScene: type: object required: [key, label] properties: key: { type: string } label: { type: string } ImageCardQuota: type: object required: [remaining, daily_free, unlimited] properties: remaining: { type: integer } daily_free: { type: integer } unlimited: { type: boolean } EnvelopeProfileList: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: type: object properties: items: type: array items: { $ref: '#/components/schemas/Profile' } EnvelopeGrowthReport: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: { $ref: '#/components/schemas/GrowthReport' } EnvelopeHomeDailyTips: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: { $ref: '#/components/schemas/HomeDailyTips' } EnvelopeMembershipMe: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: { $ref: '#/components/schemas/MembershipMe' } EnvelopeScaleList: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: type: object properties: items: type: array items: { $ref: '#/components/schemas/ScaleSummary' } EnvelopeAskQuota: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: { $ref: '#/components/schemas/AskQuota' } EnvelopeReportList: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: type: object properties: items: type: array items: { $ref: '#/components/schemas/GrowthReport' } EnvelopeScaleDetail: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: { $ref: '#/components/schemas/ScaleDetail' } EnvelopeImageCardScenes: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: type: object properties: items: type: array items: { $ref: '#/components/schemas/ImageCardScene' } EnvelopeImageCardQuota: allOf: - $ref: '#/components/schemas/Envelope' - type: object properties: data: { $ref: '#/components/schemas/ImageCardQuota' }