# 生成的文件，不要手改：node src/public-api/openapi.ts（来源：src/public-api/endpoints.ts 与 schemas.ts）
openapi: "3.1.0"
info:
  title: "渡鸥 DOOU 对外接口"
  version: "1.0.0"
  description: "金额单位都是分（人民币，100 分 = ¥1），时间是 ISO 8601。价格与工作台一样，不另收。\n认证：Authorization: Bearer doou_live_…（在工作台「API 与 MCP」建密钥；密钥绑一个工作区，代表建它的人）。\n限流（每分钟）：每把密钥 60 次，其中标了 x-doou-costly 的（要调外部数据或模型的）10 次；每个组织合计 300 次、30 次。超了 429 + Retry-After。\n会改东西的请求可以带 Idempotency-Key（1–100 个可见 ASCII 字符）：24 小时内同一把密钥重发同一个键，拿到第一次的结果（响应头 Idempotent-Replayed: true），不会再跑一次、再扣一次。\n说明见 docs/api/README.md。"
servers:
  - url: "https://{host}/v1"
    variables:
      host:
        default: doouai.com
security:
  - apiKey: []
paths:
  /balance:
    get:
      operationId: getBalance
      tags:
        - "账户"
      summary: "余额（单位分）"
      description: "availableFen 可用；heldFen 进行中（正在跑的查询占着的）；creditFen 这期会员用量还剩多少。"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WalletView"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /ledger:
    get:
      operationId: getLedger
      tags:
        - "账户"
      summary: "账户流水（最近的在前）"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LedgerResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /members:
    get:
      operationId: listMembers
      tags:
        - "账户"
      summary: "组织成员（分配负责人、待办用）"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MemberListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /searches/quote:
    post:
      operationId: quoteSearch
      tags:
        - "买家搜索"
      summary: "买家搜索报价（免费）"
      description: "maxFen 是这次最多多少钱（分）；需求搜索和相似客户搜索用同一个报价。电商卖家搜索、广告主搜索带上 kind（sellers、advertisers）另报。跑完的实付在结果的 chargedFen。"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SearchQuoteBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchQuoteResponse"
        "400":
          description: "请求体不是 JSON 对象、quantity 不是整数或 kind 认不出（bad_request）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  /searches:
    post:
      operationId: startSearch
      tags:
        - "买家搜索"
      summary: "需求搜索（花钱：最多报价的 maxFen，实付见结果）"
      description: "一句话需求（或点选的 country、buyerTypes、note）找海外买家。先回 runId，后台接着跑；用 GET /searches/{runId} 轮询。没写清卖什么或找哪个国家时回 200 和一句追问。productId 是「我的产品」里的一个（记下为哪个产品找的），不是本组织的产品回 400 unknown_product。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StartSearchBody"
      responses:
        "200":
          description: "回一句追问（needsClarification）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NeedsClarification"
        "202":
          description: "开跑了"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchStarted"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: "余额不够：差多少说清楚，没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InsufficientBalance"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          description: "同时在跑的搜索太多（too_many_running）：没开跑、没扣钱，等前面的做完再开，或请求太频繁（rate_limited，带 Retry-After）"
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: "#/components/schemas/ApiError"
                  - $ref: "#/components/schemas/RateLimited"
    get:
      operationId: listSearches
      tags:
        - "买家搜索"
      summary: "买家搜索记录（含相似客户搜索、电商卖家搜索、广告主搜索；productId 只要为这个产品找的）"
      parameters:
        - name: productId
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/searches/{runId}":
    get:
      operationId: getSearch
      tags:
        - "买家搜索"
      summary: "一次买家搜索的进度与名单（运行中轮询它；电商卖家、广告主搜索的名单行带 seenOn）"
      parameters:
        - name: runId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchView"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /similar:
    post:
      operationId: startSimilar
      tags:
        - "买家搜索"
      summary: "相似客户搜索：给 1–5 个老客户官网，找同类的公司（花钱，报价同需求搜索）"
      description: "老客户自己和客户库里已有的不会再给。老客户官网都认不出回 400 bad_seed。productId 是「我的产品」里的一个（当作卖什么的提示），不是本组织的产品回 400 unknown_product。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SimilarBody"
      responses:
        "200":
          description: "回一句追问（needsClarification）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NeedsClarification"
        "202":
          description: "开跑了"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchStarted"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: "余额不够：差多少说清楚，没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InsufficientBalance"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          description: "同时在跑的搜索太多（too_many_running）：没开跑、没扣钱，等前面的做完再开，或请求太频繁（rate_limited，带 Retry-After）"
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: "#/components/schemas/ApiError"
                  - $ref: "#/components/schemas/RateLimited"
  /sellers/countries:
    get:
      operationId: listSellerCountries
      tags:
        - "电商卖家搜索"
      summary: "电商卖家搜索能查的国家与渠道"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SellerCountriesResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /sellers:
    post:
      operationId: startSellers
      tags:
        - "电商卖家搜索"
      summary: "电商卖家搜索：在亚马逊、谷歌购物上卖这个产品的品牌与网店（花钱：最多报价的 maxFen，实付见结果）"
      description: "先 POST /searches/quote { quantity, kind: \"sellers\" } 报价，带上它的 maxFen 开跑。先回 runId，后台接着跑，用 GET /searches/{runId} 轮询；名单行带 seenOn（在哪看到它在卖，带链接）与 links（联系方式链接）。国家查不了 400 unsupported_country；productId 不是本组织的产品 400 unknown_product；产品说不清回 200 和一句追问。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SellersBody"
      responses:
        "200":
          description: "回一句追问（needsClarification）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NeedsClarification"
        "202":
          description: "开跑了，轮询 GET /searches/{runId}"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchStarted"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: "余额不够：差多少说清楚，没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InsufficientBalance"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "报价变了：现在的报价超过带的 maxFen（quote_changed，带现在的 maxFen），没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteChanged"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          description: "同时在跑的搜索太多（too_many_running）：没开跑、没扣钱，等前面的做完再开，或请求太频繁（rate_limited，带 Retry-After）"
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: "#/components/schemas/ApiError"
                  - $ref: "#/components/schemas/RateLimited"
  /advertisers/countries:
    get:
      operationId: listAdvertiserCountries
      tags:
        - "广告主搜索"
      summary: "广告主搜索能查的国家与平台"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdvertiserCountriesResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /advertisers:
    post:
      operationId: startAdvertisers
      tags:
        - "广告主搜索"
      summary: "广告主搜索：最近在 Facebook / Instagram、谷歌投这个产品广告的品牌（花钱：最多报价的 maxFen，实付见结果）"
      description: "先 POST /searches/quote { quantity, kind: \"advertisers\" } 报价，带上它的 maxFen 开跑。先回 runId，后台接着跑，用 GET /searches/{runId} 轮询；名单行带 seenOn（在哪投的广告，带链接）与 links。国家查不了 400 unsupported_country；productId 不是本组织的产品 400 unknown_product；产品说不清回 200 和一句追问。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AdvertisersBody"
      responses:
        "200":
          description: "回一句追问（needsClarification）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NeedsClarification"
        "202":
          description: "开跑了，轮询 GET /searches/{runId}"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchStarted"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: "余额不够：差多少说清楚，没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InsufficientBalance"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "报价变了：现在的报价超过带的 maxFen（quote_changed，带现在的 maxFen），没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteChanged"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          description: "同时在跑的搜索太多（too_many_running）：没开跑、没扣钱，等前面的做完再开，或请求太频繁（rate_limited，带 Retry-After）"
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: "#/components/schemas/ApiError"
                  - $ref: "#/components/schemas/RateLimited"
  /merchants/types:
    get:
      operationId: listMerchantTypes
      tags:
        - "地图商家搜索"
      summary: "地图商家搜索能选的商家类型（按零售门店、批发分销、工程安装分组）"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MerchantTypesResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /merchants/suggest:
    post:
      operationId: suggestMerchantTypes
      tags:
        - "地图商家搜索"
      summary: "按产品挑商家类型（免费）"
      description: "回几个商家类型 id，最相关的在前；开跑时可以照用或自己改。productId 不是本组织的产品 400 unknown_product。"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MerchantsSuggestBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MerchantsSuggestResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  /merchants:
    post:
      operationId: startMerchants
      tags:
        - "地图商家搜索"
      summary: "地图商家搜索：Google 地图上卖这个产品的本地门店、批发商、工程商（花钱：最多报价的 maxFen，实付见结果）"
      description: "先 POST /searches/quote { quantity, kind: \"merchants\" } 报价，带上它的 maxFen 开跑。先回 runId，后台接着跑，用 GET /searches/{runId} 轮询；名单行带 seenOn（地图上的类型、城市、评分与评论数，带链接）与 links（官网、电话）。只给有官网、还在营业、主业是选的类型的商家。国家不认得 400 unsupported_country；类型不在 GET /merchants/types 里 400 bad_request；productId 不是本组织的产品 400 unknown_product；产品说不清回 200 和一句追问。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MerchantsBody"
      responses:
        "200":
          description: "回一句追问（needsClarification）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NeedsClarification"
        "202":
          description: "开跑了，轮询 GET /searches/{runId}"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SearchStarted"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: "余额不够：差多少说清楚，没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InsufficientBalance"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "报价变了：现在的报价超过带的 maxFen（quote_changed，带现在的 maxFen），没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteChanged"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          description: "同时在跑的搜索太多（too_many_running）：没开跑、没扣钱，等前面的做完再开，或请求太频繁（rate_limited，带 Retry-After）"
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: "#/components/schemas/ApiError"
                  - $ref: "#/components/schemas/RateLimited"
  /trade/quote:
    post:
      operationId: quoteTrade
      tags:
        - "海关查询"
      summary: "海关查询报价（免费；产品先换成 HS 编码）"
      description: "三种：trade_product 产品查询、trade_competitor 竞品客户查询（进口商查询的两种），trade_company 企业进口记录（查一家公司的进口记录）。这次最多 = fixedFen + 勾选各项的 maxCostFen。执行时把 product 原样作为 productSpec 带回。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TradeQuoteBody"
      responses:
        "200":
          description: "报价，或追问"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TradeQuoteResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  /trade:
    post:
      operationId: runTrade
      tags:
        - "海关查询"
      summary: "执行海关查询（花钱：最多按报价，实付见 chargedFen）"
      description: "productId 是「我的产品」里的一个（记下为哪个产品查的），不是本组织的产品回 400 unknown_product。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TradeRunBody"
      responses:
        "200":
          description: "结果（三种查询的结果原样在 result 里）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TradeRunResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: "余额不够：差多少说清楚，没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InsufficientBalance"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
    get:
      operationId: listTrades
      tags:
        - "海关查询"
      summary: "以前的海关查询（新的在前，最多 50 次，不含结果；productId 只要为这个产品查的，kind 只要这一种）"
      parameters:
        - name: productId
          in: query
          required: false
          schema:
            type: string
        - name: kind
          in: query
          required: false
          schema:
            type: string
            enum:
              - trade_company
              - trade_product
              - trade_competitor
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TradeListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/trade/{runId}":
    get:
      operationId: getTrade
      tags:
        - "海关查询"
      summary: "看以前的一次海关查询"
      parameters:
        - name: runId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TradeRunView"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/companies/{companyId}/contacts":
    get:
      operationId: getCompanyContacts
      tags:
        - "联系人"
      summary: "一家公司的联系方式与已知的人（公司要在这个工作区的名单或客户库里）"
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompanyContactsView"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      operationId: runContacts
      tags:
        - "联系人"
      summary: "查联系人的邮箱 / 手机（花钱：没找到不收，查到过的不再收；实付见 chargedFen）"
      x-doou-costly: true
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContactRunBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactRunResult"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: "余额不够：差多少说清楚，没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InsufficientBalance"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "这几个人正在别处查"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/companies/{companyId}/channels":
    post:
      operationId: refreshCompanyChannels
      tags:
        - "联系人"
      summary: "再找一遍公司联系方式（免费）"
      x-doou-costly: true
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompanyContactsView"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "正在查，稍后再看"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/companies/{companyId}/contacts/quote":
    post:
      operationId: quoteContacts
      tags:
        - "联系人"
      summary: "找联系人报价（免费：找人不收钱，查邮箱 / 手机才收）"
      description: "人的 key 是我们的联系人编号（p_ 开头），查邮箱时原样带回。"
      x-doou-costly: true
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContactQuoteBody"
      responses:
        "200":
          description: "候选人与这次最多多少钱；没有官网回 no_domain"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactQuoteResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  /verify/quote:
    post:
      operationId: quoteVerify
      tags:
        - "邮箱验证"
      summary: "邮箱验证报价（免费）"
      description: "给邮箱（emails）、客户库里的公司（companyIds），或 allUnverified（客户库里还没验证过的，最多 500 个）。count 是要验证几个（30 天内验证过能收信的跳过，个数在 skipped）；maxFen 是最多多少钱（分）。"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VerifyBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VerifyQuoteResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          description: "给的公司不在客户库里"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  /verify:
    post:
      operationId: startVerify
      tags:
        - "邮箱验证"
      summary: "邮箱验证（花钱：最多报价的 maxFen；结果不明、没验成的不收）"
      description: "先报价，带上它的 maxFen 开始。在后台一个个验：202 回 batchId，轮询 GET /verify/{batchId}，验完一个出一个结论，全部验完结算（实付在 chargedFen）。都验证过了 400 nothing_to_verify；给的公司不在客户库里 404。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VerifyRunBody"
      responses:
        "202":
          description: "开跑了，轮询 GET /verify/{batchId}"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VerifyStarted"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: "余额不够：差多少说清楚，没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InsufficientBalance"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "要验证的邮箱变了，报价超过带的 maxFen（quote_changed，带现在的 maxFen），没开跑"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteChanged"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/verify/{batchId}":
    get:
      operationId: getVerify
      tags:
        - "邮箱验证"
      summary: "一批邮箱验证（运行中轮询它：验完一个出一个结论）"
      parameters:
        - name: batchId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VerifyBatchView"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /companies/resolve:
    post:
      operationId: resolveCompany
      tags:
        - "查一家公司"
      summary: "查一家公司：官网、邮箱或公司名 → 认出是哪一家（免费）"
      description: "for 给 similar 只认官网是哪家；认出了（company.domain）就能加进客户库：POST /library { manual: { website, name, country } }。for 给 imports 另找它在海关里的进口商写法（candidates，执行企业进口记录时作为 customsName 带上）。只给了公司名时多半认不出官网，带上官网或公司邮箱再认。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CompanyResolveBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompanyResolveView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/companies/{companyId}":
    get:
      operationId: getCompany
      tags:
        - "查一家公司"
      summary: "一家公司（不在客户库也能看：找到过的、海关查询或求购信息里认出的；别的 404）"
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompanyView"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /library:
    get:
      operationId: listLibrary
      tags:
        - "客户库"
      summary: "客户库列表（筛选、排序，一页 50）"
      parameters:
        - name: q
          in: query
          required: false
          schema:
            type: string
        - name: country
          in: query
          required: false
          schema:
            type: string
        - name: band
          in: query
          required: false
          schema:
            type: string
            enum:
              - premium
              - related
              - potential
        - name: stage
          in: query
          required: false
          schema:
            type: string
            enum:
              - new
              - contacted
              - replied
              - sampling
              - won
              - dropped
        - name: tag
          in: query
          required: false
          schema:
            type: string
        - name: hasPeople
          in: query
          required: false
          schema:
            type: boolean
        - name: due
          in: query
          required: false
          schema:
            type: boolean
        - name: owner
          in: query
          required: false
          schema:
            type: string
        - name: origin
          in: query
          required: false
          schema:
            type: string
            enum:
              - search
              - similar
              - sellers
              - advertisers
              - merchants
              - trade
              - competitor
              - manual
              - import
              - card
              - posts
              - team
        - name: starred
          in: query
          required: false
          schema:
            type: boolean
        - name: productId
          in: query
          required: false
          schema:
            type: string
        - name: sort
          in: query
          required: false
          schema:
            type: string
            enum:
              - recent
              - follow_up
              - name
              - last_activity
        - name: page
          in: query
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LibraryListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      operationId: addToLibrary
      tags:
        - "客户库"
      summary: "加进客户库（免费）：海关查询、竞品画像、求购信息里认出来的，或手动新建（必须有官网）"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LibraryAddBody"
      responses:
        "200":
          description: "已经在库里"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LibraryCompanyView"
        "201":
          description: "新加的"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LibraryCompanyView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          description: "客户库满了（免费 50 家）（quota_exceeded），或请求太频繁（rate_limited，带 Retry-After）"
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: "#/components/schemas/QuotaExceeded"
                  - $ref: "#/components/schemas/RateLimited"
  /library/import:
    post:
      operationId: importLibrary
      tags:
        - "客户库"
      summary: "导入客户（.xlsx / .csv，multipart 字段 file，≤ 5 MB、≤ 2,000 行；查询参数 dryRun=true 只预览；客户库满了超出的跳过并说明）"
      description: "列自动认；认不出的列可以带 multipart 字段 mapping（JSON：表头 → 字段 name / website / country / stage / tags / note / contactName / firstName / lastName / title / email / phone，\"\" 是不导入；没表头或重名的列用 \"#第几列\"），给了的按给的。字段不认识 400 bad_mapping；没认出官网列 400 no_website_column。"
      parameters:
        - name: dryRun
          in: query
          required: false
          schema:
            type: boolean
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  contentMediaType: application/octet-stream
                mapping:
                  type: string
                  description: "列对应（可不给）：JSON，表头 → 字段；\"\" 是不导入，没表头或重名的列用 \"#第几列\""
              required:
                - file
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LibraryImportResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  /library/export:
    get:
      operationId: exportLibrary
      tags:
        - "客户库"
      summary: "导出 Excel（公司、联系人两张表；筛选同列表，不分页，最多 5,000 家）"
      parameters:
        - name: q
          in: query
          required: false
          schema:
            type: string
        - name: country
          in: query
          required: false
          schema:
            type: string
        - name: band
          in: query
          required: false
          schema:
            type: string
            enum:
              - premium
              - related
              - potential
        - name: stage
          in: query
          required: false
          schema:
            type: string
            enum:
              - new
              - contacted
              - replied
              - sampling
              - won
              - dropped
        - name: tag
          in: query
          required: false
          schema:
            type: string
        - name: hasPeople
          in: query
          required: false
          schema:
            type: boolean
        - name: due
          in: query
          required: false
          schema:
            type: boolean
        - name: owner
          in: query
          required: false
          schema:
            type: string
        - name: origin
          in: query
          required: false
          schema:
            type: string
            enum:
              - search
              - similar
              - sellers
              - advertisers
              - merchants
              - trade
              - competitor
              - manual
              - import
              - card
              - posts
              - team
        - name: starred
          in: query
          required: false
          schema:
            type: boolean
        - name: productId
          in: query
          required: false
          schema:
            type: string
        - name: sort
          in: query
          required: false
          schema:
            type: string
            enum:
              - recent
              - follow_up
              - name
              - last_activity
        - name: page
          in: query
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: "Excel 文件"
          content:
            application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
              schema:
                type: string
                contentMediaType: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          description: "免费版不能导出，要开通会员（quota_exceeded），或请求太频繁（rate_limited，带 Retry-After）"
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: "#/components/schemas/QuotaExceeded"
                  - $ref: "#/components/schemas/RateLimited"
  /library/stats:
    get:
      operationId: getLibraryStats
      tags:
        - "客户库"
      summary: "客户报表（阶段分布、来源效果、近 8 周、按负责人）"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LibraryStatsResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /search:
    get:
      operationId: searchWorkspace
      tags:
        - "客户库"
      summary: "全局搜索：客户库里的公司、联系人、做过的搜索（每类最多 5 条）"
      parameters:
        - name: q
          in: query
          required: false
          schema:
            type: string
            description: "搜的字（≤ 100 字）"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceSearchResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/companies/{companyId}/feedback":
    post:
      operationId: markNotRelevant
      tags:
        - "客户库"
      summary: "标「不相关」：这家以后不再给这个产品（按组织）"
      description: "说过的在客户库那一行的 notRelevant、名单行的 library.notRelevant 里。productId 不是本组织的产品 400 unknown_product；公司看不到 404。"
      x-doou-costly: true
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CompanyFeedbackBody"
      responses:
        "204":
          description: "成功，没有内容"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
    delete:
      operationId: undoNotRelevant
      tags:
        - "客户库"
      summary: "撤销「不相关」（按组织；当时加进产品卡的排除词也拿掉；没说过也 204）"
      description: "查询参数 productId 必填：没带 400，不是本组织的产品 400 unknown_product；公司看不到 404。"
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
        - name: productId
          in: query
          required: false
          schema:
            type: string
            description: "对我的产品里哪一个说的（必填）"
      responses:
        "204":
          description: "成功，没有内容"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/library/{companyId}":
    get:
      operationId: getLibraryCompany
      tags:
        - "客户库"
      summary: "看客户库里的一家"
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LibraryCompanyView"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
    patch:
      operationId: updateLibraryCompany
      tags:
        - "客户库"
      summary: "改跟进（阶段、标签、备注、负责人、重点客户；只改带了的；改阶段自动记一条跟进）"
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/LibraryUpdateBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LibraryCompanyView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/library/{companyId}/activities":
    get:
      operationId: listActivities
      tags:
        - "客户库"
      summary: "跟进记录（新的在前）"
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ActivityListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      operationId: addActivity
      tags:
        - "客户库"
      summary: "记一条跟进"
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ActivityCreateBody"
      responses:
        "201":
          description: "建好了"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ActivityView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/library/{companyId}/activities/{activityId}":
    delete:
      operationId: deleteActivity
      tags:
        - "客户库"
      summary: "删一条跟进（只能删自己记的；系统记的不能删）"
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
        - name: activityId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: "成功，没有内容"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/library/{companyId}/people":
    post:
      operationId: addManualPerson
      tags:
        - "客户库"
      summary: "手动记一个联系人（只在这个工作区）"
      parameters:
        - name: companyId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ManualPersonBody"
      responses:
        "201":
          description: "建好了"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LibraryPersonView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  /people:
    get:
      operationId: listPeople
      tags:
        - "客户库"
      summary: "联系人（付过钱查到的 + 手动记的；一页 50）"
      parameters:
        - name: q
          in: query
          required: false
          schema:
            type: string
        - name: companyId
          in: query
          required: false
          schema:
            type: string
        - name: role
          in: query
          required: false
          schema:
            type: string
            enum:
              - procurement
              - related
              - other
        - name: has
          in: query
          required: false
          schema:
            type: string
            enum:
              - email
              - mobile
        - name: page
          in: query
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PeopleListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/people/{personKey}":
    patch:
      operationId: updateManualPerson
      tags:
        - "客户库"
      summary: "改手动记的联系人"
      parameters:
        - name: personKey
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ManualPersonUpdateBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LibraryPersonView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
    delete:
      operationId: deleteManualPerson
      tags:
        - "客户库"
      summary: "删手动记的联系人"
      parameters:
        - name: personKey
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: "成功，没有内容"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /tasks:
    get:
      operationId: listTasks
      tags:
        - "待办"
      summary: "待办（status、when=overdue|today|week、assignee=me|<userId>、companyId）"
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
            enum:
              - open
              - done
        - name: when
          in: query
          required: false
          schema:
            type: string
            enum:
              - overdue
              - today
              - week
        - name: assignee
          in: query
          required: false
          schema:
            type: string
        - name: companyId
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      operationId: createTask
      tags:
        - "待办"
      summary: "建待办（不给负责人就是密钥的建立人）"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TaskCreateBody"
      responses:
        "201":
          description: "建好了"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/tasks/{taskId}":
    patch:
      operationId: updateTask
      tags:
        - "待办"
      summary: "改待办 / 打勾做完"
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TaskUpdateBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
    delete:
      operationId: deleteTask
      tags:
        - "待办"
      summary: "删待办"
      parameters:
        - name: taskId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: "成功，没有内容"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /products:
    get:
      operationId: listProducts
      tags:
        - "我的产品"
      summary: "我的产品（组织卖的东西，改过的在前）"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductListResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      operationId: createProduct
      tags:
        - "我的产品"
      summary: "加一个产品（组织内不重名，最多 50 个）"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductBody"
      responses:
        "201":
          description: "建好了"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "重名（duplicate_name，忽略大小写）或超过 50 个（too_many_products）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/products/{productId}":
    patch:
      operationId: updateProduct
      tags:
        - "我的产品"
      summary: "改产品（只改带了的；可空的文字字段传 null 清空，列表传 [] 清空）"
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductUpdateBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "重名（duplicate_name，忽略大小写）或超过 50 个（too_many_products）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
    delete:
      operationId: deleteProduct
      tags:
        - "我的产品"
      summary: "删产品"
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: "成功，没有内容"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /products/ensure:
    post:
      operationId: ensureProduct
      tags:
        - "我的产品"
      summary: "按名字取或建一个产品（同名的给已有的那个）"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductEnsureBody"
      responses:
        "200":
          description: "已有同名的"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductView"
        "201":
          description: "新建的"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "超过 50 个（too_many_products）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/products/{productId}/understand":
    post:
      operationId: understandProduct
      tags:
        - "我的产品"
      summary: "产品卡：把产品名理解成几种（免费），选一个确认"
      description: "同一个名字 24 小时内再理解给同一份。确认用 POST /products/{productId}/card { optionId }。"
      x-doou-costly: true
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductUnderstanding"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          description: "24 小时内理解太多次（too_many），或请求太频繁（rate_limited，带 Retry-After）"
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: "#/components/schemas/ApiError"
                  - $ref: "#/components/schemas/RateLimited"
        "502":
          description: "没理解出来（understand_failed），换个写法再试"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
  "/products/{productId}/card":
    post:
      operationId: setProductCard
      tags:
        - "我的产品"
      summary: "确认产品卡：选一个理解（optionId），或给改好的整张卡"
      description: "之后买家搜索、海关查询、国家进口榜单、电商卖家与广告主搜索、买家评论分析都按卡找。理解找不到了（过期、改过名）400 stale_option。"
      parameters:
        - name: productId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProductCardBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProductView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  /markets/quote:
    post:
      operationId: quoteMarket
      tags:
        - "国家进口榜单"
      summary: "国家进口榜单报价（免费）"
      description: "maxFen 是这份最多多少钱（分）。每月 1 份不收钱（maxFen 是 0）；30 天内同一品目 + 国家做过的不收钱（给了 hs 才认得出）。"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MarketStartBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MarketQuoteResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  /markets:
    post:
      operationId: startMarket
      tags:
        - "国家进口榜单"
      summary: "做一份国家进口榜单（每月 1 份不收钱；要收钱的先报价，带 maxFen 开始）"
      description: "30 天内同一产品 + 国家再做一份不占份数、不收钱。要收钱而没带 maxFen 或报价超过它：409 quote_changed（带现在的 maxFen），不开跑。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MarketStartBody"
      responses:
        "200":
          description: "回一句追问（needsClarification）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NeedsClarification"
        "202":
          description: "开跑了，轮询 GET /markets/{reportId}"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MarketStarted"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: "余额不够：差多少说清楚，没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InsufficientBalance"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "要收钱：没带 maxFen 或报价超过它（quote_changed，带 maxFen）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteChanged"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
    get:
      operationId: listMarkets
      tags:
        - "国家进口榜单"
      summary: "国家进口榜单列表（productId 只要为这个产品做的）"
      parameters:
        - name: productId
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MarketListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/markets/{reportId}":
    get:
      operationId: getMarket
      tags:
        - "国家进口榜单"
      summary: "一份国家进口榜单（运行中轮询它）"
      parameters:
        - name: reportId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MarketReportView"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /reviews/quote:
    post:
      operationId: quoteReviews
      tags:
        - "买家评论分析"
      summary: "买家评论分析报价（免费）"
      description: "maxFen 是这份最多多少钱（分）；7 天内本组织看过同一个的 reused 为 true、不收钱。"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReviewBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReviewQuoteResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  /reviews:
    post:
      operationId: startReviews
      tags:
        - "买家评论分析"
      summary: "买家评论分析：一个品类（或一个商品）的买家抱怨什么、夸什么、常问什么（花钱：最多报价的 maxFen，什么都没归纳出来不收）"
      description: "先报价，带上它的 maxFen 开始。国家没有亚马逊站 400 unsupported_country；productId 不是本组织的产品 400 unknown_product；商品链接认不出、看单个商品又开了 reddit 400 bad_request。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ReviewStartBody"
      responses:
        "200":
          description: "回一句追问（needsClarification）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NeedsClarification"
        "202":
          description: "开跑了，轮询 GET /reviews/{reportId}"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReviewStarted"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          description: "余额不够：差多少说清楚，没开跑、没扣钱"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InsufficientBalance"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "报价变了（quote_changed，带现在的 maxFen），或同组织别的工作区正在做同一个（busy）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QuoteChangedOrBusy"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
    get:
      operationId: listReviews
      tags:
        - "买家评论分析"
      summary: "买家评论分析记录（productId 只要为这个产品做的；不含商品与主题）"
      parameters:
        - name: productId
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReviewListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/reviews/{reportId}":
    get:
      operationId: getReviews
      tags:
        - "买家评论分析"
      summary: "一份买家评论分析（运行中轮询它）"
      parameters:
        - name: reportId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReviewReportView"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /reviews/countries:
    get:
      operationId: listReviewCountries
      tags:
        - "买家评论分析"
      summary: "买家评论分析能看的亚马逊站"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReviewCountriesResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /outreach/drafts:
    post:
      operationId: writeOutreach
      tags:
        - "写开发信"
      summary: "写开发信（免费；只写不发）：按这家的情况写第一次联系的信，带中文对照"
      description: "第一封给 companyId（to 不给挑最合适的收件人）；改写带上 previousId 与 action（another 换一封、shorter 短一点、formal 更正式）。公司或上一封看不到 404；productId 不是本组织的产品 400 unknown_product；to 不是这家能用的邮箱 400 unknown_recipient。信末的落款是真人的：这把密钥的人还没在工作台确认过落款时回 409 signature_needed（带预填的那份），到工作台写一封开发信时确认一次，之后这里也用它。模型暂时调不通回 503 writer_unavailable（不出信、不算进限量），稍后再试。发信在工作台上。"
      x-doou-costly: true
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OutreachDraftBody"
      responses:
        "201":
          description: "写好了"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OutreachDraftView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "还没确认落款（signature_needed）：去工作台写开发信时确认一次"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignatureNeeded"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          description: "这个组织 24 小时内写得太多，或这一封改得太多（too_many），或请求太频繁（rate_limited，带 Retry-After）"
          content:
            application/json:
              schema:
                anyOf:
                  - $ref: "#/components/schemas/TooMany"
                  - $ref: "#/components/schemas/RateLimited"
        "503":
          description: "现在写不了（writer_unavailable），稍后再试"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
  /watches:
    get:
      operationId: listWatches
      tags:
        - "客户动态"
      summary: "关注与订阅（关注的客户、同行，需求订阅，求购帖订阅；productId 只要为这个产品订阅的）"
      parameters:
        - name: productId
          in: query
          required: false
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WatchListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      operationId: createWatch
      tags:
        - "客户动态"
      summary: "关注一家客户、一个同行，或订阅（需求订阅、求购帖订阅：每周查一次，最多花 weeklyCapFen）"
      description: "有动静出一条动态（GET /signals）。同一目标已经关注过 200 回原来那个；超了上限 409 too_many_watches；客户不在客户库 404；productId 不是本组织的产品 400 unknown_product。"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WatchCreateBody"
      responses:
        "200":
          description: "已经关注过同一个"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WatchView"
        "201":
          description: "关注 / 订阅好了"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WatchView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "超了上限（too_many_watches）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/watches/{watchId}":
    patch:
      operationId: updateWatch
      tags:
        - "客户动态"
      summary: "暂停 / 恢复，改订阅的每周上限"
      parameters:
        - name: watchId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WatchUpdateBody"
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WatchView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
    delete:
      operationId: deleteWatch
      tags:
        - "客户动态"
      summary: "取消关注 / 订阅（连它的动态一起删）"
      parameters:
        - name: watchId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: "成功，没有内容"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /signals:
    get:
      operationId: listSignals
      tags:
        - "客户动态"
      summary: "客户动态（一页 50；unread、kind、watchId、companyId 筛）"
      parameters:
        - name: unread
          in: query
          required: false
          schema:
            type: boolean
        - name: kind
          in: query
          required: false
          schema:
            type: string
            enum:
              - new_shipment
              - volume_up
              - supplier_change
              - competitor_buyer_lost
              - competitor_new_buyer
              - reorder_due
              - hiring
              - new_category
              - new_buyers
              - ads_started
              - new_posts
              - notice
              - done
              - failed
        - name: watchId
          in: query
          required: false
          schema:
            type: string
        - name: companyId
          in: query
          required: false
          schema:
            type: string
        - name: page
          in: query
          required: false
          schema:
            type: integer
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignalListResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /signals/read:
    post:
      operationId: markSignalsRead
      tags:
        - "客户动态"
      summary: "标已读（ids，或 all: true）"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignalReadBody"
      responses:
        "204":
          description: "成功，没有内容"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/signals/{signalId}/task":
    post:
      operationId: signalToTask
      tags:
        - "客户动态"
      summary: "动态转待办（挂这家公司，标题是建议做的事；转过的回原来那条）"
      parameters:
        - name: signalId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignalTaskBody"
      responses:
        "200":
          description: "这条转过了"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskView"
        "201":
          description: "建好了"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskView"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: "公司不在客户库（not_in_library）或认不出是哪家（no_company）"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiError"
        "413":
          $ref: "#/components/responses/TooLarge"
        "429":
          $ref: "#/components/responses/RateLimited"
  "/posts/{runId}":
    get:
      operationId: getPosts
      tags:
        - "客户动态"
      summary: "一次求购信息：求购帖订阅查到的新帖子（动态 new_posts 的 runId）"
      parameters:
        - name: runId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: "成功"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PostsRunView"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/PlanRequired"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: "doou_live_ 开头的 API 密钥"
  responses:
    BadRequest:
      description: "参数不对"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
    Unauthorized:
      description: "没带密钥、密钥不对、已吊销（建密钥的人离开组织时自动吊销）（error: invalid_api_key）"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
    PlanRequired:
      description: "这项要开通会员才能用（error: plan_required，tier 是最低哪一档有）。API 与 MCP 本身所有账户都能用（N-123）"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/PlanRequired"
    NotFound:
      description: "没有这个（不在密钥的工作区 / 组织里，一律 404）"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
    TooLarge:
      description: "请求体太大"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiError"
    RateLimited:
      description: "请求太频繁（带 Retry-After 头）"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/RateLimited"
  schemas:
    ActivityCreateBody:
      type: object
      properties:
        kind:
          type: string
          enum:
            - note
            - email
            - call
            - meeting
            - whatsapp
            - sample
            - quote
            - order
        body:
          type: string
        personKey:
          type: string
        occurredAt:
          type: string
          description: "ISO 8601 时间"
      required:
        - kind
        - body
      additionalProperties: false
    ActivityKind:
      type: string
      enum:
        - note
        - email
        - call
        - meeting
        - whatsapp
        - sample
        - quote
        - order
        - stage
        - brief
        - contacts
        - verify
        - bounce
        - signal
        - task_added
        - task_done
        - owner
        - starred
        - added
        - feedback
    ActivityListResponse:
      type: object
      properties:
        activities:
          type: array
          items:
            $ref: "#/components/schemas/ActivityView"
      required:
        - activities
      additionalProperties: false
    ActivityView:
      type: object
      properties:
        id:
          type: string
        companyId:
          type: string
        kind:
          $ref: "#/components/schemas/ActivityKind"
        body:
          type: string
        personKey:
          type:
            - string
            - "null"
        personName:
          type:
            - string
            - "null"
        occurredAt:
          type: string
          description: "ISO 8601 时间"
        email:
          anyOf:
            - type: object
              properties:
                direction:
                  type: string
                  enum:
                    - in
                    - out
                subject:
                  type: string
                counterpart:
                  type: string
                snippet:
                  type: string
              required:
                - direction
                - subject
                - counterpart
                - snippet
              additionalProperties: false
            - type: "null"
        quoteId:
          type:
            - string
            - "null"
        refKind:
          anyOf:
            - type: string
              enum:
                - brief
                - contacts
                - verify
                - signal
                - task
                - quote
                - search
                - similar
                - sellers
                - advertisers
                - merchants
                - trade
                - manual
                - import
                - card
                - posts
                - team
                - competitor
            - type: "null"
          description: "系统记的那条点回哪里（背调、找联系人的运行、验证批次、信号、待办、报价单；进库那条是来源）"
        refId:
          type:
            - string
            - "null"
        createdBy:
          anyOf:
            - type: object
              properties:
                userId:
                  type: string
                email:
                  type:
                    - string
                    - "null"
              required:
                - userId
                - email
              additionalProperties: false
            - type: "null"
        createdAt:
          type: string
          description: "ISO 8601 时间"
      required:
        - id
        - companyId
        - kind
        - body
        - personKey
        - personName
        - occurredAt
        - createdBy
        - createdAt
      additionalProperties: false
    AdvertiserCountriesResponse:
      type: object
      properties:
        countries:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              name:
                type: string
              platforms:
                type: array
                items:
                  type: string
                  enum:
                    - meta
                    - google
              metaHistory:
                type: boolean
                description: "选 90 天时 Facebook 也看得到最近停了的广告"
            required:
              - code
              - name
              - platforms
              - metaHistory
            additionalProperties: false
      required:
        - countries
      additionalProperties: false
    AdvertisersBody:
      type: object
      properties:
        product:
          type: string
          description: "产品，中文或英文（≤ 80 字）；或给 productId"
        productId:
          type: string
          description: "我的产品里的一个（有确认过的产品卡就按卡）"
        includeChina:
          type: boolean
          description: "默认 false：注册在中国大陆 / 香港的去掉（多半是同行）"
        quantity:
          type: integer
        maxFen:
          type: integer
          description: "用户认的上限（分，报价的 maxFen）：现在的报价超过它 409 quote_changed，不开跑"
        country:
          type: string
          description: "两位国家代码（GET /advertisers/countries 里的）"
        platforms:
          type: array
          items:
            type: string
            enum:
              - meta
              - google
          description: "meta Facebook 与 Instagram、google 谷歌；不给就是这个国家能查的全部"
        days:
          anyOf:
            - type: number
              const: 30
            - type: number
              const: 90
          description: "看最近多少天在投的，默认 30"
      required:
        - country
      additionalProperties: false
    ApiError:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
      required:
        - error
        - message
      additionalProperties: false
    BuyingPostView:
      type: object
      properties:
        postId:
          type: string
        library:
          type: object
          properties:
            inLibrary:
              type: boolean
            stage:
              anyOf:
                - type: string
                  enum:
                    - new
                    - contacted
                    - replied
                    - sampling
                    - won
                    - dropped
                - type: "null"
            starred:
              type: boolean
            ownerId:
              type:
                - string
                - "null"
            notRelevant:
              type: array
              items:
                type: object
                properties:
                  productId:
                    type: string
                  reason:
                    type: string
                    enum:
                      - not_this_product
                      - competitor
                      - forwarder
                      - too_small
                      - other
                  at:
                    type: string
                    description: "ISO 8601 时间"
                required:
                  - productId
                  - reason
                  - at
                additionalProperties: false
              description: "本组织说过这家「不相关」的产品（每个产品最近一次，新的在前）；没说过不带"
          required:
            - inLibrary
            - stage
            - starred
            - ownerId
          additionalProperties: false
        author:
          type: string
        authorTitle:
          type:
            - string
            - "null"
        authorUrl:
          type:
            - string
            - "null"
        company:
          type:
            - string
            - "null"
        companyDomain:
          type:
            - string
            - "null"
        companyId:
          type:
            - string
            - "null"
        inLibrary:
          type: boolean
        country:
          type:
            - string
            - "null"
        text:
          type: string
        url:
          type: string
        postedAt:
          anyOf:
            - type: string
              description: "ISO 8601 时间"
            - type: "null"
        why:
          type: string
          description: "为什么算求购"
      required:
        - postId
        - author
        - authorTitle
        - authorUrl
        - company
        - companyDomain
        - companyId
        - inLibrary
        - country
        - text
        - url
        - postedAt
        - why
      additionalProperties: false
    ChannelView:
      type: object
      properties:
        type:
          type: string
          enum:
            - phone
            - mobile
            - email
            - website
            - contact_page
            - social
        value:
          type: string
        platform:
          type:
            - string
            - "null"
        source:
          $ref: "#/components/schemas/SourceView"
        originUrl:
          type:
            - string
            - "null"
        observedAt:
          type: string
          description: "ISO 8601 时间"
        emailStatus:
          anyOf:
            - type: string
              enum:
                - valid
                - invalid
                - accept_all
                - unknown
            - type: "null"
        roleAddress:
          type: boolean
        freeMailbox:
          type: boolean
      required:
        - type
        - value
        - platform
        - source
        - originUrl
        - observedAt
        - emailStatus
        - roleAddress
        - freeMailbox
      additionalProperties: false
    CompanyContactsView:
      type: object
      properties:
        companyId:
          type: string
        name:
          type: string
        domain:
          type:
            - string
            - "null"
        channels:
          type: array
          items:
            $ref: "#/components/schemas/ChannelView"
        channelsCheckedAt:
          anyOf:
            - type: string
              description: "ISO 8601 时间"
            - type: "null"
        people:
          type: array
          items:
            $ref: "#/components/schemas/PersonView"
      required:
        - companyId
        - name
        - domain
        - channels
        - channelsCheckedAt
        - people
      additionalProperties: false
    CompanyFeedbackBody:
      type: object
      properties:
        productId:
          type: string
          description: "对我的产品里哪一个说的"
        reason:
          type: string
          enum:
            - not_this_product
            - competitor
            - forwarder
            - too_small
            - other
          description: "not_this_product 卖的不是这个（这家卖的东西记成产品卡的排除词）、competitor 同行、forwarder 货代、too_small 太小、other 别的"
      required:
        - productId
        - reason
      additionalProperties: false
    CompanyFoundView:
      type: object
      properties:
        kind:
          type: string
          enum:
            - search
            - similar
            - sellers
            - advertisers
            - merchants
            - trade
            - competitor
            - manual
            - import
            - card
            - posts
            - team
        runId:
          type:
            - string
            - "null"
        refId:
          type:
            - string
            - "null"
        label:
          type: string
        productRef:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
        at:
          type: string
          description: "ISO 8601 时间"
        reasons:
          type: array
          items:
            type: string
      required:
        - kind
        - runId
        - refId
        - label
        - productRef
        - at
        - reasons
      additionalProperties: false
    CompanyResolveBody:
      type: object
      properties:
        input:
          type: string
          description: "官网、网址、邮箱，或公司名（英文、拼错的、中文、拼音）"
        name:
          type: string
          description: "公司名线索（官网和名字一起给时：官网读不出名字就按它找）"
        country:
          type: string
          description: "两位国家代码；不给按官网读出的国家"
        for:
          type: string
          enum:
            - imports
            - competitor
            - similar
          description: "similar 只认官网是哪家（查一家公司、相似客户的老客户）；imports 再找它在海关里的进口商写法（企业进口记录）；competitor 找中国出口商的写法（竞品客户查询）"
        productId:
          type: string
          description: "competitor：按我的产品里这个的 HS 与英文叫法数票数"
        step:
          type: string
          enum:
            - site
          description: "只走第一步：打开官网读出公司全称、国家、地址（存下，接着认人时直接用），不找海关写法、不数票数"
      required:
        - input
        - for
      additionalProperties: false
    CompanyResolveView:
      type: object
      properties:
        inputKind:
          type: string
          enum:
            - website
            - email
            - free_email
            - platform
            - name
            - chinese_name
        company:
          anyOf:
            - type: object
              properties:
                companyId:
                  type:
                    - string
                    - "null"
                domain:
                  type: string
                name:
                  type:
                    - string
                    - "null"
                country:
                  type:
                    - string
                    - "null"
                city:
                  type:
                    - string
                    - "null"
                address:
                  type:
                    - string
                    - "null"
              required:
                - companyId
                - domain
                - name
                - country
                - city
                - address
              additionalProperties: false
            - type: "null"
          description: "官网或邮箱认出的那家（库里有就带 companyId）；只给了名字、免费邮箱是 null"
        candidates:
          type: array
          items:
            type: object
            properties:
              customsName:
                type: string
              country:
                type:
                  - string
                  - "null"
              shipments12m:
                anyOf:
                  - type: integer
                  - type: "null"
              exact:
                type: boolean
                description: "true：核实过的全部货物票数（写法归一后相等的才算）；false：免费按名字粗数的，含名字相近的别家"
              match:
                type: string
                enum:
                  - verified
                  - confirmed
                  - address
                  - name
              evidence:
                type: string
            required:
              - customsName
              - country
              - shipments12m
              - exact
              - match
              - evidence
            additionalProperties: false
          description: "海关里的写法（执行企业进口记录时作为 customsName 带上）"
        status:
          type: string
          enum:
            - certain
            - choose
            - none
            - failed
          description: "certain 认定了（chosen）；choose 要点一个；none 海关里没有它（看 note）；failed 海关记录这次没查成（上游出错，不是没有；稍后再试）"
        chosen:
          type:
            - string
            - "null"
        note:
          type:
            - string
            - "null"
      required:
        - inputKind
        - company
        - candidates
        - status
        - chosen
        - note
      additionalProperties: false
    CompanyRowView:
      type: object
      properties:
        position:
          type: integer
        companyId:
          type: string
        name:
          type: string
        domain:
          type:
            - string
            - "null"
        location:
          type:
            - string
            - "null"
        segment:
          type: string
        segmentKey:
          type:
            - string
            - "null"
        band:
          type: string
          enum:
            - premium
            - related
            - potential
        isForwarder:
          type: boolean
        size:
          type:
            - string
            - "null"
        industry:
          type:
            - string
            - "null"
        description:
          type:
            - string
            - "null"
        reasons:
          type: array
          items:
            type: string
        chargedFen:
          anyOf:
            - type: integer
            - type: "null"
        free:
          type:
            - string
            - "null"
        difficulty:
          anyOf:
            - type: string
              enum:
                - easy
                - hard
            - type: "null"
        seenOn:
          type: array
          items:
            $ref: "#/components/schemas/SeenOnView"
          description: "电商卖家搜索、广告主搜索才有：在哪看到它在卖 / 在投广告，带链接"
        library:
          type: object
          properties:
            inLibrary:
              type: boolean
            stage:
              anyOf:
                - type: string
                  enum:
                    - new
                    - contacted
                    - replied
                    - sampling
                    - won
                    - dropped
                - type: "null"
            starred:
              type: boolean
            ownerId:
              type:
                - string
                - "null"
            notRelevant:
              type: array
              items:
                type: object
                properties:
                  productId:
                    type: string
                  reason:
                    type: string
                    enum:
                      - not_this_product
                      - competitor
                      - forwarder
                      - too_small
                      - other
                  at:
                    type: string
                    description: "ISO 8601 时间"
                required:
                  - productId
                  - reason
                  - at
                additionalProperties: false
              description: "本组织说过这家「不相关」的产品（每个产品最近一次，新的在前）；没说过不带"
          required:
            - inLibrary
            - stage
            - starred
            - ownerId
          additionalProperties: false
        links:
          type: array
          items:
            $ref: "#/components/schemas/ContactLinkView"
          description: "这家已经拿到的联系方式链接（不另收钱）：官网 → 公司主页 → 电话 → 邮箱 → 店铺 → 广告，每样一个，最多 8 个"
      required:
        - position
        - companyId
        - name
        - domain
        - location
        - segment
        - segmentKey
        - band
        - isForwarder
        - size
        - industry
        - description
        - reasons
        - chargedFen
        - free
        - difficulty
        - links
      additionalProperties: false
    CompanyView:
      type: object
      properties:
        companyId:
          type: string
        name:
          type: string
        domain:
          type:
            - string
            - "null"
        location:
          type:
            - string
            - "null"
        country:
          type:
            - string
            - "null"
        segment:
          type:
            - string
            - "null"
        band:
          anyOf:
            - type: string
              enum:
                - premium
                - related
                - potential
            - type: "null"
        isForwarder:
          type: boolean
        description:
          type:
            - string
            - "null"
        industry:
          type:
            - string
            - "null"
        size:
          type:
            - string
            - "null"
        library:
          type: object
          properties:
            inLibrary:
              type: boolean
            stage:
              anyOf:
                - type: string
                  enum:
                    - new
                    - contacted
                    - replied
                    - sampling
                    - won
                    - dropped
                - type: "null"
            starred:
              type: boolean
            ownerId:
              type:
                - string
                - "null"
            notRelevant:
              type: array
              items:
                type: object
                properties:
                  productId:
                    type: string
                  reason:
                    type: string
                    enum:
                      - not_this_product
                      - competitor
                      - forwarder
                      - too_small
                      - other
                  at:
                    type: string
                    description: "ISO 8601 时间"
                required:
                  - productId
                  - reason
                  - at
                additionalProperties: false
              description: "本组织说过这家「不相关」的产品（每个产品最近一次，新的在前）；没说过不带"
          required:
            - inLibrary
            - stage
            - starred
            - ownerId
          additionalProperties: false
        libraryEntry:
          anyOf:
            - $ref: "#/components/schemas/LibraryCompanyView"
            - type: "null"
          description: "在客户库里时是那一行；不在库是 null"
        canAct:
          type: boolean
          description: "能不能直接找联系人、跟进（= 在客户库里）"
        seenOn:
          type: array
          items:
            $ref: "#/components/schemas/SeenOnView"
        reasons:
          type: array
          items:
            type: string
        foundIn:
          type: array
          items:
            $ref: "#/components/schemas/CompanyFoundView"
          description: "这个工作区里哪几次找到过它，新的在前"
        trade:
          anyOf:
            - type: object
              properties:
                shipments12m:
                  anyOf:
                    - type: integer
                    - type: "null"
                  description: "近 12 个月全部货物的票数（海关写法归一后相等的才算）；只按产品查过是 null"
                lastImportAt:
                  anyOf:
                    - type: string
                      description: "YYYY-MM-DD（北京时间）"
                    - type: "null"
                product:
                  anyOf:
                    - type: object
                      properties:
                        label:
                          type: string
                        shipments12m:
                          anyOf:
                            - type: integer
                            - type: "null"
                      required:
                        - label
                        - shipments12m
                      additionalProperties: false
                    - type: "null"
                  description: "按产品查过的那一部分：其中这个产品多少票"
              required:
                - shipments12m
                - lastImportAt
                - product
              additionalProperties: false
            - type: "null"
        productRefs:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
            required:
              - id
              - name
            additionalProperties: false
        links:
          type: array
          items:
            $ref: "#/components/schemas/ContactLinkView"
      required:
        - companyId
        - name
        - domain
        - location
        - country
        - segment
        - band
        - isForwarder
        - description
        - industry
        - size
        - library
        - libraryEntry
        - canAct
        - seenOn
        - reasons
        - foundIn
        - trade
        - productRefs
        - links
      additionalProperties: false
    ContactLinkView:
      type: object
      properties:
        kind:
          type: string
          enum:
            - website
            - linkedin
            - facebook
            - instagram
            - x
            - youtube
            - tiktok
            - xing
            - pinterest
            - phone
            - email
            - store
            - ads
          description: "website 官网；linkedin … pinterest 公司在这个平台上的主页；phone 电话；email 邮箱；store 电商店铺页；ads 广告库页"
        url:
          type: string
          description: "网页 https://…；电话 tel:…；邮箱 mailto:…"
        label:
          type: string
          description: "平台或渠道的名字（官网、LinkedIn、电话、亚马逊……）"
      required:
        - kind
        - url
        - label
      additionalProperties: false
    ContactQuoteBody:
      type: object
      properties:
        people:
          type: integer
        mobile:
          type: boolean
      additionalProperties: false
    ContactQuoted:
      type: object
      properties:
        candidates:
          type: array
          items:
            $ref: "#/components/schemas/PersonView"
        chosen:
          type: array
          items:
            type: string
        perPersonFen:
          type: object
          properties:
            email:
              type: integer
            mobile:
              anyOf:
                - type: integer
                - type: "null"
          required:
            - email
            - mobile
          additionalProperties: false
        fixedFen:
          type: integer
        maxFen:
          type: integer
        alreadyPaid:
          type: array
          items:
            type: string
        wallet:
          $ref: "#/components/schemas/WalletView"
      required:
        - candidates
        - chosen
        - perPersonFen
        - fixedFen
        - maxFen
        - alreadyPaid
        - wallet
      additionalProperties: false
    ContactQuoteResponse:
      anyOf:
        - type: object
          properties:
            error:
              type: string
              const: no_domain
            message:
              type: string
          required:
            - error
            - message
          additionalProperties: false
        - $ref: "#/components/schemas/ContactQuoted"
    ContactRunBody:
      type: object
      properties:
        personKeys:
          type: array
          items:
            type: string
            description: "联系人编号（p_ 开头；手动记的人 manual: 开头）"
        mobile:
          type: boolean
      required:
        - personKeys
      additionalProperties: false
    ContactRunResult:
      type: object
      properties:
        runId:
          type: string
        contacts:
          $ref: "#/components/schemas/CompanyContactsView"
        results:
          type: array
          items:
            type: object
            properties:
              key:
                type: string
                description: "联系人编号（p_ 开头；手动记的人 manual: 开头）"
              email:
                anyOf:
                  - type: string
                    enum:
                      - found
                      - had
                      - not_found
                      - failed
                  - type: "null"
              mobile:
                anyOf:
                  - type: string
                    enum:
                      - found
                      - had
                      - not_found
                      - failed
                  - type: "null"
              chargedFen:
                anyOf:
                  - type: integer
                  - type: "null"
            required:
              - key
              - email
              - mobile
              - chargedFen
            additionalProperties: false
        chargedFen:
          anyOf:
            - type: integer
            - type: "null"
      required:
        - runId
        - contacts
        - results
        - chargedFen
      additionalProperties: false
    InsufficientBalance:
      type: object
      properties:
        error:
          type: string
          const: insufficient_balance
        availableFen:
          type: integer
        neededFen:
          type: integer
      required:
        - error
        - availableFen
        - neededFen
      additionalProperties: false
    JobKind:
      type: string
      enum:
        - market
        - reviews
        - brief
        - competitor
        - search
        - verify
        - cards
    LedgerEntryView:
      type: object
      properties:
        id:
          type: string
        at:
          type: string
          description: "ISO 8601 时间"
        kind:
          type: string
          enum:
            - topup
            - grant
            - refund
            - charge
            - topup_refund
        amountFen:
          type: integer
        balanceAfterFen:
          type: integer
        runId:
          type:
            - string
            - "null"
        run:
          anyOf:
            - type: object
              properties:
                kind:
                  type: string
                  enum:
                    - search
                    - trade_company
                    - trade_product
                    - trade_competitor
                    - enrich_contacts
                    - verify_emails
                    - intel_brief
                    - intel_competitor
                    - reviews
                    - linkedin_posts
                    - market_report
                label:
                  type: string
                workspaceId:
                  type: string
                refId:
                  type:
                    - string
                    - "null"
              required:
                - kind
                - label
                - workspaceId
              additionalProperties: false
            - type: "null"
        companyName:
          type:
            - string
            - "null"
        free:
          type:
            - string
            - "null"
        contact:
          anyOf:
            - type: object
              properties:
                name:
                  type: string
                what:
                  type: string
                  enum:
                    - email
                    - mobile
              required:
                - name
                - what
              additionalProperties: false
            - type: "null"
        difficulty:
          anyOf:
            - type: string
              enum:
                - easy
                - hard
            - type: "null"
        label:
          type:
            - string
            - "null"
        companyId:
          type:
            - string
            - "null"
        fixedFen:
          anyOf:
            - type: integer
            - type: "null"
        costFen:
          anyOf:
            - type: integer
            - type: "null"
      required:
        - id
        - at
        - kind
        - amountFen
        - balanceAfterFen
        - runId
        - run
        - companyName
        - free
        - contact
        - difficulty
        - fixedFen
        - costFen
      additionalProperties: false
    LedgerResponse:
      type: object
      properties:
        wallet:
          $ref: "#/components/schemas/WalletView"
        entries:
          type: array
          items:
            $ref: "#/components/schemas/LedgerEntryView"
      required:
        - wallet
        - entries
      additionalProperties: false
    LibraryAddBody:
      anyOf:
        - type: object
          properties:
            companyId:
              type: string
            tradeRunId:
              type: string
          required:
            - companyId
            - tradeRunId
          additionalProperties: false
        - type: object
          properties:
            companyId:
              type: string
            competitorProfileId:
              type: string
          required:
            - companyId
            - competitorProfileId
          additionalProperties: false
        - type: object
          properties:
            companyId:
              type: string
            postsRunId:
              type: string
          required:
            - companyId
            - postsRunId
          additionalProperties: false
        - type: object
          properties:
            companyId:
              type: string
            fromWorkspace:
              type: boolean
              const: true
          required:
            - companyId
            - fromWorkspace
          additionalProperties: false
          description: "也放进本工作区：同组织别的工作区已有的公司（免费）"
        - type: object
          properties:
            companyId:
              type: string
          required:
            - companyId
          additionalProperties: false
          description: "只给公司：按这个工作区最近一次见到它的地方（海关查询结果里的公司、竞品画像、求购信息）记来源；哪都没见过 404"
        - type: object
          properties:
            manual:
              type: object
              properties:
                website:
                  type: string
                name:
                  type: string
                country:
                  type: string
                stage:
                  $ref: "#/components/schemas/LibraryStage"
                tags:
                  type: array
                  items:
                    type: string
                note:
                  type: string
              required:
                - website
              additionalProperties: false
          required:
            - manual
          additionalProperties: false
    LibraryCompanyView:
      type: object
      properties:
        companyId:
          type: string
        name:
          type: string
        domain:
          type:
            - string
            - "null"
        location:
          type:
            - string
            - "null"
        country:
          type:
            - string
            - "null"
        segment:
          type:
            - string
            - "null"
        band:
          anyOf:
            - type: string
              enum:
                - premium
                - related
                - potential
            - type: "null"
        isForwarder:
          type: boolean
        origin:
          type: object
          properties:
            kind:
              type: string
              enum:
                - search
                - similar
                - sellers
                - advertisers
                - merchants
                - trade
                - competitor
                - manual
                - import
                - card
                - posts
                - team
            runId:
              type:
                - string
                - "null"
            label:
              type: string
            at:
              type: string
              description: "ISO 8601 时间"
            refId:
              description: "点了去哪：competitor（从竞品画像加的）是画像 id，扫名片、表格导入是那一批，team 是从哪个工作区放进来的；别的是 null"
              type:
                - string
                - "null"
          required:
            - kind
            - runId
            - label
            - at
          additionalProperties: false
        stage:
          $ref: "#/components/schemas/LibraryStage"
        starred:
          type: boolean
        products:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              name:
                type: string
            required:
              - id
              - name
            additionalProperties: false
        origins:
          type: array
          items:
            type: object
            properties:
              kind:
                type: string
                enum:
                  - search
                  - similar
                  - sellers
                  - advertisers
                  - merchants
                  - trade
                  - competitor
                  - manual
                  - import
                  - card
                  - posts
                  - team
              runId:
                type:
                  - string
                  - "null"
              productRef:
                anyOf:
                  - type: object
                    properties:
                      id:
                        type: string
                      name:
                        type: string
                    required:
                      - id
                      - name
                    additionalProperties: false
                  - type: "null"
              at:
                type: string
                description: "ISO 8601 时间"
              refId:
                type:
                  - string
                  - "null"
            required:
              - kind
              - runId
              - productRef
              - at
            additionalProperties: false
        tags:
          type: array
          items:
            type: string
        note:
          type:
            - string
            - "null"
        owner:
          anyOf:
            - type: object
              properties:
                userId:
                  type: string
                email:
                  type:
                    - string
                    - "null"
              required:
                - userId
                - email
              additionalProperties: false
            - type: "null"
        nextTask:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                title:
                  type: string
                dueOn:
                  type: string
                  description: "YYYY-MM-DD（北京时间）"
              required:
                - id
                - title
                - dueOn
              additionalProperties: false
            - type: "null"
        lastActivityAt:
          anyOf:
            - type: string
              description: "ISO 8601 时间"
            - type: "null"
        channelCount:
          type: integer
        peopleCount:
          type: integer
        updatedAt:
          type: string
          description: "ISO 8601 时间"
        notRelevant:
          type: array
          items:
            type: object
            properties:
              productId:
                type: string
              reason:
                type: string
                enum:
                  - not_this_product
                  - competitor
                  - forwarder
                  - too_small
                  - other
              at:
                type: string
                description: "ISO 8601 时间"
            required:
              - productId
              - reason
              - at
            additionalProperties: false
          description: "本组织说过这家「不相关」的产品（每个产品最近一次，新的在前）；没说过不带"
      required:
        - companyId
        - name
        - domain
        - location
        - country
        - segment
        - band
        - isForwarder
        - origin
        - stage
        - tags
        - note
        - owner
        - nextTask
        - lastActivityAt
        - channelCount
        - peopleCount
        - updatedAt
      additionalProperties: false
    LibraryImportResponse:
      type: object
      properties:
        dryRun:
          type: boolean
        columns:
          type: array
          items:
            type: object
            properties:
              header:
                type: string
              field:
                type:
                  - string
                  - "null"
            required:
              - header
              - field
            additionalProperties: false
        added:
          type: integer
        existing:
          type: integer
        people:
          type: integer
        skipped:
          type: array
          items:
            type: object
            properties:
              row:
                type: integer
              reason:
                type: string
            required:
              - row
              - reason
            additionalProperties: false
        batchId:
          type: string
          description: "这一批（不是预览才有）；这次加进来的公司 origin.refId 就是它"
      required:
        - dryRun
        - columns
        - added
        - existing
        - people
        - skipped
      additionalProperties: false
    LibraryListResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/LibraryCompanyView"
        total:
          type: integer
        page:
          type: integer
        perPage:
          type: integer
        facets:
          type: object
          properties:
            countries:
              type: array
              items:
                type: object
                properties:
                  code:
                    type: string
                  count:
                    type: integer
                required:
                  - code
                  - count
                additionalProperties: false
            tags:
              type: array
              items:
                type: object
                properties:
                  tag:
                    type: string
                  count:
                    type: integer
                required:
                  - tag
                  - count
                additionalProperties: false
            stages:
              type: object
              propertyNames:
                $ref: "#/components/schemas/LibraryStage"
              additionalProperties:
                type: integer
              required:
                - new
                - contacted
                - replied
                - sampling
                - won
                - dropped
            due:
              type: integer
            starred:
              type: integer
          required:
            - countries
            - tags
            - stages
            - due
          additionalProperties: false
      required:
        - items
        - total
        - page
        - perPage
        - facets
      additionalProperties: false
    LibraryPersonView:
      type: object
      properties:
        key:
          type: string
          description: "联系人编号（p_ 开头；手动记的人 manual: 开头）"
        source:
          type: string
          enum:
            - revealed
            - manual
        companyId:
          type: string
        companyName:
          type: string
        companyDomain:
          type:
            - string
            - "null"
        name:
          type: string
        title:
          type:
            - string
            - "null"
        roleRank:
          type: string
          enum:
            - procurement
            - related
            - other
        email:
          anyOf:
            - $ref: "#/components/schemas/ChannelView"
            - type: "null"
        mobile:
          anyOf:
            - $ref: "#/components/schemas/ChannelView"
            - type: "null"
        linkedinUrl:
          type:
            - string
            - "null"
        revealedAt:
          type: string
          description: "ISO 8601 时间"
        via:
          anyOf:
            - type: string
              enum:
                - manual
                - card
                - import
                - mail
            - type: "null"
          description: "手动记的人怎么来的：手记、扫名片、表格导入、邮件同步"
        bounced:
          type: boolean
          description: "这个人现在的邮箱报过退信"
        verdict:
          anyOf:
            - type: string
              enum:
                - valid
                - invalid
                - accept_all
                - unknown
            - type: "null"
          description: "这个邮箱最近一次验证的结论"
      required:
        - key
        - source
        - companyId
        - companyName
        - companyDomain
        - name
        - title
        - roleRank
        - email
        - mobile
        - linkedinUrl
        - revealedAt
      additionalProperties: false
    LibraryStage:
      type: string
      enum:
        - new
        - contacted
        - replied
        - sampling
        - won
        - dropped
    LibraryStatsResponse:
      type: object
      properties:
        total:
          type: integer
        stages:
          type: object
          propertyNames:
            $ref: "#/components/schemas/LibraryStage"
          additionalProperties:
            type: integer
          required:
            - new
            - contacted
            - replied
            - sampling
            - won
            - dropped
        bySource:
          type: array
          items:
            type: object
            properties:
              kind:
                type: string
                enum:
                  - search
                  - similar
                  - sellers
                  - advertisers
                  - merchants
                  - trade
                  - competitor
                  - manual
                  - import
                  - card
                  - posts
                  - team
              count:
                type: integer
              engaged:
                type: integer
            required:
              - kind
              - count
              - engaged
            additionalProperties: false
        weekly:
          type: array
          items:
            type: object
            properties:
              weekStart:
                type: string
                description: "YYYY-MM-DD（北京时间）"
              added:
                type: integer
              activities:
                type: integer
              won:
                type: integer
            required:
              - weekStart
              - added
              - activities
              - won
            additionalProperties: false
        byOwner:
          type: array
          items:
            type: object
            properties:
              userId:
                type:
                  - string
                  - "null"
              email:
                type:
                  - string
                  - "null"
              count:
                type: integer
              activitiesThisWeek:
                type: integer
            required:
              - userId
              - email
              - count
              - activitiesThisWeek
            additionalProperties: false
      required:
        - total
        - stages
        - bySource
        - weekly
        - byOwner
      additionalProperties: false
    LibraryUpdateBody:
      type: object
      properties:
        stage:
          $ref: "#/components/schemas/LibraryStage"
        tags:
          type: array
          items:
            type: string
        note:
          type:
            - string
            - "null"
        ownerId:
          type:
            - string
            - "null"
        starred:
          type: boolean
      additionalProperties: false
    ManualPersonBody:
      type: object
      properties:
        name:
          type: string
        title:
          type: string
        email:
          type: string
        phone:
          type: string
        linkedinUrl:
          type: string
      required:
        - name
      additionalProperties: false
    ManualPersonUpdateBody:
      type: object
      properties:
        name:
          type: string
        title:
          type:
            - string
            - "null"
        email:
          type:
            - string
            - "null"
        phone:
          type:
            - string
            - "null"
        linkedinUrl:
          type:
            - string
            - "null"
      additionalProperties: false
    MarketCell:
      type: string
      enum:
        - rivals
        - seasonality
        - tariff
        - buyers
      description: "运行中还在查的格子：竞争国、季节性、关税、买家结构"
    MarketCountryView:
      type: object
      properties:
        country:
          type: string
        rank:
          type: integer
        score:
          type: number
        verdict:
          type: string
        sizeRank:
          anyOf:
            - type: integer
            - type: "null"
        growth:
          type: object
          properties:
            yoy:
              type:
                - number
                - "null"
            cagr5:
              type:
                - number
                - "null"
          required:
            - yoy
            - cagr5
          additionalProperties: false
        china:
          type: object
          properties:
            share:
              type:
                - number
                - "null"
            change3y:
              type:
                - number
                - "null"
          required:
            - share
            - change3y
          additionalProperties: false
        rivals:
          type: array
          items:
            type: object
            properties:
              country:
                type: string
              share:
                type: number
              change3y:
                type:
                  - number
                  - "null"
            required:
              - country
              - share
              - change3y
            additionalProperties: false
        seasonality:
          anyOf:
            - type: object
              properties:
                peakMonths:
                  type: array
                  items:
                    type: integer
                index:
                  type: array
                  items:
                    type: object
                    properties:
                      month:
                        type: integer
                      value:
                        type: number
                    required:
                      - month
                      - value
                    additionalProperties: false
              required:
                - peakMonths
                - index
              additionalProperties: false
            - type: "null"
        tariff:
          anyOf:
            - type: object
              properties:
                onChina:
                  type:
                    - number
                    - "null"
                mfn:
                  type:
                    - number
                    - "null"
                remedies:
                  type: array
                  items:
                    type: string
                source:
                  $ref: "#/components/schemas/SourceView"
              required:
                - onChina
                - mfn
                - remedies
                - source
              additionalProperties: false
            - type: "null"
        tariffFailed:
          description: "关税这次没查到（关税税率表超时或出错，不是没有关税）；没出错不给"
          type: boolean
        buyers:
          anyOf:
            - type: object
              properties:
                importers:
                  type: integer
                top5Share:
                  type:
                    - number
                    - "null"
              required:
                - importers
                - top5Share
              additionalProperties: false
            - type: "null"
        buyersFailed:
          description: "买家结构这次没查成（上游出错，不是这个国家没有进口商数据）；没出错不给"
          type: boolean
        marks:
          type: array
          items:
            $ref: "#/components/schemas/MarketMark"
        reporter:
          type: string
        asOf:
          type: string
        pending:
          description: "运行中这一行还在查的格子（格子先是空的），查到一格去掉一格；这一行都查到了、做完的报告不给。运行中的名次、分数、结论是临时的"
          type: array
          items:
            $ref: "#/components/schemas/MarketCell"
      required:
        - country
        - rank
        - score
        - verdict
        - sizeRank
        - growth
        - china
        - rivals
        - seasonality
        - tariff
        - buyers
        - marks
        - reporter
        - asOf
      additionalProperties: false
    MarketListItem:
      type: object
      properties:
        reportId:
          type: string
        product:
          type: string
        productRef:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
        hsCode:
          type: string
        status:
          type: string
          enum:
            - running
            - completed
            - failed
        createdAt:
          type: string
          description: "ISO 8601 时间"
        top:
          type: array
          items:
            type: string
      required:
        - reportId
        - product
        - hsCode
        - status
        - createdAt
        - top
      additionalProperties: false
    MarketListResponse:
      type: object
      properties:
        reports:
          type: array
          items:
            $ref: "#/components/schemas/MarketListItem"
      required:
        - reports
      additionalProperties: false
    MarketMark:
      type: string
      enum:
        - entrepot
        - china_exports
        - by_consignor
        - gateway
      description: "这一行的小标：转口地、按中国出口推算（进口国近年没报）、按发货国统计、欧盟清关门户"
    MarketQuoteResponse:
      type: object
      properties:
        maxFen:
          type: integer
          description: "这份最多收多少（分）；0 是不收钱（会员额度里、30 天内做过同样的）"
        wallet:
          $ref: "#/components/schemas/WalletView"
      required:
        - maxFen
        - wallet
      additionalProperties: false
    MarketReportView:
      type: object
      properties:
        reportId:
          type: string
        status:
          type: string
          enum:
            - running
            - completed
            - failed
        product:
          type: string
        productRef:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
        hs:
          type: object
          properties:
            code:
              type: string
            label:
              type: string
          required:
            - code
            - label
          additionalProperties: false
        progress:
          anyOf:
            - type: object
              properties:
                done:
                  type: integer
                total:
                  type: integer
                steps:
                  type: object
                  properties:
                    done:
                      type: integer
                    total:
                      type: integer
                  required:
                    - done
                    - total
                  additionalProperties: false
                  description: "进度条走到哪一步（老报告没有）"
              required:
                - done
                - total
              additionalProperties: false
              description: "done / total 是查完了几个国家"
            - type: "null"
        countries:
          type: array
          items:
            $ref: "#/components/schemas/MarketCountryView"
          description: "按名次排好；运行中先给已经查到的国家（还在查的格子在 pending 里，名次临时）"
        createdAt:
          type: string
          description: "ISO 8601 时间"
        error:
          type:
            - string
            - "null"
      required:
        - reportId
        - status
        - product
        - hs
        - progress
        - countries
        - createdAt
        - error
      additionalProperties: false
    MarketStartBody:
      type: object
      properties:
        product:
          type: string
        hs:
          type: string
        countries:
          type: array
          items:
            type: string
        productId:
          type: string
        maxFen:
          type: integer
          description: "用户认的上限（分，报价的 maxFen）；这份不收钱时不用给"
      required:
        - product
      additionalProperties: false
    MarketStarted:
      type: object
      properties:
        reportId:
          type: string
      required:
        - reportId
      additionalProperties: false
    MemberListResponse:
      type: object
      properties:
        members:
          type: array
          items:
            $ref: "#/components/schemas/MemberView"
      required:
        - members
      additionalProperties: false
    MemberView:
      type: object
      properties:
        userId:
          type: string
        email:
          type:
            - string
            - "null"
        role:
          type: string
      required:
        - userId
        - email
        - role
      additionalProperties: false
    MerchantsBody:
      type: object
      properties:
        product:
          type: string
          description: "产品，中文或英文（≤ 80 字）；或给 productId"
        productId:
          type: string
          description: "我的产品里的一个（有确认过的产品卡就按卡）"
        country:
          type: string
          description: "两位国家代码"
        types:
          type: array
          items:
            type: string
          description: "商家类型 id（GET /merchants/types 里的），1–8 个"
        quantity:
          type: integer
        maxFen:
          type: integer
          description: "用户认的上限（分，报价的 maxFen）：现在的报价超过它 409 quote_changed，不开跑"
      required:
        - country
        - types
      additionalProperties: false
    MerchantsSuggestBody:
      type: object
      properties:
        product:
          type: string
          description: "产品，中文或英文"
        productId:
          type: string
          description: "我的产品里的一个"
      additionalProperties: false
    MerchantsSuggestResponse:
      type: object
      properties:
        types:
          type: array
          items:
            type: string
          description: "按产品挑的商家类型 id，最相关的在前"
      required:
        - types
      additionalProperties: false
    MerchantTypesResponse:
      type: object
      properties:
        groups:
          type: array
          items:
            type: object
            properties:
              key:
                type: string
                enum:
                  - retail
                  - wholesale
                  - project
              name:
                type: string
              types:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: string
                    name:
                      type: string
                  required:
                    - id
                    - name
                  additionalProperties: false
            required:
              - key
              - name
              - types
            additionalProperties: false
      required:
        - groups
      additionalProperties: false
    NeedsClarification:
      type: object
      properties:
        needsClarification:
          type: string
      required:
        - needsClarification
      additionalProperties: false
    OutreachDraftBody:
      type: object
      properties:
        companyId:
          type: string
          description: "这个工作区看得到的公司"
        to:
          description: "收件邮箱（这家能用的邮箱之一）；不给挑最合适的"
          type:
            - string
            - "null"
        productId:
          description: "写哪个产品；不给用找到这家的那个产品"
          type:
            - string
            - "null"
        previousId:
          type: string
          description: "改写：上一封的 draftId（和 action 一起给）"
        action:
          type: string
          enum:
            - another
            - shorter
            - formal
          description: "改写：another 换一封、shorter 短一点、formal 更正式"
      required:
        - companyId
      additionalProperties: false
    OutreachDraftView:
      type: object
      properties:
        draftId:
          type: string
        companyId:
          type: string
        product:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
        language:
          type: string
          enum:
            - en
            - de
            - fr
            - es
            - pt
            - it
            - nl
        languageName:
          type: string
        style:
          type: object
          properties:
            length:
              type: string
              enum:
                - normal
                - short
            tone:
              type: string
              enum:
                - normal
                - formal
          required:
            - length
            - tone
          additionalProperties: false
        to:
          type:
            - string
            - "null"
        recipients:
          type: array
          items:
            $ref: "#/components/schemas/OutreachRecipient"
        greeting:
          type: object
          properties:
            text:
              type: string
            zh:
              type: string
          required:
            - text
            - zh
          additionalProperties: false
        subject:
          type: string
        body:
          type: string
          description: "纯文字：称呼、正文、落款、退订说明（对方的语言）"
        zh:
          type: object
          properties:
            subject:
              type: string
            body:
              type: string
          required:
            - subject
            - body
          additionalProperties: false
          description: "中文对照"
        from:
          anyOf:
            - type: object
              properties:
                address:
                  type: string
              required:
                - address
              additionalProperties: false
            - type: "null"
          description: "工作台上「用邮箱发」时的发信邮箱；没有是 null"
        sent:
          anyOf:
            - type: object
              properties:
                at:
                  type: string
                  description: "ISO 8601 时间"
                via:
                  type: string
                  enum:
                    - mailbox
                    - manual
                to:
                  type:
                    - string
                    - "null"
              required:
                - at
                - via
                - to
              additionalProperties: false
            - type: "null"
        signature:
          $ref: "#/components/schemas/OutreachSignatureView"
        figures:
          type: object
          properties:
            moq:
              type:
                - string
                - "null"
            leadTime:
              type:
                - string
                - "null"
            certifications:
              type: array
              items:
                type: string
          required:
            - moq
            - leadTime
            - certifications
          additionalProperties: false
          description: "信里能写的具体的数：产品的起订量、认证，公司资料的默认交期（原样）"
        createdAt:
          type: string
          description: "ISO 8601 时间"
      required:
        - draftId
        - companyId
        - product
        - language
        - languageName
        - style
        - to
        - recipients
        - greeting
        - subject
        - body
        - zh
        - from
        - sent
        - signature
        - figures
        - createdAt
      additionalProperties: false
    OutreachRecipient:
      type: object
      properties:
        personKey:
          anyOf:
            - type: string
              description: "联系人编号（p_ 开头；手动记的人 manual: 开头）"
            - type: "null"
        name:
          type:
            - string
            - "null"
        title:
          type:
            - string
            - "null"
        email:
          type: string
        verified:
          type: boolean
        greeting:
          type: object
          properties:
            text:
              type: string
            zh:
              type: string
          required:
            - text
            - zh
          additionalProperties: false
      required:
        - personKey
        - name
        - title
        - email
        - verified
        - greeting
      additionalProperties: false
    OutreachSignatureView:
      type: object
      properties:
        name:
          type:
            - string
            - "null"
        title:
          type:
            - string
            - "null"
        phone:
          type:
            - string
            - "null"
        company:
          type:
            - string
            - "null"
        website:
          type:
            - string
            - "null"
        saved:
          type: boolean
        lines:
          type: array
          items:
            type: string
      required:
        - name
        - title
        - phone
        - company
        - website
        - saved
        - lines
      additionalProperties: false
    PeopleListResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/LibraryPersonView"
        total:
          type: integer
        page:
          type: integer
        perPage:
          type: integer
      required:
        - items
        - total
        - page
        - perPage
      additionalProperties: false
    PersonView:
      type: object
      properties:
        key:
          type: string
          description: "联系人编号（p_ 开头；手动记的人 manual: 开头）"
        name:
          type: string
        title:
          type:
            - string
            - "null"
        roleRank:
          type: string
          enum:
            - procurement
            - related
            - other
        linkedinUrl:
          type:
            - string
            - "null"
        observedAt:
          type: string
          description: "ISO 8601 时间"
        hasEmail:
          type:
            - boolean
            - "null"
        hasPhone:
          type:
            - boolean
            - "null"
        channels:
          type: array
          items:
            $ref: "#/components/schemas/ChannelView"
        publicEmail:
          type:
            - string
            - "null"
      required:
        - key
        - name
        - title
        - roleRank
        - linkedinUrl
        - observedAt
        - hasEmail
        - hasPhone
        - channels
        - publicEmail
      additionalProperties: false
    PlanRequired:
      type: object
      properties:
        error:
          type: string
          const: plan_required
        message:
          type: string
        feature:
          type: string
          enum:
            - watch
            - daily_tasks
            - mailsync
            - team
            - api
            - export_emails
        tier:
          type: string
          enum:
            - basic
            - plus
            - pro
      required:
        - error
        - message
        - feature
      additionalProperties: false
    PostsRunView:
      type: object
      properties:
        runId:
          type: string
        status:
          type: string
          enum:
            - running
            - completed
            - failed
        progress:
          anyOf:
            - type: object
              properties:
                done:
                  type: integer
                total:
                  type: integer
              required:
                - done
                - total
              additionalProperties: false
            - type: "null"
        createdAt:
          type: string
          description: "ISO 8601 时间"
        chargedFen:
          anyOf:
            - type: integer
            - type: "null"
        product:
          type: string
        productRef:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
        country:
          type:
            - string
            - "null"
        days:
          type: integer
        read:
          type: integer
        found:
          type: integer
        posts:
          type: array
          items:
            $ref: "#/components/schemas/BuyingPostView"
        notes:
          type: array
          items:
            type: string
        error:
          type:
            - string
            - "null"
      required:
        - runId
        - status
        - createdAt
        - chargedFen
        - product
        - country
        - days
        - read
        - found
        - posts
        - notes
        - error
      additionalProperties: false
    ProductBody:
      type: object
      properties:
        name:
          type: string
        description:
          type:
            - string
            - "null"
        hs:
          type:
            - string
            - "null"
        keywords:
          type: array
          items:
            type: string
        priceRange:
          type:
            - string
            - "null"
        moq:
          type:
            - string
            - "null"
        certifications:
          type: array
          items:
            type: string
      required:
        - name
      additionalProperties: false
    ProductCard:
      type: object
      properties:
        terms:
          type: array
          items:
            type: string
          description: "英文规范名与买家常用叫法，第一个是主名"
        hs:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              label:
                type: string
            required:
              - code
              - label
            additionalProperties: false
          description: "海关编码（优先 6 位）与一句说明"
        summary:
          type: string
        buyers:
          type: array
          items:
            type: object
            properties:
              type:
                type: string
                enum:
                  - wholesale
                  - importer
                  - brand_oem
                  - project_contractor
                  - ecommerce
                  - retail
                  - service
              why:
                type: string
            required:
              - type
              - why
            additionalProperties: false
        exclude:
          type: array
          items:
            type: string
          description: "排除词：结果里带这些的丢掉"
        title:
          type: string
          description: "选中的那个理解的标题"
      required:
        - terms
        - hs
        - summary
        - buyers
        - exclude
      additionalProperties: false
    ProductCardBody:
      anyOf:
        - type: object
          properties:
            optionId:
              type: string
          required:
            - optionId
          additionalProperties: false
          description: "选一个理解"
        - type: object
          properties:
            card:
              $ref: "#/components/schemas/ProductCard"
          required:
            - card
          additionalProperties: false
          description: "给改好的整张卡"
    ProductEnsureBody:
      type: object
      properties:
        name:
          type: string
      required:
        - name
      additionalProperties: false
    ProductInterpretation:
      type: object
      properties:
        id:
          type: string
          description: "确认时作为 optionId 带回"
        title:
          type: string
        summary:
          type: string
        card:
          $ref: "#/components/schemas/ProductCard"
        preview:
          anyOf:
            - type: object
              properties:
                country:
                  type: string
                shipments12m:
                  anyOf:
                    - type: integer
                    - type: "null"
              required:
                - country
                - shipments12m
              additionalProperties: false
            - type: "null"
          description: "大概量：推荐国家近 12 个月按这个 HS 的进口票数"
      required:
        - id
        - title
        - summary
        - card
        - preview
      additionalProperties: false
    ProductListResponse:
      type: object
      properties:
        products:
          type: array
          items:
            $ref: "#/components/schemas/ProductView"
      required:
        - products
      additionalProperties: false
    ProductUnderstanding:
      type: object
      properties:
        status:
          type: string
          enum:
            - clear
            - ambiguous
            - broad
          description: "clear 一个理解；ambiguous 有几种理解；broad 太宽，给几个更具体的"
        options:
          type: array
          items:
            $ref: "#/components/schemas/ProductInterpretation"
        hsCandidates:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              label:
                type: string
            required:
              - code
              - label
            additionalProperties: false
          description: "改卡时海关编码的候选"
      required:
        - status
        - options
      additionalProperties: false
    ProductUpdateBody:
      type: object
      properties:
        name:
          type: string
        description:
          type:
            - string
            - "null"
        hs:
          type:
            - string
            - "null"
        keywords:
          type: array
          items:
            type: string
        priceRange:
          type:
            - string
            - "null"
        moq:
          type:
            - string
            - "null"
        certifications:
          type: array
          items:
            type: string
      additionalProperties: false
    ProductView:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type:
            - string
            - "null"
        hs:
          type:
            - string
            - "null"
        keywords:
          type: array
          items:
            type: string
        priceRange:
          type:
            - string
            - "null"
        moq:
          type:
            - string
            - "null"
        certifications:
          type: array
          items:
            type: string
        card:
          anyOf:
            - $ref: "#/components/schemas/ProductCard"
            - type: "null"
        cardConfirmedAt:
          anyOf:
            - type: string
              description: "ISO 8601 时间"
            - type: "null"
        createdAt:
          type: string
          description: "ISO 8601 时间"
        updatedAt:
          type: string
          description: "ISO 8601 时间"
      required:
        - id
        - name
        - description
        - hs
        - keywords
        - priceRange
        - moq
        - certifications
        - createdAt
        - updatedAt
      additionalProperties: false
    QuotaExceeded:
      type: object
      properties:
        error:
          type: string
          const: quota_exceeded
        message:
          type: string
        feature:
          type: string
          enum:
            - library
            - export
            - market
            - card
            - quote
        used:
          type: integer
        limit:
          type: integer
        resetsAt:
          anyOf:
            - type: string
              description: "ISO 8601 时间"
            - type: "null"
          description: "什么时候恢复（客户库是总数，null）"
      required:
        - error
        - message
        - feature
        - used
        - limit
        - resetsAt
      additionalProperties: false
    QuoteChanged:
      type: object
      properties:
        error:
          type: string
          const: quote_changed
        message:
          type: string
        maxFen:
          type: integer
      required:
        - error
        - message
        - maxFen
      additionalProperties: false
    QuoteChangedOrBusy:
      anyOf:
        - $ref: "#/components/schemas/QuoteChanged"
        - $ref: "#/components/schemas/ApiError"
    RateLimited:
      type: object
      properties:
        error:
          type: string
          const: rate_limited
        message:
          type: string
        retryAfterSeconds:
          type: integer
      required:
        - error
        - message
        - retryAfterSeconds
      additionalProperties: false
    ReviewBody:
      type: object
      properties:
        product:
          type: string
          description: "看一个品类：产品，中文或英文（≤ 80 字）；或给 productId"
        productId:
          type: string
          description: "我的产品里的一个（看单个商品时也可以带上，记下为哪个产品看的）"
        item:
          type: string
          description: "看一个商品：亚马逊商品链接或 10 位 ASIN（给了就不看 product；给链接时站点按链接）"
        country:
          type: string
          description: "两位国家代码：看哪个亚马逊站（GET /reviews/countries 里的）"
        reddit:
          type: boolean
          description: "加上 Reddit 上的讨论（多一点数据费），默认 false；只对品类有用"
      required:
        - country
      additionalProperties: false
    ReviewCountriesResponse:
      type: object
      properties:
        countries:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              name:
                type: string
              site:
                type: string
                description: "亚马逊站的域名，如 amazon.de"
            required:
              - code
              - name
              - site
            additionalProperties: false
      required:
        - countries
      additionalProperties: false
    ReviewListResponse:
      type: object
      properties:
        reports:
          type: array
          items:
            $ref: "#/components/schemas/ReviewReportView"
      required:
        - reports
      additionalProperties: false
    ReviewQuoteResponse:
      type: object
      properties:
        maxFen:
          type: integer
        reused:
          type: boolean
          description: "7 天内本组织看过同一个：这份不收钱"
        wallet:
          $ref: "#/components/schemas/WalletView"
      required:
        - maxFen
        - reused
        - wallet
      additionalProperties: false
    ReviewReportView:
      type: object
      properties:
        reportId:
          type: string
        status:
          type: string
          enum:
            - running
            - completed
            - failed
        progress:
          anyOf:
            - type: object
              properties:
                done:
                  type: integer
                total:
                  type: integer
              required:
                - done
                - total
              additionalProperties: false
            - type: "null"
        createdAt:
          type: string
          description: "ISO 8601 时间"
        chargedFen:
          anyOf:
            - type: integer
            - type: "null"
        target:
          type: object
          properties:
            kind:
              type: string
              enum:
                - category
                - item
            product:
              type:
                - string
                - "null"
            country:
              type: string
            asin:
              type:
                - string
                - "null"
            title:
              type:
                - string
                - "null"
            url:
              type:
                - string
                - "null"
          required:
            - kind
            - product
            - country
            - asin
            - title
            - url
          additionalProperties: false
        productRef:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
        products:
          type: array
          items:
            type: object
            properties:
              asin:
                type: string
              title:
                type: string
              brand:
                type:
                  - string
                  - "null"
              rating:
                type:
                  - number
                  - "null"
              ratings:
                type:
                  - number
                  - "null"
              price:
                type:
                  - string
                  - "null"
              url:
                type: string
            required:
              - asin
              - title
              - brand
              - rating
              - ratings
              - price
              - url
            additionalProperties: false
          description: "看了哪些商品"
        read:
          type: object
          properties:
            reviews:
              type: integer
            lowStar:
              type: integer
            posts:
              type: integer
          required:
            - reviews
            - lowStar
            - posts
          additionalProperties: false
          description: "读了多少：评论条数、其中 1–3 星几条、Reddit 帖子与评论几条"
        summary:
          type: string
        complaints:
          type: array
          items:
            $ref: "#/components/schemas/ReviewTheme"
        praises:
          type: array
          items:
            $ref: "#/components/schemas/ReviewTheme"
        questions:
          type: array
          items:
            $ref: "#/components/schemas/ReviewTheme"
        ideas:
          type: array
          items:
            type: string
          description: "改款与卖点的建议"
        notes:
          type: array
          items:
            type: string
        error:
          type:
            - string
            - "null"
      required:
        - reportId
        - status
        - createdAt
        - chargedFen
        - target
        - products
        - read
        - summary
        - complaints
        - praises
        - questions
        - ideas
        - notes
        - error
      additionalProperties: false
    ReviewStartBody:
      type: object
      properties:
        product:
          type: string
          description: "看一个品类：产品，中文或英文（≤ 80 字）；或给 productId"
        productId:
          type: string
          description: "我的产品里的一个（看单个商品时也可以带上，记下为哪个产品看的）"
        item:
          type: string
          description: "看一个商品：亚马逊商品链接或 10 位 ASIN（给了就不看 product；给链接时站点按链接）"
        country:
          type: string
          description: "两位国家代码：看哪个亚马逊站（GET /reviews/countries 里的）"
        reddit:
          type: boolean
          description: "加上 Reddit 上的讨论（多一点数据费），默认 false；只对品类有用"
        maxFen:
          type: integer
          description: "用户认的上限（分，报价的 maxFen）：现在的报价超过它 409 quote_changed，不开跑"
      required:
        - country
        - maxFen
      additionalProperties: false
    ReviewStarted:
      type: object
      properties:
        reportId:
          type: string
      required:
        - reportId
      additionalProperties: false
    ReviewTheme:
      type: object
      properties:
        text:
          type: string
        count:
          type: integer
          description: "读过的评论里有多少条提到"
        share:
          type: number
          description: "占比 0–100"
        quotes:
          type: array
          items:
            type: object
            properties:
              text:
                type: string
              rating:
                type:
                  - number
                  - "null"
              source:
                type: string
                enum:
                  - amazon
                  - reddit
              url:
                type:
                  - string
                  - "null"
              date:
                type:
                  - string
                  - "null"
            required:
              - text
              - rating
              - source
              - url
              - date
            additionalProperties: false
          description: "原话，带出处"
      required:
        - text
        - count
        - share
        - quotes
      additionalProperties: false
    SearchListItem:
      type: object
      properties:
        runId:
          type: string
        kind:
          type: string
          enum:
            - similar
            - sellers
            - advertisers
            - merchants
        productRef:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
        request:
          type: string
        status:
          type: string
          enum:
            - running
            - completed
            - failed
        requestedQuantity:
          type: integer
        delivered:
          type: integer
        startedAt:
          type: string
          description: "ISO 8601 时间"
        finishedAt:
          anyOf:
            - type: string
              description: "ISO 8601 时间"
            - type: "null"
        chargedFen:
          anyOf:
            - type: integer
            - type: "null"
      required:
        - runId
        - request
        - status
        - requestedQuantity
        - delivered
        - startedAt
        - finishedAt
        - chargedFen
      additionalProperties: false
    SearchListResponse:
      type: object
      properties:
        searches:
          type: array
          items:
            $ref: "#/components/schemas/SearchListItem"
      required:
        - searches
      additionalProperties: false
    SearchQuoteBody:
      type: object
      properties:
        quantity:
          type: integer
        kind:
          type: string
          enum:
            - similar
            - sellers
            - advertisers
            - merchants
      additionalProperties: false
    SearchQuoteResponse:
      type: object
      properties:
        quantity:
          type: integer
        maxFen:
          type: integer
        easyFen:
          type: integer
        hardFen:
          type: integer
        wallet:
          $ref: "#/components/schemas/WalletView"
      required:
        - quantity
        - maxFen
        - easyFen
        - hardFen
        - wallet
      additionalProperties: false
    SearchStarted:
      type: object
      properties:
        runId:
          type: string
      required:
        - runId
      additionalProperties: false
    SearchView:
      type: object
      properties:
        runId:
          type: string
        kind:
          type: string
          enum:
            - similar
            - sellers
            - advertisers
            - merchants
        productRef:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
        similar:
          type: object
          properties:
            seeds:
              type: array
              items:
                type: string
            country:
              type:
                - string
                - "null"
            productId:
              type:
                - string
                - "null"
          required:
            - seeds
            - country
            - productId
          additionalProperties: false
          description: "相似客户搜索才有：老客户的域名、指定的国家、选的产品；按它再发一次 POST /similar 就是接着找（找到过的不再给）"
        need:
          type: object
          properties:
            country:
              type:
                - string
                - "null"
            buyerTypes:
              type: array
              items:
                type: string
                enum:
                  - wholesale
                  - importer
                  - brand_oem
                  - project_contractor
                  - ecommerce
                  - retail
                  - service
            note:
              type:
                - string
                - "null"
          required:
            - country
            - buyerTypes
            - note
          additionalProperties: false
          description: "点选的找法才有：国家、买家类型、补充；和 productId 一起原样再发就是同样的找法"
        request:
          type: string
        status:
          type: string
          enum:
            - running
            - completed
            - failed
        waiting:
          type: integer
          description: "还在跑、数据源人多时才有：前面还有几个请求在排队"
        stage:
          type: string
          enum:
            - discover
            - judge
            - deliver
        product:
          type: string
        countries:
          type: array
          items:
            type: string
        requestedQuantity:
          type: integer
        delivered:
          type: integer
        startedAt:
          type: string
          description: "ISO 8601 时间"
        finishedAt:
          anyOf:
            - type: string
              description: "ISO 8601 时间"
            - type: "null"
        rows:
          type: array
          items:
            $ref: "#/components/schemas/CompanyRowView"
        summary:
          anyOf:
            - type: object
              properties:
                fromReserve:
                  type: integer
                alreadyDelivered:
                  type: integer
                reachedLevel:
                  anyOf:
                    - type: integer
                    - type: "null"
                suggestions:
                  type: array
                  items:
                    type: object
                    properties:
                      countryCode:
                        type: string
                      name:
                        type: string
                      why:
                        type: string
                    required:
                      - countryCode
                      - name
                      - why
                    additionalProperties: false
                firstDeliveryMs:
                  anyOf:
                    - type: integer
                    - type: "null"
                completedMs:
                  type: integer
                partialReason:
                  description: "没凑够数时的一句；要查的都没查成、一家都没给时是 null（看 trouble）"
                  type:
                    - string
                    - "null"
                canFindMore:
                  type: boolean
                  description: "还能不能接着找：false 是这个市场能找到的都给了，再搜一次也不会多"
                failedSources:
                  type: integer
                  description: "上游出错没查成的来源有几个（额度用完、鉴权失败、用不了）；0 是都查成了"
                trouble:
                  description: "没查成时的一句：要查的都没查成、一家都没给是「买家搜索现在查不了，稍后再试」（不是没找到），别的是「这次有 N 个来源没查成，结果可能不全」；都查成了是 null"
                  type:
                    - string
                    - "null"
                alreadyElsewhere:
                  type: array
                  items:
                    type: object
                    properties:
                      companyId:
                        type: string
                      name:
                        type: string
                      workspaceName:
                        type: string
                    required:
                      - companyId
                      - name
                      - workspaceName
                    additionalProperties: false
                  description: "团队里别的工作区交付过 / 在库里、这次因此没给的公司（按组织去重）；POST /library { companyId, fromWorkspace: true } 也放进本工作区"
              required:
                - fromReserve
                - alreadyDelivered
                - reachedLevel
                - suggestions
                - firstDeliveryMs
                - completedMs
                - partialReason
                - canFindMore
                - failedSources
                - trouble
              additionalProperties: false
            - type: "null"
        chargedFen:
          anyOf:
            - type: integer
            - type: "null"
        notes:
          type: array
          items:
            type: string
      required:
        - runId
        - request
        - status
        - stage
        - product
        - countries
        - requestedQuantity
        - delivered
        - startedAt
        - finishedAt
        - rows
        - summary
        - chargedFen
      additionalProperties: false
    SeenOnView:
      type: object
      properties:
        channel:
          type: string
          enum:
            - amazon
            - shopping
            - meta
            - google
            - maps
          description: "amazon 亚马逊、shopping 谷歌购物、meta Facebook / Instagram 广告、google 谷歌搜索广告、maps Google 地图"
        text:
          type: string
          description: "给人看的一句（「亚马逊美国站卖 12 款，均价 $35」「最近 30 天在 Facebook 投了 8 条广告」）"
        url:
          type:
            - string
            - "null"
        at:
          anyOf:
            - type: string
              description: "ISO 8601 时间"
            - type: "null"
          description: "最近一次看到"
      required:
        - channel
        - text
        - url
        - at
      additionalProperties: false
    SellerCountriesResponse:
      type: object
      properties:
        countries:
          type: array
          items:
            type: object
            properties:
              code:
                type: string
              name:
                type: string
              channels:
                type: array
                items:
                  type: string
                  enum:
                    - amazon
                    - shopping
            required:
              - code
              - name
              - channels
            additionalProperties: false
      required:
        - countries
      additionalProperties: false
    SellersBody:
      type: object
      properties:
        product:
          type: string
          description: "产品，中文或英文（≤ 80 字）；或给 productId"
        productId:
          type: string
          description: "我的产品里的一个（有确认过的产品卡就按卡）"
        includeChina:
          type: boolean
          description: "默认 false：注册在中国大陆 / 香港的去掉（多半是同行）"
        quantity:
          type: integer
        maxFen:
          type: integer
          description: "用户认的上限（分，报价的 maxFen）：现在的报价超过它 409 quote_changed，不开跑"
        country:
          type: string
          description: "两位国家代码（GET /sellers/countries 里的）"
        channels:
          type: array
          items:
            type: string
            enum:
              - amazon
              - shopping
          description: "amazon 亚马逊、shopping 谷歌购物；不给就是这个国家能查的全部"
      required:
        - country
      additionalProperties: false
    SignalListResponse:
      type: object
      properties:
        signals:
          type: array
          items:
            $ref: "#/components/schemas/SignalView"
        total:
          type: integer
        unread:
          type: integer
        page:
          type: integer
        perPage:
          type: integer
      required:
        - signals
        - total
        - unread
        - page
        - perPage
      additionalProperties: false
    SignalReadBody:
      type: object
      properties:
        ids:
          type: array
          items:
            type: string
        all:
          type: boolean
      additionalProperties: false
    SignalTaskBody:
      type: object
      properties:
        dueOn:
          type: string
          description: "不给就是今天"
      additionalProperties: false
    SignalView:
      type: object
      properties:
        id:
          type: string
        watchId:
          description: "哪个关注、订阅出的；done、failed 不是关注出的，是 null"
          type:
            - string
            - "null"
        kind:
          type: string
          enum:
            - new_shipment
            - volume_up
            - supplier_change
            - competitor_buyer_lost
            - competitor_new_buyer
            - reorder_due
            - hiring
            - new_category
            - new_buyers
            - ads_started
            - new_posts
            - notice
            - done
            - failed
          description: "new_posts 是求购帖订阅这周查到的新帖子（runId 打开 GET /posts/{runId}）；new_buyers 是需求订阅找到的新买家（runId 打开 GET /searches/{runId}）；notice 是给用户的提示；done、failed 是自己开的、跑了超过 1 分钟的后台任务做好了 / 没做成（job 是哪一份，failed 的 why 是原因），只给开它的人看"
        companyId:
          type:
            - string
            - "null"
        companyName:
          type: string
        library:
          type: object
          properties:
            inLibrary:
              type: boolean
            stage:
              anyOf:
                - type: string
                  enum:
                    - new
                    - contacted
                    - replied
                    - sampling
                    - won
                    - dropped
                - type: "null"
            starred:
              type: boolean
            ownerId:
              type:
                - string
                - "null"
            notRelevant:
              type: array
              items:
                type: object
                properties:
                  productId:
                    type: string
                  reason:
                    type: string
                    enum:
                      - not_this_product
                      - competitor
                      - forwarder
                      - too_small
                      - other
                  at:
                    type: string
                    description: "ISO 8601 时间"
                required:
                  - productId
                  - reason
                  - at
                additionalProperties: false
              description: "本组织说过这家「不相关」的产品（每个产品最近一次，新的在前）；没说过不带"
          required:
            - inLibrary
            - stage
            - starred
            - ownerId
          additionalProperties: false
        title:
          type: string
        why:
          type: string
        action:
          type: string
        occurredAt:
          anyOf:
            - type: string
              description: "ISO 8601 时间"
            - type: "null"
        detectedAt:
          type: string
          description: "ISO 8601 时间"
        source:
          $ref: "#/components/schemas/SourceView"
        runId:
          type:
            - string
            - "null"
        job:
          description: "done、failed 是哪一份：market GET /markets/{id}、reviews GET /reviews/{id}、search GET /searches/{id}、verify GET /verify/{id}；brief、competitor、cards 在工作台里看"
          anyOf:
            - type: object
              properties:
                kind:
                  $ref: "#/components/schemas/JobKind"
                id:
                  type: string
              required:
                - kind
                - id
              additionalProperties: false
            - type: "null"
        read:
          type: boolean
      required:
        - id
        - watchId
        - kind
        - companyId
        - companyName
        - title
        - why
        - action
        - occurredAt
        - detectedAt
        - source
        - read
      additionalProperties: false
    SignatureNeeded:
      type: object
      properties:
        error:
          type: string
          const: signature_needed
        message:
          type: string
        signature:
          $ref: "#/components/schemas/OutreachSignatureView"
      required:
        - error
        - message
        - signature
      additionalProperties: false
      description: "还没确认落款：signature 是按账户邮箱与公司资料预填的那份"
    SimilarBody:
      type: object
      properties:
        seeds:
          type: array
          items:
            type: string
        productId:
          type: string
        country:
          type: string
        quantity:
          type: integer
      required:
        - seeds
      additionalProperties: false
    SourceView:
      type: object
      properties:
        kind:
          type: string
          enum:
            - customs
            - customs_stats
            - tariff
            - fx
            - registry
            - company_db
            - website
            - maps
            - marketplace
            - ads
            - jobs
            - social
            - reviews
            - contact_db
            - email_check
            - ai
            - search
            - library
            - manual
            - other
          description: "来源的种类：customs 海关记录、customs_stats 海关统计、tariff 关税税率表、fx 汇率、registry 企业档案、company_db 企业数据库、website 公司官网、maps 地图商家信息、marketplace 电商平台、ads 广告库、jobs 招聘信息、social 社交平台、reviews 买家评论、contact_db 联系人数据、email_check 邮箱验证、ai AI 归纳、search 买家搜索、library 客户库、manual 手动记的、other 公开资料"
        label:
          type: string
          description: "给人看的说法"
        url:
          type:
            - string
            - "null"
      required:
        - kind
        - label
      additionalProperties: false
    StartSearchBody:
      type: object
      properties:
        request:
          description: "一句话需求；给了 country / buyerTypes / note 时不用（服务端按产品名和它们拼一句）"
          type: string
        quantity:
          type: integer
        productId:
          type: string
        country:
          description: "两位国家代码：找这个国家的（硬约束）"
          type: string
        buyerTypes:
          description: "找哪几类买家，按先后：importer 进口商、wholesale 批发商、retail 零售连锁、ecommerce 电商、project_contractor 工程商、brand_oem 品牌商、service 维修商"
          type: array
          items:
            type: string
            enum:
              - wholesale
              - importer
              - brand_oem
              - project_contractor
              - ecommerce
              - retail
              - service
        note:
          description: "补充一句（≤ 200 字）"
          type: string
      additionalProperties: false
    TaskCreateBody:
      type: object
      properties:
        title:
          type: string
        dueOn:
          type: string
          description: "YYYY-MM-DD（北京时间）"
        companyId:
          type: string
        assigneeId:
          type: string
        personKey:
          type: string
          description: "挂这家公司的一个联系人（要带 companyId）"
      required:
        - title
        - dueOn
      additionalProperties: false
    TaskListResponse:
      type: object
      properties:
        tasks:
          type: array
          items:
            $ref: "#/components/schemas/TaskView"
        counts:
          type: object
          properties:
            overdue:
              type: integer
            today:
              type: integer
            week:
              type: integer
          required:
            - overdue
            - today
            - week
          additionalProperties: false
      required:
        - tasks
        - counts
      additionalProperties: false
    TaskUpdateBody:
      type: object
      properties:
        title:
          type: string
        dueOn:
          type: string
          description: "YYYY-MM-DD（北京时间）"
        done:
          type: boolean
        assigneeId:
          type:
            - string
            - "null"
        personKey:
          type:
            - string
            - "null"
      additionalProperties: false
    TaskView:
      type: object
      properties:
        id:
          type: string
        companyId:
          type:
            - string
            - "null"
        companyName:
          type:
            - string
            - "null"
        library:
          type: object
          properties:
            inLibrary:
              type: boolean
            stage:
              anyOf:
                - type: string
                  enum:
                    - new
                    - contacted
                    - replied
                    - sampling
                    - won
                    - dropped
                - type: "null"
            starred:
              type: boolean
            ownerId:
              type:
                - string
                - "null"
            notRelevant:
              type: array
              items:
                type: object
                properties:
                  productId:
                    type: string
                  reason:
                    type: string
                    enum:
                      - not_this_product
                      - competitor
                      - forwarder
                      - too_small
                      - other
                  at:
                    type: string
                    description: "ISO 8601 时间"
                required:
                  - productId
                  - reason
                  - at
                additionalProperties: false
              description: "本组织说过这家「不相关」的产品（每个产品最近一次，新的在前）；没说过不带"
          required:
            - inLibrary
            - stage
            - starred
            - ownerId
          additionalProperties: false
        title:
          type: string
        dueOn:
          type: string
          description: "YYYY-MM-DD（北京时间）"
        done:
          type: boolean
        doneAt:
          anyOf:
            - type: string
              description: "ISO 8601 时间"
            - type: "null"
        assignee:
          anyOf:
            - type: object
              properties:
                userId:
                  type: string
                email:
                  type:
                    - string
                    - "null"
              required:
                - userId
                - email
              additionalProperties: false
            - type: "null"
        personKey:
          type:
            - string
            - "null"
        personName:
          type:
            - string
            - "null"
        createdAt:
          type: string
          description: "ISO 8601 时间"
        fromSignalId:
          type:
            - string
            - "null"
      required:
        - id
        - companyId
        - companyName
        - title
        - dueOn
        - done
        - doneAt
        - assignee
        - createdAt
      additionalProperties: false
    TooMany:
      type: object
      properties:
        error:
          type: string
          const: too_many
        message:
          type: string
        upgrade:
          type: string
      required:
        - error
        - message
      additionalProperties: false
    TradeListItem:
      type: object
      properties:
        runId:
          type: string
        kind:
          type: string
          enum:
            - trade_company
            - trade_product
            - trade_competitor
        label:
          type: string
        country:
          type: string
        productRef:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
        status:
          type: string
          enum:
            - running
            - completed
            - failed
        startedAt:
          type: string
          description: "ISO 8601 时间"
        chargedFen:
          anyOf:
            - type: integer
            - type: "null"
      required:
        - runId
        - kind
        - label
        - country
        - productRef
        - status
        - startedAt
        - chargedFen
      additionalProperties: false
    TradeListResponse:
      type: object
      properties:
        runs:
          type: array
          items:
            $ref: "#/components/schemas/TradeListItem"
      required:
        - runs
      additionalProperties: false
    TradeOptionView:
      type: object
      properties:
        key:
          type: string
        label:
          type: string
        explain:
          type: string
        required:
          description: "必选、勾不掉（首次核对）"
          type: boolean
        maxCostFen:
          type: integer
      required:
        - key
        - label
        - explain
        - maxCostFen
      additionalProperties: false
    TradeProductView:
      type: object
      properties:
        canonical:
          type: string
        hs4:
          type: array
          items:
            type: string
        terms:
          type: array
          items:
            type: string
      required:
        - canonical
        - hs4
        - terms
      additionalProperties: false
    TradeQuoteBody:
      oneOf:
        - type: object
          properties:
            kind:
              type: string
              const: trade_company
            companyId:
              description: "只给它就行：这个工作区看得到的公司，名字、官网、国家由服务端取（看不到 404）"
              type: string
            country:
              description: "两位代码；给了 companyId 可不给（用这家的国家）"
              type: string
            name:
              description: "没给 companyId 时，name 与 customsName 至少给一个"
              type: string
            domain:
              type: string
            email:
              description: "对方公司邮箱：取 @ 后面的域名当官网（免费邮箱 400）"
              type: string
            customsName:
              description: "先认准是哪一家之后用户点的海关写法：按它查；官网与它第一次查时报价里多一项必选的首次核对，核对不上不查、不收钱"
              type: string
            product:
              type: string
            productId:
              description: "我的产品里的这个；有确认过的产品卡、product 写的就是它的名字时按卡查（HS 与英文叫法用卡上的）"
              type: string
          required:
            - kind
          additionalProperties: false
        - type: object
          properties:
            kind:
              type: string
              const: trade_product
            country:
              type: string
            product:
              type: string
            page:
              type: integer
            perPage:
              type: integer
            productId:
              description: "我的产品里的这个；有确认过的产品卡、product 写的就是它的名字时按卡查（HS 与英文叫法用卡上的）"
              type: string
          required:
            - kind
            - country
            - product
          additionalProperties: false
        - type: object
          properties:
            kind:
              type: string
              const: trade_competitor
            country:
              type: string
            exporter:
              type: string
            product:
              type: string
            buyers:
              type: integer
            productId:
              description: "我的产品里的这个；有确认过的产品卡、product 写的就是它的名字时按卡查（HS 与英文叫法用卡上的）"
              type: string
          required:
            - kind
            - country
            - exporter
          additionalProperties: false
    TradeQuoteCovered:
      type: object
      properties:
        covered:
          type: boolean
        reason:
          type:
            - string
            - "null"
        fixedFen:
          type: integer
        options:
          type: array
          items:
            $ref: "#/components/schemas/TradeOptionView"
        defaults:
          type: array
          items:
            type: string
        product:
          anyOf:
            - $ref: "#/components/schemas/TradeProductView"
            - type: "null"
      required:
        - covered
        - reason
        - fixedFen
        - options
        - defaults
        - product
      additionalProperties: false
    TradeQuoteResponse:
      anyOf:
        - $ref: "#/components/schemas/NeedsClarification"
        - $ref: "#/components/schemas/TradeQuoteCovered"
    TradeRunBody:
      oneOf:
        - type: object
          properties:
            kind:
              type: string
              const: trade_company
            companyId:
              description: "只给它就行：这个工作区看得到的公司，名字、官网、国家由服务端取（看不到 404）"
              type: string
            country:
              description: "两位代码；给了 companyId 可不给（用这家的国家）"
              type: string
            name:
              description: "没给 companyId 时，name 与 customsName 至少给一个"
              type: string
            domain:
              type: string
            email:
              description: "对方公司邮箱：取 @ 后面的域名当官网（免费邮箱 400）"
              type: string
            customsName:
              description: "先认准是哪一家之后用户点的海关写法：按它查；官网与它第一次查时报价里多一项必选的首次核对，核对不上不查、不收钱"
              type: string
            product:
              type: string
            productId:
              description: "我的产品里的这个；有确认过的产品卡、product 写的就是它的名字时按卡查（HS 与英文叫法用卡上的）"
              type: string
            options:
              type: array
              items:
                type: string
            productSpec:
              anyOf:
                - $ref: "#/components/schemas/TradeProductView"
                - type: "null"
          required:
            - kind
            - options
          additionalProperties: false
        - type: object
          properties:
            kind:
              type: string
              const: trade_product
            country:
              type: string
            product:
              type: string
            page:
              type: integer
            perPage:
              type: integer
            productId:
              description: "我的产品里的这个；有确认过的产品卡、product 写的就是它的名字时按卡查（HS 与英文叫法用卡上的）"
              type: string
            options:
              type: array
              items:
                type: string
            productSpec:
              anyOf:
                - $ref: "#/components/schemas/TradeProductView"
                - type: "null"
          required:
            - kind
            - country
            - product
            - options
          additionalProperties: false
        - type: object
          properties:
            kind:
              type: string
              const: trade_competitor
            country:
              type: string
            exporter:
              type: string
            product:
              type: string
            buyers:
              type: integer
            productId:
              description: "我的产品里的这个；有确认过的产品卡、product 写的就是它的名字时按卡查（HS 与英文叫法用卡上的）"
              type: string
            options:
              type: array
              items:
                type: string
            productSpec:
              anyOf:
                - $ref: "#/components/schemas/TradeProductView"
                - type: "null"
          required:
            - kind
            - country
            - exporter
            - options
          additionalProperties: false
    TradeRunResult:
      type: object
      properties:
        runId:
          type: string
        kind:
          type: string
          enum:
            - trade_company
            - trade_product
            - trade_competitor
        result:
          description: "三种查询的结果原样（企业进口记录 / 产品查询 / 竞品客户查询）；认出来的公司（company.companyId、名单与买家行的 knownCompanyId）是合并后的 id，并带 library（客户库状态，读的时候现算）"
        chargedFen:
          anyOf:
            - type: integer
            - type: "null"
        fixedFen:
          anyOf:
            - type: integer
            - type: "null"
        costFen:
          anyOf:
            - type: integer
            - type: "null"
        free:
          description: "为什么是 ¥0：nothing_found 没查到、market_not_covered 这个国家查不到、failed 没查成（上游出错，不是没查到）"
          type:
            - string
            - "null"
        failures:
          type: array
          items:
            type: object
            properties:
              option:
                type: string
              code:
                type: string
              message:
                type: string
            required:
              - option
              - code
              - message
            additionalProperties: false
          description: "没取到的项（一项一条；message 是「进口概况现在查不了，稍后再试」这样的一句）"
        productRef:
          description: "为「我的产品」里哪一个查的；没挂是 null"
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
      required:
        - runId
        - kind
        - result
        - chargedFen
        - fixedFen
        - costFen
        - free
        - failures
      additionalProperties: false
    TradeRunView:
      type: object
      properties:
        runId:
          type: string
        kind:
          type: string
          enum:
            - trade_company
            - trade_product
            - trade_competitor
        result:
          description: "三种查询的结果原样（企业进口记录 / 产品查询 / 竞品客户查询）；认出来的公司（company.companyId、名单与买家行的 knownCompanyId）是合并后的 id，并带 library（客户库状态，读的时候现算）"
        chargedFen:
          anyOf:
            - type: integer
            - type: "null"
        fixedFen:
          anyOf:
            - type: integer
            - type: "null"
        costFen:
          anyOf:
            - type: integer
            - type: "null"
        free:
          description: "为什么是 ¥0：nothing_found 没查到、market_not_covered 这个国家查不到、failed 没查成（上游出错，不是没查到）"
          type:
            - string
            - "null"
        failures:
          type: array
          items:
            type: object
            properties:
              option:
                type: string
              code:
                type: string
              message:
                type: string
            required:
              - option
              - code
              - message
            additionalProperties: false
          description: "没取到的项（一项一条；message 是「进口概况现在查不了，稍后再试」这样的一句）"
        productRef:
          description: "为「我的产品」里哪一个查的；没挂是 null"
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
        startedAt:
          type: string
          description: "ISO 8601 时间"
        query: {}
      required:
        - runId
        - kind
        - result
        - chargedFen
        - fixedFen
        - costFen
        - free
        - failures
        - startedAt
        - query
      additionalProperties: false
    VerifyBatchView:
      type: object
      properties:
        batchId:
          type: string
        status:
          type: string
          enum:
            - running
            - completed
            - failed
          description: "running 还在验；completed 验完了、结算了；failed 一个都没验成或中途断了（error 说为什么，验了的结论照样在）"
        createdAt:
          type: string
          description: "ISO 8601 时间"
        createdBy:
          anyOf:
            - type: object
              properties:
                userId:
                  type: string
                email:
                  type:
                    - string
                    - "null"
              required:
                - userId
                - email
              additionalProperties: false
            - type: "null"
        progress:
          type: object
          properties:
            done:
              type: integer
            total:
              type: integer
          required:
            - done
            - total
          additionalProperties: false
          description: "出了结论的个数 / 一共几个"
        chargedFen:
          type: integer
          description: "实付（分）：全部验完结算后才有，运行中是 0"
        skipped:
          type: integer
        error:
          type:
            - string
            - "null"
        results:
          type: array
          items:
            type: object
            properties:
              email:
                type: string
              status:
                type: string
                enum:
                  - valid
                  - invalid
                  - accept_all
                  - unknown
                  - failed
                  - pending
                description: "valid 有效、invalid 收不到（含一次性邮箱）、accept_all 不确定能收到、unknown 结果不明、failed 没验成（不收钱，下次再试）、pending 还在验"
              chargedFen:
                type: integer
              companyIds:
                type: array
                items:
                  type: string
                description: "在本工作区客户库里属于哪几家"
            required:
              - email
              - status
              - chargedFen
              - companyIds
            additionalProperties: false
      required:
        - batchId
        - status
        - createdAt
        - createdBy
        - progress
        - chargedFen
        - error
        - results
      additionalProperties: false
    VerifyBody:
      type: object
      properties:
        emails:
          description: "要验证的邮箱"
          type: array
          items:
            type: string
        companyIds:
          description: "客户库里这几家公司的邮箱"
          type: array
          items:
            type: string
        allUnverified:
          description: "客户库里还没验证过的全部（最多 500 个）"
          type: boolean
      additionalProperties: false
    VerifyQuoteResponse:
      type: object
      properties:
        count:
          type: integer
          description: "这次要验证几个"
        maxFen:
          type: integer
          description: "最多多少钱（分）"
        wallet:
          $ref: "#/components/schemas/WalletView"
        skipped:
          description: "30 天内验证过能收信、这次跳过的个数"
          type: integer
      required:
        - count
        - maxFen
        - wallet
      additionalProperties: false
    VerifyRunBody:
      type: object
      properties:
        emails:
          description: "要验证的邮箱"
          type: array
          items:
            type: string
        companyIds:
          description: "客户库里这几家公司的邮箱"
          type: array
          items:
            type: string
        allUnverified:
          description: "客户库里还没验证过的全部（最多 500 个）"
          type: boolean
        maxFen:
          type: integer
          description: "用户认的上限（分，报价的 maxFen）"
      required:
        - maxFen
      additionalProperties: false
    VerifyStarted:
      type: object
      properties:
        batchId:
          type: string
        count:
          type: integer
        skipped:
          type: integer
      required:
        - batchId
        - count
      additionalProperties: false
    WalletView:
      type: object
      properties:
        balanceFen:
          type: integer
        heldFen:
          type: integer
        availableFen:
          type: integer
        creditFen:
          type: integer
        pricePct:
          type: integer
      required:
        - balanceFen
        - heldFen
        - availableFen
      additionalProperties: false
    WatchCreateBody:
      oneOf:
        - type: object
          properties:
            kind:
              type: string
              const: company
            companyId:
              type: string
              description: "客户库里的一家"
          required:
            - kind
            - companyId
          additionalProperties: false
          description: "关注一家客户"
        - type: object
          properties:
            kind:
              type: string
              const: competitor
            exporter:
              type: string
              description: "同行（出口商）的全名"
            country:
              type: string
            product:
              type: string
              description: "HS 编码或英文品名"
          required:
            - kind
            - exporter
          additionalProperties: false
          description: "关注一个同行"
        - type: object
          properties:
            kind:
              type: string
              const: search
            request:
              type: string
            weeklyCapFen:
              type: integer
              description: "每周最多花多少（分）：需求订阅默认 500、求购帖订阅默认 1000"
            productId:
              type: string
            country:
              type: string
            buyerTypes:
              type: array
              items:
                type: string
                enum:
                  - wholesale
                  - importer
                  - brand_oem
                  - project_contractor
                  - ecommerce
                  - retail
                  - service
            note:
              type: string
          required:
            - kind
          additionalProperties: false
          description: "需求订阅：每周按这个需求找新买家"
        - type: object
          properties:
            kind:
              type: string
              const: posts
            productId:
              type: string
            product:
              type: string
            country:
              type: string
            weeklyCapFen:
              type: integer
              description: "每周最多花多少（分）：需求订阅默认 500、求购帖订阅默认 1000"
          required:
            - kind
          additionalProperties: false
          description: "求购帖订阅：每周查领英上新的求购帖"
    WatchListResponse:
      type: object
      properties:
        watches:
          type: array
          items:
            $ref: "#/components/schemas/WatchView"
        limits:
          type: object
          properties:
            companies:
              anyOf:
                - type: integer
                - type: "null"
            competitors:
              anyOf:
                - type: integer
                - type: "null"
            searches:
              anyOf:
                - type: integer
                - type: "null"
            posts:
              anyOf:
                - type: integer
                - type: "null"
          required:
            - companies
            - competitors
          additionalProperties: false
        used:
          type: object
          properties:
            companies:
              type: integer
            competitors:
              type: integer
            searches:
              type: integer
            posts:
              type: integer
          required:
            - companies
            - competitors
          additionalProperties: false
      required:
        - watches
        - limits
        - used
      additionalProperties: false
    WatchUpdateBody:
      type: object
      properties:
        paused:
          type: boolean
        weeklyCapFen:
          type: integer
          description: "每周最多花多少（分）：需求订阅默认 500、求购帖订阅默认 1000"
      additionalProperties: false
    WatchView:
      type: object
      properties:
        id:
          type: string
        kind:
          type: string
          enum:
            - company
            - competitor
            - search
            - posts
        label:
          type: string
        productRef:
          anyOf:
            - type: object
              properties:
                id:
                  type: string
                name:
                  type: string
              required:
                - id
                - name
              additionalProperties: false
            - type: "null"
        companyId:
          type:
            - string
            - "null"
        exporter:
          type:
            - string
            - "null"
        country:
          type:
            - string
            - "null"
        product:
          type:
            - string
            - "null"
        request:
          type:
            - string
            - "null"
        weeklyCapFen:
          anyOf:
            - type: integer
            - type: "null"
        spentThisWeekFen:
          anyOf:
            - type: integer
            - type: "null"
        paused:
          type: boolean
        lastProblem:
          type:
            - string
            - "null"
        lastFound:
          description: "需求订阅、求购帖订阅：上次检查找到几家新买家 / 几条新的求购帖（0 是查了没有新的）；还没查过、没跑成是 null"
          anyOf:
            - type: integer
            - type: "null"
        createdAt:
          type: string
          description: "ISO 8601 时间"
        lastCheckedAt:
          anyOf:
            - type: string
              description: "ISO 8601 时间"
            - type: "null"
        nextCheckAt:
          type: string
          description: "ISO 8601 时间"
        signals:
          type: integer
      required:
        - id
        - kind
        - label
        - companyId
        - exporter
        - country
        - request
        - weeklyCapFen
        - paused
        - createdAt
        - lastCheckedAt
        - nextCheckAt
        - signals
      additionalProperties: false
    WorkspaceSearchResponse:
      type: object
      properties:
        companies:
          type: array
          items:
            type: object
            properties:
              companyId:
                type: string
              name:
                type: string
              domain:
                type:
                  - string
                  - "null"
              country:
                type:
                  - string
                  - "null"
            required:
              - companyId
              - name
              - domain
              - country
            additionalProperties: false
        people:
          type: array
          items:
            type: object
            properties:
              key:
                type: string
                description: "联系人编号（p_ 开头；手动记的人 manual: 开头）"
              name:
                type: string
              email:
                type:
                  - string
                  - "null"
              companyId:
                type: string
              companyName:
                type: string
            required:
              - key
              - name
              - email
              - companyId
              - companyName
            additionalProperties: false
        searches:
          type: array
          items:
            type: object
            properties:
              runId:
                type: string
              kind:
                type: string
                enum:
                  - similar
                  - sellers
                  - advertisers
                  - merchants
              request:
                type: string
              startedAt:
                type: string
                description: "ISO 8601 时间"
            required:
              - runId
              - request
              - startedAt
            additionalProperties: false
      required:
        - companies
        - people
        - searches
      additionalProperties: false
