{"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":"同时返回**售价**与**上游 COGS 预估**两项（售价与成本解耦，分开披露）。","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":"竞品数会影响 COGS 预估（domain_intersection 按每对域计费）"}}}}}},"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），零上游调用（COGS = 0），售价按**查询窗口**分档。\n\n**用 POST 而不是 GET**：这是扣费端点 —— GET 会被预取、被爬虫、被代理重试，\n每一种都会在客户没打算花钱时扣掉一笔。\n\n**授权**：未登记在 `tenant_brands` 的品牌 → 403（fail-closed，不做隐式兜底）。\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/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/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**默认供应商**由 `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。⚠️ 下限是**成本口径**：MoR 固定费在小额单上会把有效费率推到 11.9%"},"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/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入账幂等键 = `wh:<provider>:<event_id>`（支付方重试不会多充一笔）。\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"]}}],"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/site_audit","/api/jobs","/api/tenants/me","/api/signup","/api/billing/checkout","/api/billing/webhook","/api/openapi.json","/api/diagnose"]}