{"openapi":"3.1.0","info":{"title":"saler.ai API","version":"v1","summary":"AI 可见度（GEO）与搜索结果数据的交付 API","description":"按次计费的搜索结果 / AI 可见度数据交付 API。\n\n**鉴权**：多数端点要求租户 API key —— `Authorization: Bearer <key>`。\n`/api/v1/diagnose` 与 `/api/v1/openapi.json` 为公开端点（无 key）。\n\n**版本**：`/api/v1` 与不带版本的 `/api` 指向同一份实现（入口处改写路径，不是两套代码）。\n不带 /v1 的路径保留可用，但不承诺兼容窗口；新集成方请一律使用 /api/v1。\n破坏性变更只在新 major 版本中进行：新版本发布后旧版本至少保留 6 个月，期间旧版本只修安全与正确性缺陷，不加新能力。退出前 30 天以响应头 `x-api-version` 与对外文档同时公告。\n响应头 `x-api-version` 回写当前版本，便于运行时断言。\n\n**数据口径**：默认交付**派生/聚合视图**（如按 TLD、按排名区间的分布），\n不交付上游原始明细列表；派生视图附 `attribution`（须保留的署名）。\n原始明细仅对定向合作通道开放（见 docs/data-authorization-terms.md）。\n\n**范围**：本 spec 只收录对外的版本化面；管理面（ADMIN_TOKEN）**有意不收**（见 docs/api.md）。"},"servers":[{"url":"https://api.saler.ai","description":"生产 API 基址（推荐）"},{"url":"https://saler.ai","description":"生产（同源站点）"}],"security":[{"tenantKey":[]}],"tags":[{"name":"meters","description":"计量端点（消耗额度）"},{"name":"quotes","description":"报价端点（免费，不消耗额度）"},{"name":"account","description":"租户自查"},{"name":"onboarding","description":"自助开通与支付回调"},{"name":"public","description":"公开端点（无鉴权，带配额）"}],"paths":{"/api/v1/serp":{"post":{"tags":["meters"],"summary":"执行一次 SERP 查询（计费）","operationId":"runSerp","description":"先扣后调 + 幂等。命中缓存按 30% 价结算，不打上游。","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["engine","query"],"properties":{"engine":{"type":"string","description":"如 google / bing / chatgpt"},"query":{"type":"string"},"market":{"type":"string","examples":["en-US"]},"locale":{"type":"string","examples":["en"]},"freshness":{"type":"string","enum":["live","cached"]},"idempotency_key":{"type":"string","description":"重试时原样回传可避免二次计费"}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/cost_estimate":{"post":{"tags":["quotes"],"summary":"SERP 报价（免费）","operationId":"quoteSerp","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["engine"],"properties":{"engine":{"type":"string"},"market":{"type":"string"},"query":{"type":"string"},"freshness":{"type":"string","enum":["live","cached"]}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/seo_estimate":{"post":{"tags":["quotes"],"summary":"SEO 研究报价（免费，不扣费）","operationId":"quoteSeoResearch","description":"只返回**售价**（按 kind 的固定牌价与定价版本）。","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["keywords","competitor_gap","domain_overview","backlinks"]},"competitor_domains":{"type":"array","items":{"type":"string"},"description":"竞品域数组（≤ 10）"}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/seo_research":{"post":{"tags":["meters"],"summary":"执行一次 SEO 研究（计费）","operationId":"runSeoResearch","description":"只返回数据、不落库（数据归属是租户）。默认返回**派生视图** + `attribution`；原始明细列表对匿名租户 key 不可用（`raw:true` → 403 SCOPE_FORBIDDEN），须走定向合作通道。","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind","domain"],"properties":{"kind":{"type":"string","enum":["keywords","competitor_gap","domain_overview","backlinks"]},"domain":{"type":"string","examples":["example.com"]},"brand":{"type":"string"},"keywords":{"type":"array","items":{"type":"string"}},"competitor_domains":{"type":"array","items":{"type":"string"}},"market":{"type":"string"},"locale":{"type":"string"},"limit":{"type":"integer","minimum":1},"expand_with_ideas":{"type":"boolean"},"idempotency_key":{"type":"string"},"raw":{"type":"boolean","description":"原始明细列表。对租户 key 恒为 403（仅管理/定向通道）。","deprecated":true}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/brand-visibility/{brandId}":{"post":{"tags":["meters"],"summary":"读取品牌 AI 可见度时序（计费，按查询窗口）","operationId":"runBrandVisibility","description":"我方自有的时间锁语料（brand × engine × day），按**查询窗口**分档（$10.80 / $32.20 / $130.60）。\n\n**用 POST 而不是 GET**：这是扣费端点 —— GET 会被预取、被爬虫、被代理重试，\n每一种都会在客户没打算花钱时扣掉一笔。\n\n**授权**：未登记在 `tenant_brands` 的品牌 → 403（fail-closed，不做隐式兜底）。\n**范围闸**：按次计费仅限「已在采品牌」（tier=core，日采样持续进行中）。未由订阅覆盖的\n查询若品牌不在采集范围 → 403 `SUBSCRIPTION_REQUIRED`（引导建订阅）。\n订阅覆盖内的查询价为 $0（`response.subscription.covered=true`）。\n**口径**：可见度得分口径为 vs2；空档日（无采样）以 `null` 呈现，**不是 0**；\n`is_sample` 行不进交付物但会计数并披露。","parameters":[{"name":"brandId","in":"path","required":true,"schema":{"type":"string"},"examples":{"default":{"value":"acme-foods"}}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","enum":[30,90,365],"description":"查询窗口（**定价单元**，只能是登记的档位；不会就近取整）"},"market":{"type":"string","examples":["en-US"]},"prompt_version":{"type":"string","description":"缺省取该品牌**实际有采样**的最新题目版本"},"to":{"type":"string","description":"窗口终点（YYYY-MM-DD，UTC），缺省为今天"},"idempotency_key":{"type":"string"}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/brand-visibility":{"get":{"tags":["account"],"summary":"本租户被授权的品牌清单（免费）","operationId":"listGrantedBrands","description":"同清单即 SoV 的比较集（上限 20 个品牌；截断会在交付物的 comparison.truncated 里披露）—— 未被授权的品牌不会出现在任何交付物里。","responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/brand_visibility_estimate":{"post":{"tags":["quotes"],"summary":"品牌可见度报价（免费，不扣费）","operationId":"quoteBrandVisibility","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","enum":[30,90,365]}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/site_audit":{"post":{"tags":["meters"],"summary":"建技术审计任务（On-Page Lighthouse，计费）","operationId":"enqueueSiteAudit","description":"上游为**异步**任务，因此本端点只**建任务**并返回 `job_id`，用 `GET /api/v1/jobs/{jobId}` 轮询取结果。\n\n**为什么是两段式**：同步形态只能\"等 24 秒然后说还没好\"，而重试 = 重新建任务 = **重新扣费**。job 化之后轮询免费，扣费只发生一次。\n\n**不落库**：结果只在任务的 `result_json` 里（数据归属是租户，不写进按 brand 组织的客户表）。\n**失败必退款**：任何\"未交付结果\"的终态都会全额退款并把 `settled_micro` 写回 0。\n**同幂等键重试**返回**同一个 job_id**（不重复建任务、不重复扣费）。","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","examples":["https://example.com"],"description":"http/https；拒绝 IP 字面量与内部主机名"},"idempotency_key":{"type":"string","description":"缺省按（租户, URL）自动生成；同键重试返回同一任务"}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/jobs/{jobId}":{"get":{"tags":["meters"],"summary":"轮询任务状态与结果（免费）","operationId":"pollJob","description":"仅能查看**本租户**的任务；不存在或非本租户一律 404（不用 403 —— 那会泄漏任务 id 的存在性）。\n\n`status` 非终态时 `result` 为 null，按 `poll_after_ms` 的节奏重试即可（轮询免费）。\n终态 `failed` 表示未交付且**已全额退款**，`settled_micro` 为 0。","parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tenants/me":{"get":{"tags":["account"],"summary":"查看自己的余额、key 列表与账单口径","operationId":"getAccount","description":"tenant_id 取自验签结果，不接受请求参数指定。key 只返回前缀，绝不返回 hash/明文。","responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/subscriptions/me":{"get":{"tags":["account"],"summary":"查看自己的订阅与权益是否正在生效（租户 key，只读）","operationId":"getMySubscription","description":"回答一个问题：**我现在查品牌可见度，到底收不收费** —— 以及不收费/收费的理由。\n\n权益由三个各自独立的事实共同决定，任一不成立都不覆盖：订阅未被取消、未过期、已授权品牌数不超过档位上限。响应把它们摊开：\n· `entitlement_active` —— 当前是否覆盖（**与扣费判定同源**：页面说免费，查询就一定是 $0）；\n· `coverage_reason` —— 不生效时说明是哪一条不成立，并指向该做的动作（续费 / 换支付方式 / 重新订阅 / 撤授权）；\n· `over_limit` + `brands` —— 授权 `granted` 与档位 `limit` 的关系，`remaining` 为剩余名额；\n· `current` —— 当前承载权益的那条订阅（**过期后为 `null`**，记录本身仍在 `subscriptions` 里，`entitled=false`）。\n\n**鉴权 = 租户 API key**：tenant_id 取自验签结果，不接受请求参数指定 —— 只回本租户。\n只读语义：不扣费、不改状态，`read-only` scope 也能调。","responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tenants/me/usage":{"get":{"tags":["account"],"summary":"按天的用量与账单（窗口上限 92 天，超出明确 400，不静默截断）","operationId":"getUsage","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date"},"description":"YYYY-MM-DD；缺省为默认窗口"},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/brands":{"get":{"tags":["brand-onboarding"],"summary":"列出本租户已授权的品牌 + 额度条（租户 key，免费）","operationId":"listBrands","description":"与 `GET /api/v1/brand-visibility` 的分工：那个只回 id/note/granted_at（够判定\"我被授权了哪些\"）；本端点补上**描述、采样状态与可补录项**，并给出**额度条** `{limit, used, remaining}`，让客户自己看清\"我还能再加几个品牌\"，不必来问。\n\n· 刻意不返回的字段：`kind`（客户/竞品的分级）、`stage`（商务阶段）、`note`（内部备注）、`aliases`。`kind=client` 相当于对外宣布\"这家在付钱给我们\" —— 那是保密边界本身，不是可选项。\n· 非订阅租户的 `quota` 为 `mode:\"on_demand\"`（无数量上限，但只能授权已在采品牌）。","responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["brand-onboarding"],"summary":"自助登记一个或多个品牌到自己的租户（租户 key，免 admin）","operationId":"addBrand","description":"**把品牌登记权下放到租户自己的 API key**：订阅客户 claim 到 key 后，调一次本端点即可开始查询，不再需要管理员后台授权。\n\n· 两种入参：单条 `{brand_id,...}`，或批量 `{brands:[{brand_id,...}|\"brand_id\", ...]}`（一次建多个，整批先校验再落库）。\n· `brand_id` 若已在品牌目录（clients）中存在，直接授权；若不存在，按 `tier=core`（进日采样）自动建品牌，**无需 admin 审核**；可附带 `brand`/`domain`/`category` 作为描述补录。\n· 受两道商业纪律约束（与 admin 端点同源）：非订阅租户只能授权 core 品牌；订阅租户授权数不超过档位上限 —— **整批一起判**，超限则整批 400（不会只落一半），并指引撤授权/升档。\n· 鉴权 = 租户 API key：tenant_id 取自验签结果，只能操作自己的授权。","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"提供 brand_id（单条）或 brands（批量）之一。","properties":{"brand_id":{"type":"string","description":"单条模式：品牌标识，如 acme-foods"},"brand":{"type":"string","description":"品牌展示名（自动建时缺省为 brand_id）"},"domain":{"type":"string","description":"品牌主域名（可选，自动建时补录）"},"category":{"type":"string","description":"品类（可选，自动建时补录）"},"note":{"type":"string","description":"授权备注（可选）"},"brands":{"type":"array","description":"批量模式：一次登记多个品牌（元素为对象或纯 brand_id 字符串）。与 brand_id 二选一。","items":{"oneOf":[{"type":"string"},{"type":"object","required":["brand_id"],"properties":{"brand_id":{"type":"string","description":"品牌标识"},"brand":{"type":"string","description":"品牌展示名（可选）"},"domain":{"type":"string","description":"品牌主域名（可选）"},"category":{"type":"string","description":"品类（可选）"},"note":{"type":"string","description":"授权备注（可选）"}}}]}}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/brands/{brandId}":{"delete":{"tags":["brand-onboarding"],"summary":"撤销自己租户对某个品牌的授权（租户 key，幂等）","operationId":"removeBrand","description":"重复撤销返回 `revoked:false`（幂等），但如实告知\"这次没有改动任何行\"。","parameters":[{"name":"brandId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["brand-onboarding"],"summary":"自助补录品牌描述（租户 key）—— 只能填空缺，不能改已有值","operationId":"patchBrandDescription","description":"**只补空缺、不覆盖已有值**。原因是 `clients` 是**跨租户共享**的品牌目录：peer 品牌会出现在所有租户的报告里，若允许任意租户改写已有 `category`，一个租户就能悄悄改变其他所有租户的报告口径与问题集。分工因此是：**客户补遗漏，管理员做修改**（与登记时\"仅当字段为空才补录\"同一纪律）。\n\n· 客户侧只能补 `domain` / `category`，且仅当该字段当前为空。\n· 字段已有值 → **409**（附 `details.blocked_fields`）。\n· 传其它字段（`brand`/`aliases`/`kind`/`tier`/`stage`/`market`）→ **400**。\n· 品牌不在本租户授权范围内 → **404**（防止任意租户改写共享目录里的任意品牌）。\n· 请求体里的可补字段清单：见 `GET /api/v1/brands` 每条的 `self_editable_missing`。","parameters":[{"name":"brandId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string","description":"品牌主域名（仅当当前为空时可补）"},"category":{"type":"string","description":"品类（仅当当前为空时可补）"}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/signup":{"post":{"tags":["onboarding"],"summary":"自助开通（公开，但**默认关闭**）","operationId":"signup","security":[],"description":"开关关闭时返回 501 CAPABILITY_DISABLED。开放后：name 与 email 必填（email 是后续唯一触达方式）。明文 key 只在此响应出现一次。受两层配额限制（来源级 + 全局级），超限 429 且带 Retry-After。","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string","maxLength":80},"email":{"type":"string","maxLength":160,"format":"email"}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/checkout":{"post":{"tags":["onboarding"],"summary":"创建在线充值会话（MoR checkout，租户 key）","operationId":"createCheckout","description":"为当前租户创建一笔托管收银台会话，返回 `checkout_url` 供客户完成付款。\n\n**鉴权 = 租户 API key**：充值必须绑定到具体租户，不能匿名。\n**金额**：`amount_usd`（两位小数）或 `credit_micro`（整数 micro_usd）二选一，区间 $20–$5000，须为整分。\n**入账**：付款完成后由支付方回调 `POST /api/v1/billing/webhook/{provider}` 自动入账；\n本端点**不扣费**。\n**建单即登记**：在把 `checkout_url` 交出去**之前**，会先把 `(provider, transaction_id, 我方报价)` 落进本地订单表 `checkout_sessions` —— 回调据此比对（见 webhook 的「本地订单绑定」）。因此若登记失败（或上游返回了一个已登记过的交易 ID）本端点直接失败：宁可客户这次拿不到链接，也不产生一笔**无法绑定的收款**。响应字段 `order_bound` 指示是否已登记。\n**默认供应商**由 `SALER_BILLING_PROVIDER` 配置（paddle / creem / waffo）；\n该供应商的 webhook 自动入账未就绪（或对应 API 密钥未配）时返回 501，绝不「收钱不入账」。","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount_usd":{"type":"number","minimum":20,"maximum":5000,"examples":[20],"description":"充值金额（USD，两位小数）；区间 $20–$5000，须为整分。⚠️ 下限是支付通道的费率约束：固定费会让小额单的有效费率过高"},"credit_micro":{"type":"integer","minimum":20000000,"maximum":5000000000,"description":"要充值的额度（micro_usd）；与 amount_usd 二选一，且必须是最小货币单位（分）的整数倍"}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/subscribe/public":{"post":{"tags":["onboarding"],"summary":"订阅自助购买（匿名，造影子租户；可下单即带品牌）","operationId":"createPublicSubscription","security":[],"description":"**免注册 / 免 API key** 的订阅入口：调用即按其来源 IP 造一个影子租户 `pub_<uuid>`，返回 MoR 收银台 `checkout_url` 供完成付款。\n\n**档位**：`tier` 取固定档位之一 —— `starter`（$29/月，≤3 品牌）、`growth`（$99/月，≤10 品牌）、`scale`（$199/月，≤20 品牌）。\n**品牌（可选，多品牌一次成型）**：可传 `brands`（≤ 该档 `brand_limit`）声明要监测的品牌；付款后凭 `request_id` 调 `POST /api/v1/billing/self-serve/claim` **领 key 时自动授权**这些品牌，客户买完即用，无需逐条调 `POST /api/v1/brands`。品牌不在目录时按 `tier=core` 自动建（进日采样）。\n**订阅生效**由 MoR 回调驱动订阅状态机（无需人工干预）；claim 时订阅未生效返回 `pending`。\n**匿名配额**：匿名来源 IP 限流 qps 6。","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["tier"],"properties":{"tier":{"type":"string","enum":["starter","growth","scale"],"description":"订阅档位"},"brands":{"type":"array","description":"下单即声明要监测的品牌（可选，数量不得超过该档品牌上限）；claim 领 key 时自动授权。元素为 brand_id 字符串或 {brand_id,...} 对象。","items":{"oneOf":[{"type":"string"},{"type":"object","required":["brand_id"],"properties":{"brand_id":{"type":"string"},"brand":{"type":"string"},"domain":{"type":"string"},"category":{"type":"string"},"note":{"type":"string"}}}]}}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/checkout/public":{"post":{"tags":["onboarding"],"summary":"在线充值会话（匿名自助，造影子租户）","operationId":"createPublicCheckout","security":[],"description":"**免注册 / 免 API key** 的在线充值入口：调用即按其来源 IP 造一个影子租户 `pub_<uuid>`，返回 `checkout_url` 供完成付款。\n\n**金额**：`amount_usd`（两位小数），实际区间由 `GET /api/v1/billing/checkout/limits` 动态返回（默认 $20–$5000，后台可配）；须为整分。\n**入账与绑定**：与租户版完全一致 —— 付款后由支付方回调自动入账，订单在建单时即登记，查无此单 / 租户不符 / 额度不符一律拒绝入账。\n**领取密钥**：付款后凭 `request_id` 调 `POST /api/v1/billing/self-serve/claim` 领取明文 API key（仅此一次）。\n**匿名配额**：匿名来源 IP 限流 qps 6；本地用 `request_id` 反查激活，避免同一笔付款重复造租户。","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount_usd"],"properties":{"amount_usd":{"type":"number","minimum":20,"maximum":5000,"examples":[20],"description":"充值金额（USD，两位小数）；实际区间以 GET /api/v1/billing/checkout/limits 为准（后台可配）"}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/billing/checkout/limits":{"get":{"tags":["onboarding"],"summary":"在线充值限额（公开，动态）","operationId":"checkoutLimits","security":[],"description":"返回当前生效的在线充值最小 / 最大额度（USD）。额度由后台配置（默认 $20–$5000），改完即时生效；前端首页据此动态放开输入框边界。","responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","properties":{"min_usd":{"type":"number"},"max_usd":{"type":"number"}}},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}}}}},"/api/v1/billing/webhook/{provider}":{"post":{"tags":["onboarding"],"summary":"支付回调（公开，HMAC 验签）","operationId":"billingWebhook","security":[],"description":"支付方（MoR）调用，无法携带我们的 ADMIN_TOKEN，安全性完全由 HMAC 验签承担。\n签名对**原始报文字节**计算（不要重新序列化 JSON）。\n\n**generic（本服务定义的规范契约）**：头 `x-saler-signature: t=<unix秒>,v1=<hex>`，被签串 `<t>.<rawBody>`，事件字段 `id` / `type` / `data.tenant_id` / `data.amount_micro|amount_minor`。\n**paddle（Paddle Billing）**：头 `paddle-signature: ts=<unix秒>;h1=<hex>`（分号分隔），被签串 `<ts>:<rawBody>`，事件字段 `event_id` / `event_type`（仅 `transaction.completed` / `transaction.paid` 入账，其余事件 ack 后忽略）/`data.custom_data.tenant_id`；金额取 `data.custom_data.credit_micro`，回退 `data.details.totals.grand_total`（最低单位字符串，此时**必须**带 `data.currency_code`）。\n\n时间戳容忍窗口 ±300 秒；时间戳进签名，过期报文不可被刷新。\n**入账幂等键 = `pay:<provider>:<支付侧交易 ID>`，且键不含租户** —— 一笔付款只可能入一个账户。\n⚠️ 它**不是** `wh:<provider>:<event_id>`：Paddle 对同一笔交易会先后投递 `transaction.paid` 与 `transaction.completed`（**event_id 不同、data.id 相同**），按事件 ID 去重等于给同一笔钱发两张入账凭证。`event_id` 只承担\"投递幂等\"，入账唯一性由交易 ID 承担。\n**本地订单绑定**：订单由本服务创建的供应商（`paddle` / `creem`）在入账前会比对 `checkout_sessions` 里登记的**我方报价**：查无此单、租户不符、额度不符，一律**拒绝入账**并返回结构化错误码（`ORDER_NOT_FOUND` / `ORDER_TENANT_MISMATCH` / `ORDER_AMOUNT_MISMATCH`）待人工核对 —— 既不以回调为准、也不以本地为准。响应字段 `order_bound` 说明本次是否真的做了比对（`false` ⇒ 该供应商的订单不由本服务创建，防线退为\"同一份报文的内部自洽校验\"）。收到钱的补录路径：`POST /api/billing/sessions`（ADMIN_TOKEN，管理面、不带 `/v1`）。\n未配置对应密钥（`PAYMENT_WEBHOOK_SECRET` / `PADDLE_WEBHOOK_SECRET`）时该供应商入口 501（绝不\"无密钥即放行\"）。\n未知币种 / 缺 event id / 缺 tenant_id / 走 totals 回退却缺 currency_code / 业务状态非成功态 / 额度超过实付总额 —— 一律拒绝（不做隐式汇率、不猜归属、不猜单位、不收自相矛盾的报文）。","parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","enum":["generic","paddle","creem"]}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["id","type"],"properties":{"id":{"type":"string","description":"事件 id（幂等依据，缺失即拒绝）"},"type":{"type":"string","description":"事件类型；入账类如 payment.succeeded / invoice.paid"},"data":{"type":"object","properties":{"tenant_id":{"type":"string"},"amount_micro":{"type":"integer","minimum":1,"description":"micro USD（优选）"},"amount_minor":{"type":"integer","minimum":1,"description":"最小货币单位（如分）；与 amount_micro 二选一"},"currency":{"type":"string","enum":["USD"]},"note":{"type":"string"}}}}}}}},"responses":{"200":{"description":"成功。统一封套：`{ code:\"OK\", data:<load>, meta:{ locale } }`","content":{"application/json":{"schema":{"type":"object","required":["code","data"],"properties":{"code":{"type":"string","enum":["OK"]},"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"locale":{"type":"string"}}}}}}}},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/diagnose":{"get":{"tags":["public"],"summary":"免费 GEO 诊断（公开，带配额）","operationId":"diagnose","security":[],"description":"零认证获客入口。两层配额（来源级 20/60 分钟 + 全局级 2000/60 分钟），超限 429 且带 Retry-After。","parameters":[{"name":"domain","in":"query","required":true,"schema":{"type":"string"}},{"name":"brand","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"诊断结果 JSON"},"400":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/diagnose/{domain}/card":{"get":{"tags":["public"],"summary":"诊断结果卡片（HTML，公开，带配额）","operationId":"diagnoseCard","security":[],"parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string"}},{"name":"brand","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"HTML 卡片","content":{"text/html":{}}},"429":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"统一错误封套（code / message / retryable / action；HTTP 状态码由 error→status 映射表决定）","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/openapi.json":{"get":{"tags":["public"],"summary":"本规格（公开）","operationId":"getOpenApiSpec","security":[],"responses":{"200":{"description":"OpenAPI 3.1 文档","content":{"application/json":{}}}}}}},"components":{"securitySchemes":{"tenantKey":{"type":"http","scheme":"bearer","description":"租户 API key。由管理面签发或自助开通获取；只存哈希，遗失只能重新签发。"}},"schemas":{"Error":{"type":"object","required":["code","message","retryable"],"properties":{"code":{"type":"string","description":"机器可读错误码","examples":["INVALID_PARAMS","UNAUTHORIZED","SCOPE_FORBIDDEN","INSUFFICIENT_CREDITS","RATE_LIMITED","CAPABILITY_DISABLED","NOT_FOUND"]},"message":{"type":"string","description":"英文说明"},"message_zh":{"type":"string","description":"中文说明"},"retryable":{"type":"boolean"},"action":{"type":"string","description":"给调用方的下一步建议（可直接展示）"},"retry_after_ms":{"type":"integer","description":"限流时可用的建议等待毫秒数"},"details":{"type":"object","additionalProperties":true}}}}},"x-saler-version-policy":{"version":"v1","canonical_prefix":"/api","versioned_prefix":"/api/v1","unversioned_policy":"不带 /v1 的路径保留可用，但不承诺兼容窗口；新集成方请一律使用 /api/v1。","deprecation_policy":"破坏性变更只在新 major 版本中进行：新版本发布后旧版本至少保留 6 个月，期间旧版本只修安全与正确性缺陷，不加新能力。退出前 30 天以响应头 `x-api-version` 与对外文档同时公告。","response_header":"x-api-version"},"x-saler-versioned-prefixes":["/api/serp","/api/cost_estimate","/api/seo_estimate","/api/seo_research","/api/brand_visibility_estimate","/api/brand-visibility","/api/brands","/api/site_audit","/api/jobs","/api/tenants/me","/api/signup","/api/billing/checkout","/api/billing/webhook","/api/openapi.json","/api/diagnose"],"x-saler-mcp":{"endpoint":"/mcp","protocol":"JSON-RPC 2.0 over Streamable HTTP","protocolVersion":"2025-06-18","auth":"tenantKey (Bearer)","discovery":"/.well-known/mcp.json","failureSemantics":"MCP 工具级失败统一走 HTTP 200 + result.isError:true（JSON-RPC 惯例：失败信号在 body 的 code 字段，不映射非 200 状态码）。agent 必须在 body 读 code 自行决策，不能只看 HTTP 状态。CAPABILITY_DISABLED 即此形态（不是 501）。","tools":[{"name":"cost_estimate","metered":false,"disabled":false},{"name":"serp","metered":true,"disabled":false},{"name":"geo_diagnose","metered":false,"disabled":false},{"name":"onpage_audit","metered":true,"disabled":false},{"name":"backlinks","metered":true,"disabled":true},{"name":"full_diagnosis","metered":true,"disabled":false}]}}