openapi: 3.0.3
info:
  title: Reward API v2
  version: 2.0.0
  description: |-
    Reward v2 是新 App / 新硬件使用的完整协议，支持 preset 与用户上传图片 asset。

    v2 图片协议：
    - icon_type=preset：icon 是 App 内置资源 key，例如 reward_gift、reward_movie。
    - icon_type=asset：icon 是 assets file_key，不是 CDN URL。
    - v2 response 返回真实 icon_type / reward_icon_type；client 应按类型选择本地 preset 或 assets CDN 图片。
    - 自定义图片复用 assets 上传链路：先调用 /api/v1/assets/presigned-url 上传，再把 file_key 作为 icon 写入 Reward。
    - 展示 asset 时通过 /api/v1/assets/config 获取 asset_prefix 和 token，再拼接 CDN URL。

    Reward Redeem 历史返回兑换时的 Reward name/icon/icon_type 快照，后续编辑 Reward 不影响历史展示。
servers:
  - url: /api/v2
tags:
  - name: RewardV2
    description: Reward v2 preset/asset endpoints
  - name: RewardRedeemV2
    description: Reward redeem v2 endpoints
  - name: RewardBatchV2
    description: Reward batch v2 endpoints

paths:
  /reward/dictionaries:
    get:
      tags: [RewardV2]
      summary: 奖品列表（v2 preset/asset）
      operationId: rewardV2ListDictionaries
      description: |-
        返回当前 family 内已上架的奖品列表。列表项包含真实 icon 和 icon_type。

        Client 渲染：
        - icon_type=preset 时，icon 按本地内置资源 key 渲染。
        - icon_type=asset 时，icon 是 assets file_key，需通过 assets config 拼接 CDN URL。
        - receive_role_ids 为空数组表示当前 family 下所有有效 role 都可兑换。
      parameters:
        - $ref: "../common/api.yml#/components/parameters/AuthorizationHeader"
        - $ref: "../common/api.yml#/components/parameters/FamilyIdHeader"
      responses:
        "200":
          description: 成功返回奖品列表。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RewardListResponseV2"
              example:
                code: 0
                message: null
                data:
                  list:
                    - id: "523e4567-e89b-12d3-a456-426614174000"
                      family_id: "123e4567-e89b-12d3-a456-426614174000"
                      name: "Photo Reward"
                      icon: "unknown-family/3fa2411b-8cb0-4285-805e-d0c80707978e/public_1825d397-3d70-443d-a928-3ae3a299bfcc/calendar.jpg"
                      icon_type: "asset"
                      credit: 50
                      star: 50
                      is_active: true
                      is_repeatable: true
                      receive_role_ids: []
                      created_uid: "323e4567-e89b-12d3-a456-426614174000"
                      modified_uid: "323e4567-e89b-12d3-a456-426614174000"
                      created_at: "2026-06-10T10:00:00Z"
                      updated_at: "2026-06-10T10:00:00Z"
                  total: 1
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/MissingFamily"
        "500":
          $ref: "#/components/responses/InternalError"

    post:
      tags: [RewardV2]
      summary: 创建奖品（v2 preset/asset）
      operationId: rewardV2CreateDictionary
      description: |-
        创建一个新的奖品项。

        图片写入规则：
        - icon_type=preset 时，icon 必须是 reward_ 开头的 App 内置 key；为空或非法时服务端规范化为 reward_gift。
        - icon_type=asset 时，icon 必须是已上传图片的 assets file_key；服务端会校验 asset 存在且 MIME 为 image/*。
        - 未传 icon_type 时，服务端按 icon 兼容推断：reward_ 前缀为 preset；可识别的图片 file_key 为 asset；其他值规范化为 reward_gift/preset。
        - 不保存 CDN URL，只保存 file_key。

        star 与 credit：
        - star 和 credit 都表示兑换所需星星数。
        - 如果两者都传，服务端以 star 合并后的值为准；新 client 建议只传 star。
      parameters:
        - $ref: "../common/api.yml#/components/parameters/AuthorizationHeader"
        - $ref: "../common/api.yml#/components/parameters/FamilyIdHeader"
        - $ref: "../common/api.yml#/components/parameters/TimezoneHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRewardRequestV2"
            examples:
              preset:
                value:
                  name: "Movie Night"
                  icon: "reward_movie"
                  icon_type: "preset"
                  star: 50
                  is_active: true
                  is_repeatable: true
                  receive_role_ids: []
              asset:
                value:
                  name: "Photo Reward"
                  icon: "unknown-family/3fa2411b-8cb0-4285-805e-d0c80707978e/public_1825d397-3d70-443d-a928-3ae3a299bfcc/calendar.jpg"
                  icon_type: "asset"
                  star: 50
                  is_active: true
                  is_repeatable: true
                  receive_role_ids: []
      responses:
        "200":
          description: 创建成功，返回创建后的奖品对象。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RewardResponseV2"
        "400":
          $ref: "#/components/responses/InvalidInput"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/MissingFamily"
        "500":
          $ref: "#/components/responses/InternalError"

  /reward/dictionaries/{id}:
    parameters:
      - $ref: "#/components/parameters/RewardId"
    post:
      tags: [RewardV2]
      summary: 更新奖品（v2 preset/asset）
      operationId: rewardV2UpdateDictionary
      description: |-
        部分更新奖品信息，未传字段保持不变。

        图片更新规则：
        - icon 和 icon_type 都不传时，不修改图片。
        - icon_type=preset 时，icon 是 reward_ 开头的 App 内置 key；非法或空值会规范化为 reward_gift。
        - icon_type=asset 时，icon 是已上传图片 file_key；必须存在且 MIME 为 image/*。
        - 支持 preset 与 asset 互相切换。
      parameters:
        - $ref: "../common/api.yml#/components/parameters/AuthorizationHeader"
        - $ref: "../common/api.yml#/components/parameters/FamilyIdHeader"
        - $ref: "../common/api.yml#/components/parameters/TimezoneHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateRewardRequestV2"
            examples:
              update_asset_icon:
                value:
                  icon: "unknown-family/3fa2411b-8cb0-4285-805e-d0c80707978e/public_1825d397-3d70-443d-a928-3ae3a299bfcc/calendar.jpg"
                  icon_type: "asset"
              update_preset_icon:
                value:
                  icon: "reward_movie"
                  icon_type: "preset"
              update_star:
                value:
                  star: 80
      responses:
        "200":
          description: 更新成功，返回更新后的奖品对象。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RewardResponseV2"
        "400":
          $ref: "#/components/responses/InvalidInput"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/MissingFamily"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /reward/redeems:
    post:
      tags: [RewardRedeemV2]
      summary: 兑换奖品
      operationId: rewardV2Redeem
      description: |-
        为一个或多个 family role 兑换同一个奖品。

        处理语义：
        - receiver_role_ids 是本次接收奖品的角色列表。
        - 服务端按 receiver 逐个检查资格、余额和不可重复限制。
        - 对每个成功 receiver 创建一条 reward_redeem，并扣减其星星余额。
        - 返回 results 数组逐个说明每个 receiver 成功或失败；部分失败时 HTTP 仍可能返回 200。
        - 成功兑换记录会快照 Reward 当前 name、icon、icon_type、credit，供兑换历史稳定展示。
        - 如果服务端开启 Reward redeemed notification，兑换成功后可能发送 OneSignal push；push 点击 URL 固定指向 Reward history 页面 `/app/reward/history`，完整域名由服务端环境配置拼接。
        - App 端触发时当前请求用户不会收到本次 push；硬件端触发时不按当前请求用户排除 target。
      parameters:
        - $ref: "../common/api.yml#/components/parameters/AuthorizationHeader"
        - $ref: "../common/api.yml#/components/parameters/FamilyIdHeader"
        - $ref: "../common/api.yml#/components/parameters/TimezoneHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RedeemRequest"
            example:
              reward_id: "523e4567-e89b-12d3-a456-426614174000"
              receiver_role_ids:
                - "223e4567-e89b-12d3-a456-426614174000"
                - "323e4567-e89b-12d3-a456-426614174000"
      responses:
        "200":
          description: 兑换请求已处理；client 应读取 results 展示每个 receiver 的结果。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RedeemResponse"
        "400":
          $ref: "#/components/responses/InvalidInput"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/MissingFamily"
        "404":
          $ref: "#/components/responses/NotFound"
        "500":
          $ref: "#/components/responses/InternalError"

    delete:
      tags: [RewardRedeemV2]
      summary: 批量取消兑换（反兑换）
      operationId: rewardV2BatchCancelRedeem
      description: |-
        批量取消兑换记录并重新计算星星余额。

        注意：请求体是裸 UUID 数组，不是 {"ids":[...]} 对象。
      parameters:
        - $ref: "../common/api.yml#/components/parameters/AuthorizationHeader"
        - $ref: "../common/api.yml#/components/parameters/FamilyIdHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchRedeemIdsRequest"
            example:
              - "623e4567-e89b-12d3-a456-426614174001"
              - "623e4567-e89b-12d3-a456-426614174002"
      responses:
        "200":
          description: 批量取消兑换成功。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BatchDeleteRedeemResponse"
        "400":
          $ref: "#/components/responses/InvalidInput"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /reward/redeems/list:
    post:
      tags: [RewardRedeemV2]
      summary: 兑换历史列表（v2 preset/asset）
      operationId: rewardV2ListRedeems
      description: |-
        分页查询当前 family 的兑换历史。

        v2 历史图片规则：
        - 列表项返回 reward_icon 和 reward_icon_type。
        - reward_icon_type=preset 时，reward_icon 是本地内置资源 key。
        - reward_icon_type=asset 时，reward_icon 是兑换当时快照的 assets file_key。
        - 历史展示以 reward_redeem 快照为准，不受 Reward 后续编辑影响。

        查询口径：
        - 仅返回 is_display=true 的兑换记录。
        - 已删除 family role 对应的兑换记录不会返回。
        - total 与 list 使用同一过滤条件。

        分页与筛选：
        - page_size、page_index 通过 query string 传递。
        - receive_role_ids 通过 JSON body 传递；空数组或省略字段表示查询所有未删除 role 的兑换记录。
        - 当前实现需要 JSON body；查询全部时传 {} 或 {"receive_role_ids":[]}。
      parameters:
        - $ref: "../common/api.yml#/components/parameters/AuthorizationHeader"
        - $ref: "../common/api.yml#/components/parameters/FamilyIdHeader"
        - $ref: "../common/api.yml#/components/parameters/TimezoneHeader"
        - $ref: "#/components/parameters/PageSize"
        - $ref: "#/components/parameters/PageIndex"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RedeemListRequest"
            examples:
              all_roles:
                value:
                  receive_role_ids: []
              specified_roles:
                value:
                  receive_role_ids:
                    - "223e4567-e89b-12d3-a456-426614174000"
      responses:
        "200":
          description: 成功返回兑换历史列表。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RedeemListResponseV2"
        "400":
          $ref: "#/components/responses/InvalidInput"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/MissingFamily"
        "500":
          $ref: "#/components/responses/InternalError"

  /reward/redeems/{id}:
    parameters:
      - $ref: "#/components/parameters/RedeemId"
    delete:
      tags: [RewardRedeemV2]
      summary: 取消兑换（反兑换）
      operationId: rewardV2CancelRedeem
      description: 删除指定 reward_redeem 并重新计算星星余额。
      parameters:
        - $ref: "../common/api.yml#/components/parameters/AuthorizationHeader"
        - $ref: "../common/api.yml#/components/parameters/FamilyIdHeader"
      responses:
        "200":
          description: 取消兑换成功。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeleteRedeemResponse"
        "400":
          $ref: "#/components/responses/InvalidInput"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/RedeemNotFound"
        "500":
          $ref: "#/components/responses/InternalError"

  /reward/rewards/batch:
    post:
      tags: [RewardBatchV2]
      summary: 批量创建 Reward（v2 路径）
      operationId: rewardV2BatchCreateRewards
      description: |-
        Agent 批量创建入口。v2 路径为 /reward/rewards/batch。

        批量写入支持完整图片能力，item 可携带 icon_type=preset|asset。
        create 时如果某个 item 同时未传 icon 和 icon_type，服务端默认写入 reward_gift/preset。
      parameters:
        - $ref: "../common/api.yml#/components/parameters/AuthorizationHeader"
        - $ref: "../common/api.yml#/components/parameters/FamilyIdHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchCreateRewardRequest"
      responses:
        "200":
          description: 批量处理结果。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BatchRewardResponse"
    delete:
      tags: [RewardBatchV2]
      summary: 批量删除 Reward（软删除，v2 路径）
      operationId: rewardV2BatchDeleteRewards
      description: 按输入顺序软删除 Reward；遇到失败后，后续项标记为 skip。
      parameters:
        - $ref: "../common/api.yml#/components/parameters/AuthorizationHeader"
        - $ref: "../common/api.yml#/components/parameters/FamilyIdHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchDeleteRewardRequest"
      responses:
        "200":
          description: 批量处理结果。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BatchRewardResponse"

  /reward/rewards/batch/update:
    post:
      tags: [RewardBatchV2]
      summary: 批量更新 Reward（v2 路径）
      operationId: rewardV2BatchUpdateRewards
      description: Agent 批量更新入口；遇到失败后，后续项标记为 skip。
      parameters:
        - $ref: "../common/api.yml#/components/parameters/AuthorizationHeader"
        - $ref: "../common/api.yml#/components/parameters/FamilyIdHeader"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchUpdateRewardRequest"
      responses:
        "200":
          description: 批量处理结果。
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BatchRewardResponse"

components:
  parameters:
    RewardId:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Reward ID（reward_dic.id）。
    RedeemId:
      name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Reward redeem ID（reward_redeem.id）。
    PageSize:
      name: page_size
      in: query
      required: false
      schema:
        type: integer
        default: 50
        minimum: 1
        maximum: 100
      description: 每页大小，默认 50，最大 100。
    PageIndex:
      name: page_index
      in: query
      required: false
      schema:
        type: integer
        default: 1
        minimum: 1
      description: 页码，从 1 开始。

  schemas:
    RewardIconType:
      type: string
      enum: [preset, asset]
      description: |-
        Reward 图片类型：
        - preset：icon 是 App 内置资源 key，当前要求 reward_ 前缀。
        - asset：icon 是 assets file_key，client 需要通过 assets config 拼接 CDN URL。

    RewardListResponseV2:
      allOf:
        - $ref: "../common/api.yml#/components/schemas/ApiResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/RewardListDataV2"
    RewardResponseV2:
      allOf:
        - $ref: "../common/api.yml#/components/schemas/ApiResponse"
        - type: object
          properties:
            data:
              $ref: "#/components/schemas/RewardDetailV2"
    RewardListDataV2:
      type: object
      properties:
        list:
          type: array
          items:
            $ref: "#/components/schemas/RewardDetailV2"
        total:
          type: integer
          description: 当前 family active Reward 数量。

    RewardDetailV2:
      type: object
      description: v2 Reward 字典项；包含真实 icon_type。
      properties:
        id: { type: string, format: uuid, description: Reward ID。 }
        family_id: { type: string, format: uuid, description: 所属 family ID。 }
        name: { type: string, description: Reward 名称。 }
        icon:
          type: string
          nullable: true
          description: preset key 或 assets file_key，由 icon_type 决定。
        icon_type:
          allOf:
            - $ref: "#/components/schemas/RewardIconType"
          nullable: true
        credit:
          type: integer
          nullable: true
          minimum: 0
          description: 兑换所需星星数。
        star:
          type: integer
          nullable: true
          minimum: 0
          description: 与 credit 等价的展示字段。
        is_active:
          type: boolean
          nullable: true
          description: 是否上架；列表接口当前只返回 true。
        is_repeatable:
          type: boolean
          nullable: true
          description: 是否可重复兑换。
        receive_role_ids:
          type: array
          items: { type: string, format: uuid }
          description: 可兑换 role_id 列表；空数组表示所有有效 role。
        created_uid:
          type: string
          format: uuid
          nullable: true
        modified_uid:
          type: string
          format: uuid
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CreateRewardRequestV2:
      type: object
      required: [name]
      additionalProperties: false
      properties:
        name: { type: string, minLength: 1, maxLength: 128 }
        icon:
          type: string
          maxLength: 1024
          description: preset key 或 assets file_key。
        icon_type:
          $ref: "#/components/schemas/RewardIconType"
        credit: { type: integer, minimum: 0, description: 历史字段，语义等同 star。 }
        star: { type: integer, minimum: 0, description: 推荐使用的星星数字段；优先级高于 credit。 }
        is_active: { type: boolean, default: true }
        is_repeatable: { type: boolean, default: true }
        receive_role_ids:
          type: array
          items: { type: string, format: uuid }
          description: 可兑换 role_id 列表；空数组或不传表示所有有效 role。
    UpdateRewardRequestV2:
      type: object
      additionalProperties: false
      properties:
        name: { type: string, minLength: 1, maxLength: 128 }
        icon:
          type: string
          maxLength: 1024
          description: preset key 或 assets file_key；不传表示不改图片。
        icon_type:
          $ref: "#/components/schemas/RewardIconType"
        credit: { type: integer, minimum: 0, description: 历史字段，语义等同 star。 }
        star: { type: integer, minimum: 0, description: 推荐使用的星星数字段；优先级高于 credit。 }
        is_active: { type: boolean }
        is_repeatable: { type: boolean }
        receive_role_ids:
          type: array
          items: { type: string, format: uuid }
          description: 可兑换 role_id 列表；空数组表示所有有效 role。

    RedeemRequest:
      type: object
      required: [reward_id, receiver_role_ids]
      additionalProperties: false
      properties:
        reward_id:
          type: string
          format: uuid
          description: 要兑换的 Reward ID。
        receiver_role_ids:
          type: array
          minItems: 1
          items: { type: string, format: uuid }
          description: 接收 Reward 的 family role ID 列表。
    RedeemResponse:
      allOf:
        - $ref: "../common/api.yml#/components/schemas/ApiResponse"
        - type: object
          properties:
            data:
              type: object
              properties:
                list:
                  type: array
                  items: { type: string, format: uuid }
                  description: 成功创建的 reward_redeem ID 列表。
                results:
                  type: array
                  items:
                    $ref: "#/components/schemas/RewardRedeemResult"
    RewardRedeemResult:
      type: object
      properties:
        family_id: { type: string, format: uuid }
        role_id: { type: string, format: uuid, description: receiver role ID。 }
        result: { type: string, enum: [success, failed] }
        message: { type: string, description: 结果消息；失败时包含原因。 }
        inserted_id:
          type: string
          format: uuid
          nullable: true
          description: 成功时创建的 reward_redeem ID。
        role_nick_name: { type: string }
        role_name: { type: string, nullable: true }

    RedeemListRequest:
      type: object
      additionalProperties: false
      properties:
        receive_role_ids:
          type: array
          items: { type: string, format: uuid }
          description: receiver role 过滤；空或不传表示所有未删除 role。
    RedeemListResponseV2:
      allOf:
        - $ref: "../common/api.yml#/components/schemas/ApiResponse"
        - type: object
          properties:
            data:
              type: object
              properties:
                list:
                  type: array
                  items:
                    $ref: "#/components/schemas/RedeemDetailV2"
                total: { type: integer, format: int64 }
                page_size: { type: integer }
                page_index: { type: integer }
    RedeemDetailV2:
      type: object
      description: v2 兑换历史项；包含 reward_icon_type 快照。
      properties:
        id: { type: string, format: uuid }
        reward_id: { type: string, format: uuid }
        reward_name: { type: string, description: 兑换时 Reward 名称快照。 }
        reward_icon:
          type: string
          nullable: true
          description: 兑换时 Reward 图标快照；preset key 或 assets file_key。
        reward_icon_type:
          allOf:
            - $ref: "#/components/schemas/RewardIconType"
          nullable: true
          description: 兑换时 Reward 图片类型快照。
        family_id: { type: string, format: uuid }
        role_id: { type: string, format: uuid, description: receiver role ID。 }
        role_nick_name: { type: string }
        role_name: { type: string, nullable: true }
        role_avatar:
          allOf:
            - $ref: "../common/api.yml#/components/schemas/AssetRequest"
          nullable: true
        user_id: { type: string, format: uuid, description: receiver user ID。 }
        trigger_uid: { type: string, format: uuid, description: 发起兑换的 user ID。 }
        credit: { type: integer, minimum: 0, description: 兑换时星星数快照。 }
        created_at: { type: string, format: date-time }

    DeleteRedeemResponse:
      allOf:
        - $ref: "../common/api.yml#/components/schemas/ApiResponse"
        - type: object
          properties:
            data:
              type: object
              properties:
                id: { type: string, format: uuid }
                message: { type: string }
    BatchRedeemIdsRequest:
      type: array
      minItems: 1
      items: { type: string, format: uuid }
    BatchDeleteRedeemResponse:
      allOf:
        - $ref: "../common/api.yml#/components/schemas/ApiResponse"
        - type: object
          properties:
            data:
              type: object
              properties:
                deleted_count: { type: integer }
                deleted_ids:
                  type: array
                  items: { type: string, format: uuid }
                message: { type: string }

    BatchCreateRewardRequest:
      type: object
      required: [rewards]
      properties:
        rewards:
          type: array
          minItems: 1
          maxItems: 100
          items:
            $ref: "#/components/schemas/CreateRewardRequestV2"
    BatchUpdateRewardRequest:
      type: object
      required: [rewards]
      properties:
        rewards:
          type: array
          minItems: 1
          maxItems: 100
          items:
            allOf:
              - $ref: "#/components/schemas/UpdateRewardRequestV2"
              - type: object
                required: [id]
                properties:
                  id: { type: string, format: uuid }
    BatchDeleteRewardRequest:
      type: object
      required: [reward_ids]
      properties:
        reward_ids:
          type: array
          minItems: 1
          items: { type: string, format: uuid }
    BatchRewardResponse:
      allOf:
        - $ref: "../common/api.yml#/components/schemas/ApiResponse"
        - type: object
          properties:
            data:
              type: object
              properties:
                success_count: { type: integer }
                break_error:
                  $ref: "#/components/schemas/BatchRewardOperationError"
                results:
                  type: array
                  items:
                    $ref: "#/components/schemas/BatchRewardOperationItem"
                  description: 按输入顺序返回每个 item 的状态；success=已执行成功，failed=当前项失败并触发 break，skip=前面失败后未执行。
    BatchRewardOperationError:
      type: object
      properties:
        index: { type: integer }
        reward_id: { type: string, nullable: true }
        error: { type: string }
    BatchRewardOperationItem:
      type: object
      properties:
        index: { type: integer }
        reward:
          $ref: "#/components/schemas/RewardDetailV2"
        status:
          type: string
          enum: [success, failed, skip]

  responses:
    InvalidInput:
      description: 请求参数非法。
      content:
        application/json:
          schema:
            $ref: "../common/api.yml#/components/schemas/ApiResponse"
    Unauthorized:
      description: 未通过鉴权。
      content:
        application/json:
          schema:
            $ref: "../common/api.yml#/components/schemas/ApiResponse"
    MissingFamily:
      description: 缺少家庭上下文。
      content:
        application/json:
          schema:
            $ref: "../common/api.yml#/components/schemas/ApiResponse"
    NotFound:
      description: Reward 不存在。
      content:
        application/json:
          schema:
            $ref: "../common/api.yml#/components/schemas/ApiResponse"
    RedeemNotFound:
      description: Reward redeem 不存在。
      content:
        application/json:
          schema:
            $ref: "../common/api.yml#/components/schemas/ApiResponse"
    InternalError:
      description: 服务内部错误。
      content:
        application/json:
          schema:
            $ref: "../common/api.yml#/components/schemas/ApiResponse"
