--- name: mia-geo description: Measure and improve brand visibility across Chinese AI models with MIA GEO. --- # MIA GEO Agent API ## Billing visibility All customer and Agent billing values use MIA Points. Provider prices and fiat accounting fields are internal only and are never returned or documented on this public surface. Owned Run list/status responses expose optional `billing` receipts, schema `mia-geo-run-billing/v1`: `scope`, `reservation_state`, `reserved_points/units`, `state`, and `charged_points/units`. Reservations are distinct from charges. Only the existing Credit owner's confirmed ACK supplies a charge; pending or failed decisions and legacy missing receipts remain unknown. A confirmed Credit ACK whose accounting tail is pending is explicitly `accounting_pending`. Expired/released holds are zero only when acknowledged; a release reporting `already_settled` does not prove zero. Aggregate children without their own hold have no invented per-child charge. This read never reserves, settles, retries or changes a ledger, and exposes no hold/Account IDs or internal supplier costs. > 生成式引擎优化(Generative Engine Optimization)。测量并提升一个品牌在中文大模型 > (千问 / DeepSeek / 智谱 GLM / Kimi / 腾讯混元大模型 / MiniMax / > 小米 MiMo / 豆包)回答里的可见度:出现率、排名、竞品格局、 > 真实引用、趋势与投放机会,并把"监测→洞察→成稿→投放→再检验"闭环产品化。 > 分析单位是「主词(Topic)」——同一品牌不同关键词独立统计竞品与长尾。 ## 这是什么 面向广告/市场团队的品牌 AI 可见度平台。给定品牌 + 主词,MIA GEO 用一批自然语言问题 分别去问多个大模型,逐条判定品牌是否被提及、排第几、被如何描述,并用**确定性字面/别名守卫** 给出正文证据;豆包(可用时)或工具模型只判断语境和发现未配置品牌,不能在没有正文证据时制造 目标品牌/竞品命中。最后汇总成可见度指标、竞品格局、来源分析、技术稽核与投放/内容建议。 ## 架构(前后端已合并为单工程单 Pod,同源提供以下路径) - `/` 营销落地页 - `/agent` 人类版接入指南 - `/llms.txt` 本 canonical 文档的机器可读派生版 - `/skill.md` 本 canonical 文档的可下载 Skill 派生版 - `/agent-contract/v1.json` Mia Agent 当前消费操作的版本化机器合同(从 FastAPI OpenAPI allow-list 派生) - `/agent-capabilities/v1.json` 全部普通客户 API 的只读机器投影;Admin、浏览器身份、内部拉取与 legacy Skill 路径显式排除 - `/app` 交互式工作台(人类界面,原生前端) - `/api/skill/*` 自包含候选规划 API;legacy visibility/run 路由保留但测量固定 409,可信测量走项目 v5 Run - `/api/*` Customer business APIs accept browser OIDC, Account-bound PAT, or admitted first-party Service + exact Account. - `/health` 存活探针(无需鉴权) - `/ready` 就绪探针;数据库 schema、Platform gateway 或必需模型不可用时返回 503 Browser result handoff: link to `/app/?brand=&keyword=&monitoring_profile_id=` with the existing target-market and language scope. The browser independently verifies its current human and Account; a URL or initiating User is never authorization. Selected existing-result readers include Account-owned results from that exact Account while preserving historical personal ownership. Brand list rows expose `read_only` for human views of Account-owned results; edits and new measurement admission remain under the existing mutation policy. An inaccessible explicit scope is not replaced with a different brand, Topic or monitoring profile. Service business calls retain their existing exact-Account authority and operation coverage. ## 非 Agent 公共路径与边界 以下路径对网络可见,但不属于可由 Agent 调用的项目能力,不能从本合同的 “Agent 可调用路由清单”推断其可用于业务操作: - `/api/health` 返回 `{"ok":true}`,只用于兼容 API 子应用的存活探针;`/health` 是生产探针首选。 - `GET /api/ready` mirrors `/ready` for the mounted API and returns only bounded `ready|unavailable` dependency states; it is not a customer-data operation. - `/api/auth/config` 只返回前端启动所需的 `{auth_mode,auth_disabled,login_url,silent_sso}`; `/api/auth/login`、`/api/auth/silent`、`/api/auth/callback/mia`、`/api/auth/logout` 是浏览器 OIDC 导航, 不返回 Agent 业务数据。 - `/api/auth/_test_login` 只在 `APP_ENV=test|testing` 且带测试密钥时存在,生产环境固定不可用。 - `/api/v1/mia/dashboard/*` is the dedicated MIA Data Platform pull surface. It accepts only an admitted calling Service credential with explicit immutable Account and optional initiating-User audit attribution, resolves terminal Account Service Context, and scopes every query to that Account. `scope=me` adds the resolved initiating User predicate. PATs, identity selectors, static pull credentials, relayed human credentials, and alternate protocols are rejected without fallback. ================================================================ # 第一部分 · 自包含 Skill API ================================================================ Base:`https://mia-geo-sit.ambr.io` 完整能力投影:`https://mia-geo-sit.ambr.io/agent-capabilities/v1.json`。它从当前 FastAPI OpenAPI 自动生成, 穷举分类普通客户与非客户操作,并保留请求/响应 schema、保守 effect class 和 GEO 权威授权边界。 它不执行操作,不接受 URL、Header 或凭证,也不改变现有前端和 REST API。 机器合同预览:`https://mia-geo-sit.ambr.io/agent-contract/v1.json`。它只发布 Mia Agent 当前消费的 14 个 method/path 操作,不是通用 HTTP 工具目录;消费方仍必须使用自己的显式 capability allow-list。合同包含 OpenAPI 请求 schema 与响应元数据、资源归属、鉴权、effect class、并发/幂等现状、生命周期与错误恢复分类;旧路由 尚未声明成功响应模型时会明确标为 `untyped_openapi_pending_issue_8`,消费方不得据此生成响应类型。v1 只允许加法兼容, 破坏性变化必须发布 v2 并至少提前 90 天通知。当前 run start 尚未提供请求幂等键,合同明确标记为 `unsupported_pending_mia_geo_issue_9`,调用方遇到不确定网络结果必须先读权威 run status,不能盲目重启。 Agent configuration recovery: `LongtailIn.intent` publishes the canonical values `recommendation`, `comparison`, `discovery`, `scenario`, and `purchase_decision`; omit it or send `null` when unknown. Legacy intent aliases remain accepted for compatibility. For an existing Topic, `POST /api/brands/{brand_id}/keywords` defaults to `merge`: it appends questions but preserves competitors, and reports `competitor_selection.disposition=unchanged`. HTTP success does not mean competitors changed. Omit `expected_keyword_id` for this default. Explicit `null` requires a new Topic under the brand identity lock and rejects an existing Topic with `geo_setup_context_changed`; positive IDs keep the existing exact identity assertion. A new-only panel rejects duplicate or blank identities with `provided_question_panel_invalid` and rolls back its configuration transaction. Question wording retains supplied punctuation and internal whitespace; NFKC, case and whitespace normalization is used only for identity deduplication. To recover an incomplete baseline using the narrow contract, first read `GET /api/keywords/{keyword_id}`, retain the complete intended question and competitor sets, then submit both with `question_selection_mode=replace` and the same Topic, market, and language. This atomically applies both sets; omissions remove active selections. Check the returned question and competitor receipts, then re-estimate before starting a Run. For a competitor-only edit, the full capability catalog also publishes `PUT /api/keywords/{keyword_id}/competitors`; it replaces the complete competitor set without replacing questions. Use only confirmed competitors and preserve the documented confirmation and ownership requirements. Neither path authorizes a paid Run. 路由仍保留 3 个同步端点(`visibility` / `plan` / `run`)、2 个异步版和 1 个 job 轮询, 但当前只有不产生客户指标的 `plan` 可执行。visibility/run 同步与异步创建、既有 job 结果均因缺少 v5 证据链固定 409;测量必须进入项目 Monitoring Profile + v5 Run。`plan` 自包含、不落业务资源, 与四个 utility generation 路由共用 `generate_assist` 一口价(默认 15 MIA Points,动态定价可覆盖); 被门禁的测量路由不调用 provider、不预留或消耗 MIA Points。 历史同步测量预算 `SKILL_SYNC_BUDGET_S` 的默认值为 `50s`;在上述测量门禁期间不再执行或产生部分结果。 既有 legacy SkillJob 行仍按 `SKILL_JOB_TTL_MIN` 默认保留 `60 分钟` 后惰性清理,但门禁期间不能据此读取旧测量结果。 ## 鉴权(必需,fail-closed) `/api/skill/*` 需认证并解析不可变 Platform User 与当前普通 Platform Account authority。 MIA GEO 以中台 Platform User 与 Platform Account 为唯一身份权威;GEO 只保留内部数据绑定 与资源 owner 边界。当前 Platform Account 同时用于请求授权、Points、下游服务归因、遥测与异步恢复。 GEO 本地 `account_id` 仅是内部物理存储主键,不是独立用户体系或钱包 selector。 个人 PAT: Authorization: Bearer mia_pat_... PAT 在签发时已绑定一个 Account,不发送任何身份 selector 或代用身份 header。 首次 PAT 调用会根据中台验证的 User+Account 自动完成 GEO 后台初始化。 不要求事先登录 GEO;可选展示邮箱为空不影响授权、资源归属、Points 或 Usage 归因。 项目 Run 与批次诊断的 Points 预估同样只使用当前 Platform Account;邮箱缺失或变化 不会把预估降为 0,也不会使已签名且仍有效的预估失效。 - A browser route using `current_user` with no session still returns **`401 {"detail":{"error":"redirect_required","reason_code":"not_onboarded",...}}`**, so the browser can redirect to Platform login. Skill routes require the PAT above. - A supplied invalid, malformed, or expired PAT returns **`401 {"detail":{"error":"invalid_token"}}`**. Identity selector and relayed-human headers are rejected with **`400 {"detail":{"error":"unsupported_identity_selector"}}`**. - If Platform authority is temporarily unavailable, return **`503 {"detail":{"error":"authority_unavailable"}}`**. If the validated Platform identity cannot be bound safely to existing GEO data, return **`403 {"detail":{"error":"account_access_required"}}`**. - Static keys, `X-API-Key`, email/Tenant selectors, identity selector headers, and alternate introspection protocols are unsupported; authority is never inferred from email, Tenant, or local history. - GEO 只消费官方客户端返回的 terminal PAT context;Platform wire schema、URL、header、错误与响应类型均不得进入 GEO 业务模块。缺失、失活、Product 未准入、Account 不匹配或上游不可用均 fail closed。 - MIA Dashboard Pull 不是通用 Agent 凭证:只接受 audience Product `mia-geo` 且调用方 `service_id=mia-platform:mia-platform`、`service_product=mia-platform`、 `service_name=mia-platform` 的 terminal Account Service Context;所有查询按解析得到的 Account 隔离,`scope=me` 才额外按解析得到的 User 过滤。 ## 1) POST /api/skill/visibility —— 暂停测量 该 legacy 自包含端点没有 RunManifest、持久化 Attempt/Response/JudgementRevision/MetricResult、95% 区间 和 Trust Receipt,因此当前固定返回 HTTP 409: {"detail":{"error":"versioned_skill_measurement_unavailable","message":"…请使用 Monitoring Profile 创建 v5 Run"}} 它不会调用模型/搜索、不会预留或消耗 MIA Points,也不会返回 mention/rank/sentiment 等无凭证数字。 请使用 Monitoring Profile 与 `POST /api/keywords/{keyword_id}/runs` 创建 v5 Run,再读取 `/runs/{id}/manifest`、`/runs/{id}/metrics` 和 receipt/evidence 端点。 ## 2) POST /api/skill/plan —— AI 生成竞品 + 长尾问题候选 请求(仅 `brand` 必填,`url` / `industry` 默认空字符串): { "brand": "你的品牌", "url": "https://example.com", "industry": "B2B 营销" } 响应:`{brand, url, industry, competitors:[{name,aliases}][≤10], longtails[20], longtail_candidates[20], question_design, note}`。 返回的是**候选**,请交用户确认/修改后写入项目 Monitoring Profile 并创建 v5 Run。长尾按语义相关而非强制逐字包含主词; 生成目标是中立地观测品牌是否自然出现,行业仅为背景,不默认锁定二级类别。 发现/场景问题优先表达用户真实需求;用户明确选择的主题类别、比较条件、市场和语言仍是限制。 问题或模型范围改变后应作为新基线,不能直接声称与旧样本可比。 兼容字段 `longtails` 仍为纯字符串数组;`longtail_candidates[]` 与 `POST /api/generate/longtails` 请求必须包含 `market` 与 `language`;`target_count` 是希望返回的候选数, `required_count` 是用户本次测量真正需要的最低可用数且不得大于 `target_count`;每个候选返回 `text/intent/cluster/topic_anchor/relevance_score/relevance_reason/demand_signal`。 请求体可选 `exclusions: string[]`(负面关键词,问句中不得出现这些词/短语;仅本次生成有效,不持久化)。 生成固定走 BytePlus Seed `seed-2-0-mini-260428`(关闭 thinking 并使用 Responses JSON object), 环境变量不能切换模型。竞品等短推荐在总预算内最多对同一 Seed 模型重试两次; 长尾生成最多调用同一个 provider 五轮且共享 120 秒应用层截止时间,并在每次模型返回后解析与筛选; 达到 `required_count` 且五层准入合格后立即返回,不会因可选候选未达到 `target_count` 而误报失败; 截止时间耗尽返回可重试的 `recommendation_generation_timeout`,释放同一 Points hold。 进程启动和 `/ready` 只验证确定性的 BytePlus 凭证与固定模型配置,不发起 provider 请求; provider 可达性、时延与内容质量由客户明确发起的生成请求验证,避免部署与探针产生付费流量。 生成后会做主题锚点、品牌泄漏、负面词、精确重复、语义重复和 intent/cluster 校验;语义重复指 服务于同一个用户决策且大概率得到相同回答结构的问题,不会把预算、人群、地域、场景或比较维度不同的问题合并。 结果不合格或覆盖不足时不再发起修复生成。模型完全不可用时不返回伪装成 AI 推荐的模板问题, 客户生成接口返回可重试的 `recommendation_generation_unavailable`。 `POST /api/generate/competitors` 会在模型返回后先删除中英文圆括号及其中的模型说明,再强制剔除目标品牌本身、 相同规范名、常见公司全称和地区后缀变体(例如目标「宏利香港」不会返回「宏利金融有限公司」), 并按清洗后的规范名去重,最多返回 10 个。模型被要求把竞品品牌主体放在规范名;`aliases` 只允许指向完全 相同实体的中英文、繁简体、官方缩写或词序变体,最多返回 3 个目标市场相关的建议识别名。中国大陆、香港、 澳门市场会确定性移除日文、韩文等无关脚本。产品、服务、业务线、子品牌、母公司与地区分支均为独立实体, 不能作为别名;服务端不会因 `Beauty`、`美妆`、`Wealth` 等后缀自动合并实体,也不做编辑距离或语义模糊匹配。 ## 3) POST /api/skill/run —— 暂停测量 与 `/skill/visibility` 相同,当前固定返回 409 `versioned_skill_measurement_unavailable`,不会调用 provider、不会计费,也不会返回无证据的 mention rate。 `POST /api/skill/plan` 仍可生成候选;确认候选后应写入品牌/Topic/Monitoring Profile 并创建 v5 Run。 ## 4) POST /api/skill/run/async —— 暂停创建 固定返回相同 409;不创建 SkillJob、不预留 MIA Points。 ## 5) POST /api/skill/visibility/async —— 暂停创建 固定返回相同 409;不创建 SkillJob、不预留 MIA Points。 ## 6) GET /api/skill/jobs/{job_id} —— legacy 结果关闭 先执行原有身份归属检查;visibility/run 类型的既有 job 随后固定返回相同 409,不返回历史临时 KPI。 部署前已排队且未完成的 legacy job 会 fail-closed、释放预留额度,不再调用模型或搜索。 New jobs persist the immutable Platform User + Account principal. A worker restores and validates both values before reading business fields, calling a provider, or settling Points. Polling must match both the current request User and Account; historical rows missing a principal are not backfilled or executed. Opaque principal IDs never appear in customer responses; `actor_email` is optional presentation data only. ## 成本警告:按 MIA Points 计费、不限流 `/skill/plan` 与 `/generate/briefs|topics|competitors|longtails` 使用 `generate_assist` 一口价从当前 MIA Platform Account wallet 扣费(默认 15 MIA Points,动态定价可覆盖)。 每次请求在 provider 调用前建立 Account-owned hold;成功结算,模型、超时或输入验证失败释放。 调用方应发送 1–160 字符的 `Idempotency-Key`:同一 User + Account + 路由下,同键同 payload 在 60 分钟保留期内重放已保存结果,不再次调用 provider 或重复冻结;同键不同 payload 返回 409 `idempotency_conflict`。网络结果不确定时保留原 key;收到明确生成失败后,如需重新生成应使用新 key。 未发送该头的兼容调用按规范化 payload 形成隐式 key;官方网页客户端始终发送显式 key。 visibility/run 同步与异步端点在 409 门禁前不预留、不扣费;部署前遗留的未完成 job 会释放旧 hold。 业务结果产出后,settle/release 决策会先持久化,再用同一 hold 幂等提交;只有 durable intent 已经提交后,中台超时、非 2xx、Pod 重启或多 Pod 接管才会后台重试到确定终态。若已有 hold 但 durable intent 无法持久化,请求使用既有 `503 redirect_required/platform_unavailable` 信封显式失败, 不会返回一个缺失结算事实的业务成功。 要执行测量,请先让用户确认问题、模型、市场、语言和测量模式,再通过项目 API 创建 Monitoring Profile 与 v5 Run;其 estimate/预留/结算规则见本合同项目部分。 ## 错误码(skill 端点) | 码 | 含义 | |---|---| | `200` | `/skill/plan` 成功返回候选 | | `400` | `unsupported_identity_selector`:请求使用了不受支持的身份 selector header | | `401` | `invalid_token` 或 `invalid_service_credential` | | `402` | `/skill/plan` 额度不足——`{"error":"redirect_required","reason_code":"insufficient_credit",...}` | | `403` | `account_access_required`;或 job 的 User + Account principal 不匹配,或缺 Account wallet authority(`billing_account_authority_required`) | | `404` | `GET /skill/jobs/{id}` 的 job 不存在或已过 TTL | | `409` | visibility/run 同步、异步或 legacy job 结果:`versioned_skill_measurement_unavailable`;或 plan 幂等冲突/执行中 | | `422` | 请求体不符合 schema;鉴权依赖先执行,合法测量请求随后仍返回 409 | | `503` | `authority_unavailable`——Platform authority 暂不可用;或已有 hold 的结算事实无法持久化时返回 `redirect_required/platform_unavailable` | ## 八模型目录与统一接入方式 产品目录固定为 `qwen`(千问)· `deepseek`(DeepSeek)· `glm`(智谱 GLM)· `kimi`(Kimi)· `hunyuan`(腾讯混元大模型)· `minimax`(MiniMax)· `mimo`(小米 MiMo)· `doubao`(豆包)。`GET /api/models` 始终按该顺序返回全部 8 行,并显式给出 `acquisition_tier` 和 `search_provider`; **百度/文心一言(issue #614):** 文心一言不在新建 Run 的可选目录中。GEO 当前没有经过验证的百度原生搜索或消费者产品观测通道;用外部提供的材料给模型 API 提供 grounding,不能证明其在文心 App 或百度 AI 搜索中的可见度。BytePlus 适配器当前只服务豆包。新请求的 `models` 数组包含 `ernie` 时返回 HTTP 422 `model_selection_invalid`,并将 `ernie` 列入 `unavailable_models`;不会替换成其它模型。保留 ERNIE adapter identity 仅供已冻结 Run 和历史报告使用,既有 Manifest 与结果不变。 无真实 provider 配置时行仍保留为不可用,不会静默启用假数据。仅本地或测试显式设置 `USE_MOCK=1` 才使用 mock 适配器。 浏览器新建检索在单一目标市场默认使用 `controlled_benchmark`“多模型联网对比”,展示并允许选择所有 `accepts_external_sources=true` 的当前可用模型;当前客户/Agent 新建 GEO 批次统一使用受控基准。 历史原生 Profile/批次固定沿用 `native_observation`,不把 OpenRouter 或 MIA 联网材料冒充厂商原生搜索; OpenRouter server tool 只在该冻结观测路径被调用,但始终标记为第三方 API grounding,不进入 vendor-native 指标。两种 Run 的指标、来源控制和分母保持独立。 底层 OpenRouter 适配器仍保留 `gpt`、`claude`、`grok`、`gemini` 兼容 key,供既有部署显式配置; 它们不属于国内八模型目录,也不出现在固定的 `/api/models` 八行目录中。 - 新客户 Run 只有一个统一的 `controlled_benchmark` 路径:客户选择的一个或多个模型共享同一份冻结 MIA source pack, 客户端不再选择“多模型联网对比”或“厂商原生观测”。模型 provider 在 Run admission 时确定并写入 RunManifest;除豆包外的模型只从 OpenRouter 装载,不再回退到同 key 的原厂或公司代理适配器。 一旦 Run 创建便在运行中不静默切换 provider,失败按冻结 provider 的重试策略处理。 - 豆包是唯一原厂直连模型,使用 BytePlus;其它目录模型未在 OpenRouter 配置时保持不可用。 旧 `native_observation` Run/Profile/Task 仍可读取和按冻结配置继续执行;普通客户或 Agent 新建 Profile、Run、Task 时请求该模式返回 422 `native_observation_read_only`。编辑旧原生任务是迁移边界: 未显式传模式也会把未来 occurrence 改为统一受控 Profile,已冻结的历史 Run 不变。 - `doubao` 的正式模型走 **BytePlus ModelArk Responses API**,界面标识为「豆包(BytePlus ModelArk, 非消费端 App)」。生产使用 `ENABLE_DOUBAO=1` + `BYTEPLUS_API_KEY` + `DOUBAO_MODEL`,端点固定为 `https://ark.ap-southeast.bytepluses.com/api/v3`,与 AutoVideo 一致。缺任一项则该 key 明确不可用,**不会**静默回退到 OpenRouter 的字节 Seed 基座。 正式监测固定使用 Pro 模型并关闭 thinking;默认不传应用侧输入/输出 token 上限,由供应商按模型能力处理。 仅显式配置 `MAX_OUTPUT_TOKENS` 时才覆盖输出预算。429、超时、5xx 和响应解析异常含首次最多 5 次指数退避,并受单模型槽位 600 秒总时限约束;鉴权、权限、无效模型与余额/配额错误不重试。 服务端只持久化安全 request ID、 失败类别、尝试次数和延迟,不保存供应商错误正文。新建检索中的主动“AI 推荐主词”使用 Mini, JSON/质量不合格时只在剩余预算内重试同一 Mini;它不改变正式监测口径。 - `glm` / `kimi` 保持通过 OpenRouter 调用,并关闭隐藏 reasoning 以保证返回可展示正文;所有 `controlled_benchmark` 模型使用同一冻结的 Tavily 问题级材料辅助回答。OpenRouter 适配器对 `native_observation` 使用 `openrouter:web_search` server tool;这是 OpenRouter 管理的第三方检索层, `authenticity_tier=third_party_api_grounding`,不是模型厂商原生搜索,也不是消费者 App 观测。且仅采纳结构化 `url_citation`/grounding annotation;正文里普通出现的 URL 不会自动变成 citation。受控模式 不会用原生搜索替代冻结 source pack,也不会混合两种口径。Tavily 对中国大陆、香港和澳门的中文网络覆盖有限, 因此该结果只是一致输入下的 `controlled_benchmark` 诊断,不代表模型原生搜索、消费者 App 可见度或完整本地媒体覆盖。 - 联网材料与「真实引用」分开计数;只有回答显式引用 `[n]` / URL 或原生 annotation 才计入 `sources` / `citations`,因此无实际引用时结果可以是空数组。 - 品牌提及判定统一做 Unicode NFKC、英文大小写、空格/标点和简繁体归一化;只接受规范名或 显式的同实体识别名。中英文、繁简体、官方缩写和词序变体可以配置到 `aliases`;产品、服务、业务线、 子品牌和地区分支必须保持为独立实体,不能借别名并入母品牌。例如 `香港汇丰银行`、`汇丰香港银行`、 `HSBC香港` 可在人工确认同一指代后归为一组,而 `汇丰银行` 与 `汇丰理财` 不归为一组。包含关系本身 不再阻止两个实体共存;同一回答里的重叠字面证据由更长、更具体的实体独占,避免母品牌与业务线重复计数。 另会忽略品牌配置名末尾用于区分项目的 `V2` / `Version 2` / `第二版` 等版本标签;版本标签前的常见企业后缀也不要求出现在回答里。 - `OPENROUTER_MODELS` 控制当前可选的七个 OpenRouter 国内模型;未装载的 key 保持不可用, 豆包由独立 BytePlus 配置控制。运行时若加载 `ernie` adapter,它只供已冻结 Run 使用, 不会出现在 `GET /api/models`,也不能由新请求选择。部署环境 `APP_ENV=prod|production|staging|sit` 默认等价于 `REQUIRED_MODELS=all`(也可显式设置),任一要求的目录模型不可用时启动失败, 避免目录模型不完整时接流量。 - `GET /api/models` 返回 `available`、`details[8]` 和 `unavailable`。每个 `details[]` 都含 `key/label/available/provider/model_id/reason_code/reason/capabilities`;可用行的两个 reason 字段为 `null`。 `capabilities` 含 `supports_native_search`、`accepts_external_sources` 与兼容历史数据所需的 `measurement_modes[]`。新客户流程只选择 `available=true` 且支持 `controlled_benchmark` 的行,默认全选但至少保留一个; 单模型选择是完整、可发布的正式面板,不要求固定“模型组合”;不可用行必须保留稳定原因,不能被静默过滤或换成其他模型。 不可用原因使用稳定代码,如 `disabled`、`missing_credentials`、`missing_model_id`、 `not_selected`、`initialization_failed`。它接受 OIDC session 或 PAT。 - 所有手动检索、批次检索、单问、监测任务创建/更新/立即执行和 Skill 检索都使用相同原子校验。显式模型数组 中只要有一个未知或不可用 key,就返回 HTTP 422: `{"detail":{"code":"model_selection_invalid","message":"…","requested_models":[],"unknown_models":[],"unavailable_models":[]}}`。 不会静默过滤,也不会把缺失回答当作 0 分。 ================================================================ # 第二部分 · 项目 API(OIDC session 或 PAT) ================================================================ 除 `/api/skill/*` 外,依赖 `current_user` 的项目 API 接受两种身份轨: 启用 `extra="forbid"` 的请求模型拒绝未声明字段;字段类型、长度、枚举、配对关系或未知字段校验失败时, 统一返回 HTTP 422 顶层信封 `{"error":"validation_error","message":"…","fields":["field_name"]}`。`fields` 只列字段路径, 且只保留当前路由请求模型已声明的路径;未知字段或动态字典键统一显示为固定的 `unexpected_field`(可带已声明的父路径),不会回显任意请求键或被拒值。模型选择使用上文的 `model_selection_invalid` 明细信封,并明确列出 requested、unknown 与 unavailable 模型,不会静默降级。 1. 浏览器 `mia_geo_session` 签名 Cookie(OIDC 登录); 2. `Authorization: Bearer mia_pat_...`(PAT 本人;Agent/API 自动化使用此轨)。 身份必须经官方 Platform 客户端解析 terminal context,随后绑定 GEO 项目数据;无法校验时 fail-closed。`current_user` 浏览器轨无会话时返回 `401 redirect_required/not_onboarded`;已提供凭证的 错误类型使用上面的 typed outcomes。静态 Skill key 不再接受。 首次 PAT 请求会根据中台验证的 User+Account 自动完成 GEO 后台初始化,不要求先完成 OIDC 登录。可选展示邮箱为空时 PAT/Skill 仍正常放行,且不会改变 provider、Points 或 Usage 归因。 鉴权始终是 `AUTH_MODE=platform` 单轨;不存在本地密码或 `AUTH_DISABLED` 旁路。 `APP_ENV=prod|production|staging|sit` 统一执行部署级 fail-closed:缺失/默认弱 `JWT_SECRET`、显式默认弱 `SESSION_SECRET`、非 platform 模式或 `AUTH_DISABLED=1` 均拒绝启动; `SESSION_SECRET` 留空时回退至已校验的 `JWT_SECRET`。这些环境禁止首登自动 admin; `SUPERADMIN_SUBJECTS` 必须至少包含一个合法、唯一、无空白的 opaque Platform User `sub`, email/username 或坏配置拒绝启动。superadmin 不入库;本地 admin 只保存在 DB,并仅能由 当前有效的配置型 superadmin 通过受审计接口授予/撤销。`/api/auth/_test_login` 仅 `APP_ENV=test|testing` 可用。 个人模式先由当前 canonical Platform User + Platform Account authority 校验请求,再由 GEO 内部数据绑定和资源 `owner_user_id` 进一步收窄;不同 Platform User 或数据范围 不能互相读取或修改。已建立用户继续使用其原有数据范围,不因当前 Platform Account 与历史 `Account.platform_account_id` 不同而搬迁或拒绝。无法确定 owner 的历史资源仅 admin 模式可见。 Admin API 还要求该身份是配置型 superadmin 或真实拥有本地 `role=admin`,并显式带 `X-MIA-Admin-Mode: 1`;该头本身不是凭证,也不能把普通用户提升为管理员。 `X-MIA-Admin-Mode: 1` 只开启跨项目 Observer 读取;Run 擦除不是其写入例外。 真实 admin 跨账号执行擦除时必须退出 Observer,并显式声明 `X-MIA-Admin-Operation: run_erasure`。操作头是请求级目的声明, 不是凭证,不能提升普通用户,值与端点不匹配、缺失或与 Observer 同时使用均返回 403。数据负责人 在普通模式下仍可对自己的项目数据执行对应操作。配置型 superadmin 对用户 role/status 的现有精确治理例外不变。 `role=owner` 是可由真实 admin 分配的数据负责人角色;它不获得 Admin API 或跨账号权限。 `GET /api/auth/me` 返回 `id`、`account_id`、`username`、兼容 `role`、`local_role`、 `effective_role`、`is_admin`、`is_superadmin`、`email`、`name`、`is_platform`、`created_at`。 OIDC / PAT 只消费 terminal Platform User context;既有用户继续使用 原有数据范围和业务数据;本地访问绑定继续作为 GEO 数据隔离边界。 Current Platform Account context is bound to the existing local data scope. Authentication never moves or merges historical projects or infers authority from local history; a conflicting Account binding is rejected. 只有 OIDC 导航/回调(`/api/auth/login|silent|callback/mia|logout`)和 `/app` UI 是浏览器流程; All customer JSON/file APIs below accept an Account-bound PAT or a first-party Service admitted by Platform. Internal Service requests send `Authorization: Bearer mia_pk_…` and `MIA-Account-Id`; optional `MIA-Initiated-By-User-Id` is historical audit attribution. GEO uses the official SDK to verify the Service and exact Account, then permits that Account's business resources through its central resource guards. No human token, local User, creator membership, per-route key, or caller-specific configuration is needed. Cross-Account resources remain denied. Browser identity and administrator-role management keep their dedicated human authorization boundaries. ### Data Trust v5:市场、监测上下文与可信凭证 #### GEO Trust Product v2:单市场批次、三分体系、解释与行动计划 这一组项目 API 与下方 v5 证据账本共用 `current_user`,因此仅支持 OIDC session 或 `mia_pat_`。 除模型能力静态目录外,所有资源按 不可变 Platform User、内部数据绑定 + owner 校验;当前 Platform Account 只用于请求授权、计费、下游服务归因、遥测与异步恢复; 跨 owner 返回 403。品牌 `region` 只保留为旧客户端兼容信息,不参与 Profile、检索、模型选择、来源地区或评分。 正式市场事实只来自 `MonitoringProfile + RunManifest`。当前新建 GEO 仅支持中国大陆(CN)、香港(HK) 和澳门(MO)三个市场,且每个 Topic、Profile、Task 和 measurement batch 都只能选择其中一个; Run、语言、进度和结果只对应该目标市场,不返回跨市场合成总分。历史其他市场身份仍可只读展示, 旧 `greater_china` 签名批次仅用于兼容历史读取,不能用于新建或重试。 - `GET /api/model-market-capabilities` 返回 `{registry_version,supported_markets,items}`(兼容字段 `greater_china_markets` 同样返回当前三市场),供读取历史证据和管理员诊断;它不再决定客户新建批次的模型列表。新客户流程只使用 `GET /api/models` 的八行目录,并为所选目标市场创建共享冻结 source pack 的 `controlled_benchmark` Run。 - `POST /api/measurement-batches/prompt-readiness` 是无计费、无持久化副作用的正式测量预检。请求为 `{questions:[{id?,text,intent,source?,measurement_scope?,demand_signal?}],topic?,market,models,purpose,keyword_id?,target_count?,excluded_entities?:string[]}`;`purpose ∈ baseline|post|scheduled|pulse|manual|exploratory`, `target_count` is an integer from 1 through 50。提供 `keyword_id` 时按 account + owner 校验,并可在 `questions` 为空时读取该 Topic 的 benchmark 问题;显式问题会同时按该 Topic 已存品牌、别名和竞品过滤,尚未保存的向导可用 `excluded_entities` 提交同一批品牌/竞品名称。命中实体的问题不计入正式题板,响应以 `entity_leakage_count/entity_leakage` 返回输入位置和命中实体,`next_action=remove_entity_mentions`,避免预检与落库后的 benchmark 范围不一致。A place named in a question does not override the selected audience market; cross-border questions are accepted. The UI preserves selection and shows entity errors beside the affected row. Response fields include `ready/required_count/actual_unique_count/duplicate_count/layers/layer_gaps/ unknown_layer_count/question_ids/question_fingerprints/required_models/official_eligible/reasons/next_action/ method_signature/adaptive_sampling/missing_count`。正式准入策略固定为 `selected-question-panel-v4`:NFKC、大小写、 全半角标点及空白归一后精确去重,不做模糊合并;五类只作为分析标签,不设分类配额。 只要有效问题总数达到本次要求即可通过。`ready:false` 仍为 HTTP 200 的预检结果, 不创建 Brand、Topic、Monitoring Profile、Run、Points hold 或账本记录。 Retain `measurement_scope` and `demand_signal` from generated rows. The preflight and persistence apply the same generated-entity and exploratory-cohort guards, including after manual edits. Malformed target entity metadata returns 422; exploratory questions cannot claim benchmark eligibility by changing their scope. - `POST /api/measurement-batches/estimate` 请求 `{keyword_id,market_preset:null,markets:string[],models:string[],purpose:"baseline"|"pulse"}`; `markets` 必须恰好包含一个 `CN|HK|MO`,`market_preset` 只能省略或传 `null`;传数组多个市场、 `supported_markets` 或 `greater_china` 都返回 422 `single_market_required`。空 `models` 表示按八模型目录顺序使用当前全部可用模型; 非空列表按目录规范化,任一未知或不可用模型都会原子返回 422 `model_selection_invalid`,不会静默删减。 每个正式新 baseline 必须已有至少 3 个不同的有效竞品;名称与显式别名按 NFKC、大小写和空白规范化, 空名称、目标品牌/别名以及与另一竞品完全相同的实体不计数。仅有名称包含关系的母品牌/子资产可以共存, 并由检测层的最长字面证据规则避免重复计数。零、一或两个有效竞品在任何 Run/Points 副作用前返回 422 `competitor_baseline_too_small`,并附 `competitor_readiness:{policy_version,ready,required_count,actual_count,actual_unique_count,missing_count,invalid_count,duplicate_count,error}`; 三个或更多才可继续。系统不会自动补造竞品;后续修改只影响下一次 Run,既有 RunManifest、MetricResult、 tracked-set SoV 与 Mia Visibility 历史 revision 保持冻结且不重算。 **Explicit one-Run panel:** `POST /api/keywords/{keyword_id}/run-preflight` with the normal Run input plus `run_scope`. First use resolves or freezes the Topic's monitoring profile through the same estimate owner after panel and capacity validation. Repeated quotes reuse its identity. This configuration admission does not create a Run, call a model, alter the question pool, or reserve Points; the signed token is still required for the separately authorized start. Its version is `mia-geo-run-scope/v1`; provide `questions` as either `{longtail_id,source:"existing_question"}` or `{text,source:"user_supplied"|"geo_generated"|"agent_drafted",intent?,cluster?}`. The panel is copied to immutable `RunQuestion` records only on start; neither new questions nor selected IDs change the Topic's existing pool. Provide an immutable `request_id` for this human-authorized scope (reused across previews, starts and recovery), and `lanes` explicitly (`target_market` and optionally `global_supplemental`), `samples`, `request_policy` (`market_context` or `verbatim_question`), `attempts_per_slot:1`, `max_model_attempts`, `research_objective` and `audience`. Preflight is read-only and returns the signed estimate, full user prompts, provider system template, retrieval inputs, scope fingerprint and planned/maximum calls. Source-pack text is explicitly dynamic: filled only from this Run's retrieval. An existing active Profile is required; create/select one through the existing Profile API if missing. Start sends the identical scope and its estimate token to POST `/api/keywords/{keyword_id}/runs`. All lanes × questions × models × samples must fit the maximum before any Run, Points hold or provider invocation. For a new controlled panel that needs bounded retrieval recovery, explicitly use `version:"mia-geo-run-scope/v2"` with `retrieval:{strategy:"same_query",max_attempts_per_question_lane:3}` (integer 1–5). Preflight freezes this policy and exposes its maximum search requests. Retrieval is shared by the models in each question/lane; the cap includes the first request and does not multiply by models or samples. Only identical queries may retry, with unchanged market, language and evidence filters. Model attempts remain one per slot, the customer Points quote/hold is unchanged, and no replacement Run is created. A known result is reused after interruption; an unconfirmed dispatch consumes its ordinal and is not automatically resent. Its internal supplier cost remains unconfirmed until the existing accounting repair path resolves it. Existing v1 scopes retain their original no-retry policy and serialization. Strict runs make one provider attempt per slot, disable SDK/output-budget retries, do not switch providers/modes, and use post-answer literal brand judgement without additional model calls. Empty controlled packs and permission failures remain typed gaps. A request identity can create only one Run across fresh quote tokens; its replay reuses existing evidence. For an already admitted request, start ACK replay is an owner-authorized read: the current Account and resource owner, verified immutable Manifest, complete original scope, models, mode, web-search flag, sample count, optional profile and confirmed Points must match. It returns the original Run and sealed profile identity without repricing, another hold, provider work or launch. The original estimate token may have expired or be omitted for this read; it never authorizes a new start. Model retirement or paused admission does not block this read. Drifted Run mirrors or changed inputs return closed 409 errors. `verbatim_question` preserves the question text and excludes brand/aliases/competitors/website background from the measured request and retrieval input. Brand identity is used only in post-answer judgement. Historical Longtail references remain readable; new answers include `run_question_id` and never pretend that identity is a pool row. New official measurements use exactly 1–50 distinct selected benchmark questions. Exact normalization deduplicates questions; intent categories carry no quotas. Selection is never expanded from historical confidence intervals or truncated to a smaller panel. Zero or more than 50 valid questions fail before creating a Run or holding Points. Estimates, frozen manifests, retrieval slots and settlement use the same panel. Changing its questions or size changes the method signature; historical sealed v2/v3 receipts retain their original eligibility and are never rewritten as v4. Small samples remain visibly small in counts and confidence intervals. Historical baseline maturity still requires three distinct UTC observation dates. Single-run estimates, starts, and batch admission share the RunWorkPlan capacity guard: `question_count × model_count × measurement_lanes × samples` must not exceed `MAX_QUERIES_PER_RUN`. HTTP 422 `run_query_capacity_exceeded` includes `requested_calls`, `max_queries_per_run`, `question_count`, `model_count`, `measurement_lanes`, and `samples`. A rejected new Run creates no Run or Points hold. Batch retries retain their existing authoritative batch/Points receipt. Explain the exact scope and limit; ask the customer which questions/models to retain before a new estimate and execution authorization. Never retry unchanged scope, silently split Runs, or switch models. 本地配置校验完成后、计价或任何 Run/Points 写入前,服务端会读取 Tavily 用量并只按明确正向证据放行:套餐仍有余额、 有限 PAYG 仍有余额,或存在非负数值 `paygo_usage` 且 `paygo_limit:null`(控制台显示 `∞`)。缺字段、布尔值、 负数、非法类型和未知结构返回 503 `measurement_dependency_unavailable` + `reason=provider_capacity_unknown`,不能误报为套餐耗尽;认证、限流、明确耗尽、超时和上游不可用分别为 `provider_authentication_failed|provider_rate_limited|provider_plan_limit|provider_timeout|provider_unavailable`。 每个失败都附 `retryable` 及 `batch_created:false` 的完整零 Points 回执。明确可用结果缓存 60 秒,明确耗尽最多缓存 5 秒,未知或瞬态错误不缓存,避免 PAYG 刚开通后继续沿用旧的耗尽判断。 响应返回 `baseline_required_windows:3`、`sampling_by_market`、目标市场语言、冻结模型/provider 摘要、Prompt 分层计数、 `official_run_count/diagnostic_run_count`(单市场时 `official_run_count=1`)、`unit_points/billing_units`、 `expected_points/max_held_points`、单位 `MIA Points`、15 分钟有效的 签名 `estimate_token`。其中 `official_run_count` 等于本次原子市场数、`diagnostic_run_count` 固定为 0; Manifest 会冻结 `policy_version/purpose/required_count/question_ids/layers/required_models/official_eligible/method_signature`; 10 题等探索性历史只读派生为不合格,不改写证据、不自动补跑、不产生 MIA Points。 - `POST /api/measurement-batches` 请求 `{estimate_token,expected_max_points,idempotency_key}`,其中 `estimate_token` 不设产品侧最大长度,幂等键 16–160 字符。服务端重新核对冻结输入与 Points, 在启动任何 provider 调用前先创建并预留全部原子 Run;明确失败会取消未启动 Run 并释放已成功 hold。若 Points 服务返回结果未知,则保留已封存的 admission,调用方必须使用同一幂等键重试;服务端会用同一 externalRef 恢复, 不重复预留或创建品牌/Topic。 每个原子市场只创建一个统一受控 Run;由于当前请求只能选择一个市场,因此每次请求只创建一个 Run, 不自动创建隐藏诊断 Run。响应 HTTP 202,canonical 字段为 `{measurement_batch_id,brand_id,keyword_id,status,runs[],official_runs[],diagnostic_runs:[],partial,attempted,completed,full_success,partial_success,failed,running,max_held_points,held_points,settled_points,released_points,billing_status,unit,progress,data_state,execution_breakdown,publication_eligibility,baseline,safe_reason,available_actions}`; `consumed_points` 是 `settled_points` 的兼容别名,`succeeded` 是 `full_success` 的弃用兼容别名,二者都不会再把意图或 partial 当成已确认事实。 `runs[]` 每项含 `run_id/brand_id/keyword_id/measurement_batch_id/monitoring_profile_id/market/language/status` 和同一组逐 Run Points 字段;过渡期 `official_runs[]` 是相同列表的兼容别名, 新调用方必须读取 `runs[]`;单市场批次的 `runs[]` 恰好包含一个 Run。 Points 字段按 durable 账本状态投影:`held_points` 只表示当前已确认冻结,`settled_points` 只表示已确认结算, `released_points` 只表示已确认释放;远端结果未知时相应字段为 `null` 且 `billing_status ∈ not_reserved|reserve_pending|held|settle_pending|settled|forfeited|release_pending|released|already_settled|unconfirmed`; 批次内 Run 状态不同时为 `mixed`。`already_settled` 只能确认该 hold 已在别处结算,无法证明结算数额, 因而 `settled_points/consumed_points` 必须为 `null`,绝不把预留上限或结算意图冒充实际值。 重复相同幂等键返回同一批次, 不同范围复用该键返回 409 `idempotency_conflict`。Points 变化为 409 `estimate_changed`,不足为 402 `insufficient_points`,Points 服务不可用为 503 `points_service_unavailable`。同键并发 admission 尚未收敛时返回 409 `batch_admission_in_progress`;旧失败 generation 的 Points hold 仍在释放时返回 503 `batch_cleanup_pending`。启动时若计价配置不可用返回 503 `model_pricing_unavailable`;若模型能力、问题数量或 检索上限或竞品基线校验失败返回 422 `controlled_benchmark_not_supported`、`competitor_baseline_too_small`、`batch_admission_invalid` 或 `run_query_capacity_exceeded`. A capacity refusal requires a user-selected scope change and a fresh estimate; never retry the unchanged scope. For an existing batch, verify its authoritative billing receipt first. Other admission exceptions are logged server-side; 对外仍只返回客户安全的批次错误语义。启动错误若已创建任一 Run,会返回 `batch_created:true/measurement_batch_id/batch_status/brand_id/keyword_id/billing`,可用批次 ID 查询核验并以同一幂等键恢复; 若确实未创建批次,则返回 `batch_created:false/held_points:0/released_points:0/billing_status:"not_reserved"/unit:"MIA Points"`。 若连本地账本查询也不可用,返回 `batch_created:null`、确定性的 `measurement_batch_id` 以及全部数值为 `null`、 `billing_status:"unconfirmed"` 的 `billing`;这不代表已创建或未创建,调用方必须保留同一幂等键与批次 ID 重试核验。 数据库拒绝 admission 持久化时返回 500 `batch_persistence_failed`,不会冒充 409 状态冲突;若账本确认没有 Run, 回执明确为 `batch_created:false/held_points:0`,调用方应联系管理员而不是按幂等冲突保留配置重试。 账本读取、重新估算不可用分别为 503 `batch_state_unavailable|batch_estimate_unavailable`;批次 Run/Profile 的 brand、Topic、批次或市场身份混杂时为 409 `measurement_batch_identity_conflict`,不会返回可能串线的数据。 - `POST /api/measurement-batches/resolve` 是未知提交恢复端点,请求为 `{idempotency_key,estimate_token?,expected_max_points?}`。服务端用当前不可变 Platform Account + User 派生与启动接口完全相同的批次 ID,不接受批次 ID 或任何身份 selector。浏览器应发送日志中冻结的完整 replay envelope;若 Run 尚未可见,服务端以原幂等键重放正式 start, 复用同一批次和 Points externalRef,不重复创建或冻结。`resolution=found,batch_created:true` 返回同一批次及真实 Points 投影; replay 获得确定性零副作用失败时返回 `resolution=not_found,batch_created:false`;仍不能确定时返回 `resolution=unavailable,batch_created:null,action=replay_submission,retryable:true`。兼容的 key-only 请求只读且保持 unavailable; 本地账本不可读时同样返回 503 `batch_resolution_unavailable` 与 unknown 回执。 浏览器在 start 前把幂等键、短期 estimate token、服务端 `expires_at`、确认 Points、创建时间和不含问题正文的配置摘要写入可跨标签页协调的 `localStorage`,并验证写回;共享存储、Web Locks 不可用或存在另一个未恢复 key 时必须 fail closed,不能降级到每标签页独立存储后 start。 guided flow 必须 `await acquireAdmissionJournal(...)` 串行执行检查与写入; 刷新、响应丢失或 token 过期必须先 resolve;过期只标记 replay envelope,不丢弃它,因为服务端仍需用它返回确定性的无创建失败。 found 后日志绑定服务端返回的 `measurement_batch_id`,只有 results 成功读取同一目标批次后才清除;原 start/estimate 的确定性 `batch_created:false` 回执可立即清除。配置编辑或用户操作都不能在 authoritative negative 前更换幂等键。 - `GET /api/measurement-batches/{batch_id}` 读取同一 `measurement_batch_id` 下的 `runs[]` 与 Points 结算汇总, 同样返回 `official_runs[]` 兼容别名和空 `diagnostic_runs[]`; 批次状态由 Run 实时聚合,不存在独立 Batch 表。`progress` 是不读取 Prompt/回答正文的批量聚合快照,包含 `state/updated_at/last_progress_at/stale_after_seconds/completed/total/stages/markets[]/data_state/` `execution_breakdown/publication_eligibility/baseline/safe_reason/safe_message/available_actions`;单市场 `markets[]` 只含一个 market 项,含 `run_id/market/state/last_progress_at/completed/total/stages/data_state/execution_breakdown/publication_eligibility/` `baseline/safe_reason/available_actions`。不存在为 404,跨租户为 403。 `state ∈ queued|running|retry_wait|stalled|done|partial|failed|cancelled|dead_letter`,阶段状态 `waiting|running|done|partial|failed|stalled`。阶段为 `retrieval:{completed,total,succeeded,empty,failed}`、 `answers:{completed,total,succeeded,excluded,failed,judged}`、 `metrics:{completed,total}`、`report:{completed,total,ready_report_id}`。 `downstream:{state,error_code}` 明确表达终态后的 metrics/report 物化;物化未完成时不得返回 `done` 且 `completed` JSON(`run_id/brand_id/keyword_id`), SPA 用它恢复导航上下文,不再并行请求 `/document`;这些 ID 仍受同一归属链校验和响应租约保护。 - `GET /api/keywords/{id}/trend` 与 Dashboard v5 趋势以最新 Run 的**精确可比签名**为比较宇宙。签名冻结市场、语言、 测量模式/lane、品牌/Topic、竞品和问题顺序、完整模型执行身份与能力、渲染 Prompt、搜索策略、Registry、算法和采样参数; 任一变化都排除该 Run 并返回 `manifest_signature_mismatch` 及 `*_changed` 原因。至少两个完全相同签名的原始指标点返回 `trend_status:"available",reason:null`,可用于绘制同口径描述性趋势;该状态只证明方法可比,不代表差异达到统计显著, Report 的方向性比较在尚无追加式 paired-bootstrap 差值 MetricResult 时仍保持 `not_comparable`; 没有完全相同签名的上期则返回 `not_comparable/no_comparable_previous_manifest`。 v5 尚无逐题和情感 MetricResult,因此 prompt/sentiment trend 继续分别返回 `v5_prompt_metric_revision_unavailable|v5_sentiment_metric_revision_unavailable`。旧“引用可能性”代理仍返回 `status:"legacy_proxy_disabled"`。 - v5 回答/来源客户导出已接入冻结证据,并统一限定目标市场通道:`GET /api/runs/{id}/answers.csv` 使用 Manifest 问题/模型、最新 JudgementRevision 与 CitationEvidence;`GET /api/keywords/{id}/sources.csv` 仅导出目标市场回答实际引用的 公开 URL,并支持既有平台/地域筛选。候选淘汰原因、执行身份、provider 诊断、Response/Source/MetricResult 公共标识和 Manifest 指纹均不进入客户导出。`GET /api/snapshots.csv` 可在同一请求范围安全合并 legacy 与 v5 回答;客户导出仅包含可读回答、 提及状态、排名、情感、竞品和公开来源链接,不包含原始回答标识/批次/Manifest 标识、Schema/Projection/测量通道/样本等内部字段或判定方法标签。排名统一显示明确的 `#N`、`仅提及`、`未进入推荐榜` 或 `不适用`;已有字面判定但 judge 不可用时,提及仍显示持久化的布尔结果,排名与情感分别显示“排名待判定”和 “情感待判定”。只有既无持久化判定、也无字面观察的旧记录才显示“待确认”。 answers 的 `cluster` 未冻结时返回 409 `v5_cluster_not_frozen`。 - v5 DeliveryBundle 必须绑定具体 ReportRevision public ID、Manifest hash 与 document hash;旧 bundle 或 revision 漂移返回 `v5_bundle_revision_mismatch` 并 fail-closed。ZIP 的 HTML/PDF/PPTX/XLSX 和证据 CSV 来自该 revision;当前行动、阵地、部署和归因 未进入 ReportRevision,故 v5 ZIP 对这些目录写入明确 `not_frozen_in_report_revision` 缺失说明,不在导出时拼接当前配置。 - v5 客户读取统一由冻结投影 `projection_version:"v5.customer.2"` 提供:`GET /api/runs/{id}`、sources/snapshots、 suggestions、opportunities、brand-tags、content-briefs、competitive source gaps、question ownership、Dashboard 与投放建议均读取 RunManifest、Response、最新 JudgementRevision、SourceObservation、CitationEvidence 和 MetricResult,不回退到可变品牌/问题或 legacy 判定。记录统一返回 schema/projection version、冻结提示词、模型、 测量通道、样本、回答、引用、measurement control、真实性等级、观察状态与客户安全的质量摘要。For each question/model/sample slot, the customer projection prefers a usable target-market answer and otherwise returns the usable global-supplemental answer from the same slot. It never exposes failure placeholders or technical verification labels for an unverified but readable answer. 执行身份、SQL/provider 诊断、内部 reason code 和发布门槛细项仅在显式 Admin 诊断面可见。 - Formal metrics use their frozen metric-specific eligibility rules; competitor gaps and action guidance require verified execution and judgement. 已有持久化 judgement 的回答始终公开其 字面 `mentioned` 布尔值,但 `rank/sentiment` 在 judge 不可用时保持 null,并返回 `judgement_status:"rank_sentiment_unavailable"`。这些降级字段不得进入排名、情感或派生建议。普通客户投影另外返回 `observed_mentioned/observed_brand_match/observed_competitors/observed_competitor_matches`,这些字段仅是对模型返回正文的确定性字面观察, 用于如实展示回答中出现的目标品牌和已配置竞品,不代表正式有效数据,也不得用于正式 KPI、排名、情感或建议。Technical `observation_status`, verification state, retry guidance and provider failure details are admin diagnostics and are omitted from the ordinary customer response. Dashboard 与投放在覆盖不足时返回可重测的质量摘要和缺失样本,不伪造机会结论。Prompt detail 收到 `trend_status:"not_comparable"` 时必须停止,不得再逐个拉 Run 在浏览器重算趋势。新手引导创建的也是 v5 Run,只能展示 MetricResult 的 value、分子/分母、覆盖率及可展开的可能波动范围;不得用 legacy `summary.mention_rate` 替代缺失综合分,也不得使用“低于基准/领先”等未经发布门禁支持的叙事。 - For v5 `/runs/{id}/suggestions`, each model's `mention_rate` and rate-based `priority` read the latest frozen target-market `customer_analytics_snapshot` for that exact RunManifest. `mention_metric` exposes its canonical presence label key, value, numerator, denominator, lane, analytics revision and quality, with `source_metric_key/id` and `manifest_hash` identifying the analytics receipt; it does not upgrade exploratory evidence to publishable evidence. Literal presence may remain eligible when ranking or sentiment is unavailable. Action gaps, negative statements and citation recommendations retain the stricter verified-judgement boundary. Missing or unsupported per-model analytics leave the rate and priority null; clients must not recalculate from that action subset, substitute another lane, or display a default placement priority. 除只返回静态受控 Registry 的 `/api/markets` 与 `/api/model-market-capabilities` 外,资源路由都执行 Run/Brand/Keyword/Profile 的 account + owner 归属检查;跨 owner 为 403、资源不存在为 404,Admin 观察模式仍为只读。客户可见响应、错误和本合同只出现 MIA Points,不出现货币符号、币种、provider 价格或 任何内部法币/会计字段。 - 项目:`/api/projects`(列/建)、`PATCH|DELETE /api/projects/{id}`、`GET /api/projects/{id}/overview`(项目级品牌汇总)。个人模式同时校验 Platform Account 与资源 `owner_user_id`,同一 Account 的不同 User 也不能互相读取项目、品牌、监测上下文或 Run;`GET /api/brands?project_id=` 过滤(`>0` 该项目 / `0` 历史未分组 / 省略=全部)。`POST /api/brands` 的 `project_id` 是必填正整数,服务端始终校验目标项目 owner;缺失或非正整数返回 422,跨 owner 返回 403,不存在或已归档项目返回 404。`PATCH /api/brands/{id}` 只允许用正整数把品牌移动到当前 owner 的项目,不能把已分组品牌移回未分组。历史 `project_id=NULL` 品牌继续通过 `project_id=0` 列出和选用,并可迁移到当前 owner 的有效项目,但不能在该范围创建新品牌。项目与品牌创建响应均含 `disposition=created|reused`:相同 owner 自然唯一身份只在完整持久化 payload 相同时作为响应丢失重放返回原 ID;同名但任一配置不同、或命中已归档行时返回 409 identity conflict,不会按名称猜测复用。品牌名称唯一性按当前 owner + project 作用域计算,同一账号在不同项目可创建同名品牌。仍有未归档品牌的项目不能直接归档,`DELETE /api/projects/{id}` 返回 409;调用方必须先把品牌迁移到其他项目。工作台“新建检索”必须先创建空项目或明确选择已有项目;新项目只允许新建品牌,已有项目才允许在该项目的品牌列表中追加主词。项目或品牌列表读取失败必须显示可重试错误,不能伪装成空列表。项目总览尚未接入 v5 MetricResult/CI,因此最新 Run 为 v5 时 `mention_rate/avg_rank` 返回 null,`overview_status:"versioned_metrics_unavailable"`,绝不回退兼容 RunMetric 点估计。 - 品牌/主词:`/api/brands`、`/api/brands/{id}/keywords`、`/api/keywords/{id}`(含竞品、长尾增删改查)。 `POST /brands` 与 `PATCH /brands/{id}` 的品牌支持 `aliases:string[]`;主词输入及 `PUT /keywords/{id}/competitors` 的 `competitors` 兼容旧 `string[]`,也接受 `[{"name":"中银香港","aliases":["中国银行(香港)","BOCHK"],"existence_status":"confirmed","confirmation_source":"manual"}]`。 For confirmed same-brand spellings, `PATCH /api/brands/{id}` also accepts `aliases_to_add:string[]` (up to 20); do not combine it with `aliases`. This additive operation merges under the brand identity lock and preserves concurrent additions. The response includes authoritative `identity:{id,name,aliases,revision}` and the existing reconciliation receipt. More than 20 distinct aliases returns `brand_alias_limit` (422). Read status before using reconciled metrics; saving aliases does not imply finished reports. 结构化对象省略 `existence_status` 时也按 `pending_confirmation` 处理并在保存入口 fail closed 返回 409; 显式写 `confirmed` 但没有 `confirmation_source:"manual"|"user_confirmation"` 也会拒绝;向导显式确认后携带 `confirmation_source:"user_confirmation"`,事务内追加不含 provider/价格信息的审计收据。品牌列表项返回 `id/name/aliases/industry/region/language/landing_url/account_id/project_id` 及 owner 字段, 读取主词除兼容的 `competitors:string[]` 外还返回 `competitor_entities:[{name,aliases}]`。 所有品牌/竞品保存入口都会检查跨实体的归一化名称与别名:完全相等时返回 409 `entity_alias_conflict`;仅有包含关系时允许保存,因为它可能代表母品牌与产品、业务线或子品牌。 系统不会再根据品类/业务后缀自动合并两个实体;`Rare Beauty`、`Fenty Beauty`、`古驰美妆`、 `HSBC Wealth` 等名称都保留其独立身份。历史响应中的 `normalization_changes` 是旧版自动合并的事务审计收据 ID,可通过 `POST /keywords/{keyword_id}/competitor-merges/{audit_id}/undo` 在合并后状态未被修改时撤销。 撤销会追加审计收据并触发 deterministic reconciliation;不会修改冻结 Run Manifest。 每个主词还包含独立 `market`、`language` 与 `target_markets:string[]`;`market` 是唯一目标市场且始终等于 `target_markets[0]`,新建默认 `CN` / `zh-Hans-CN` / `["CN"]`;CN 新建请求省略语言或传历史兼容别名 `zh-CN` 时都规范为 `zh-Hans-CN`。`target_markets` 保留数组形状以兼容客户端,但新写入必须恰好只有 一个值且与 `market` 相同;多值或不匹配返回 422 `single_target_market_required`。列表、详情与创建/复用响应统一回传这三个字段; 主词必须是一个短语,不得填写官网 URL/域名;含逗号、中文逗号或换行的列表也返回 422 `keyword_must_be_single_topic`,官网地址应填写品牌的 `landing_url`。 `POST /keywords/{id}/competitors` 以 `{name, aliases?:string[]}` 增量添加一个竞品;名称大小写不敏感去重, 已存在时只合并新别名,不覆盖其他并发编辑。响应为 `{ok,name,aliases,duplicated,competitor_directory,effective_from_next_run:true,reconciliation}`; `competitor_directory` 是添加后完整已跟踪目录,但只对下一次 Run 生效,不改写当前 Run 的冻结 KPI; 空名称返回 400,主词不属于当前 owner 返回 403。 品牌改名/别名变化、全量替换或增量添加主词竞品(含别名变化)都会把历史判定/指标/报告标为待重算,并在响应的 `reconciliation` 返回 `{id,status,total,processed,failed,attempt_round,next_attempt_at}`。这项重算由持久队列异步、分批执行;用 `GET /api/reconciliation-tasks/{id}` 查询,失败时可 `POST .../{id}/retry`。配置修改本身已成功时, 重算失败不会回滚配置;单个异常按完整扫描轮次有限重试并指数退避,任务最终保持 `failed` 且 `last_error` 可见,显式重试后重新收敛。仍在执行的 Run 始终保持 `running`,不会因轮询提前变成 `partial|failed`;待检索真实终态后才写入新判定、指标、报告失效和 `data_version`。 Run 完成后,跨 Run 实体解析会把回答中的字面品牌 span、冻结 Manifest、判定 revision、问题和模型家族写入可恢复任务。 `GET /api/entity-resolution-candidates?review_status=unreviewed` 默认只返回异常、低置信度和需要人工激活的新竞品; `candidate_id` 查询会附带客户安全的证据详情和同一品牌/主词内可重定向目标,不返回 provider 成本或 token 数。 单项审核使用 `POST /api/entity-resolution-candidates/{candidate_id}/review`;最多 50 项的批量审核使用 `POST /api/entity-resolution-candidates/bulk-review`,每项必须带独立幂等键并单独返回成功或错误。 别名只有在至少 3 个回答槽位、2 个问题、2 个由冻结 Manifest 模型身份派生的测量模型家族,以及两个 不同配置 adapter 均以不低于 0.98 判为 `same_entity` 时才具备自动激活资格;SIT 默认 shadow。重定向后必须重新核验。新竞品和 `convert_to_competitor` 始终需要真人审核才改变后续监测范围。激活、拒绝、撤销均追加收据并触发 deterministic reconciliation;冻结回答和 Manifest 永不改写。 实体分析随基础测量单价一并交付,默认**不单独计费**:`entity_resolution.verifier_calls` 是该 Run 的 供应商调用上限,`unit_points` 与 `points` 为 0,除非部署显式配置了独立的 verifier 单价。没有发现候选 也是已交付的负向分析。调用上限按回答槽位 × 配置的 verifier 模型数 × 每槽位候选上限计算,并冻结进 Manifest;同一 Run 的多次尝试共享这一个上限,不会因重试而重新开始计数。实体解析 durable job 的瞬态 失败独立重试,不回滚已交付 Run,也不会产生第二笔 Points。证据收据只把成功且模型家族不同的 verifier 计为独立,任一最新证据结论冲突都保持 shadow/needs-review。 旧 `/runs/{id}/entity-suggestions` 在一个兼容版本内仅供读取;`materialize` 不再创建或自动接受,旧 `accept/revert` 返回 410 `legacy_entity_suggestion_read_only`,不能绕过双核验门槛。待所有受支持客户端迁移到 `entity-resolution-candidates` 且历史 accepted 行均有新收据映射后,删除 `RunEntitySuggestion` 及兼容 API。 品牌接入模式 `site_mode ∈ website|materials|hybrid`:`materials` 表示没有可稽核/部署的自有官网, 官网稽核与官网部署资产返回 `not_applicable`/`applicable:false`,不能当成 0 分;竞品、采样、内容、 外部阵地与报告仍可使用。历史品牌默认 `website`。 品牌事实库(单一确认口径):`GET /api/brands/{id}/facts` 返回严格六字段事实、完整度、 `facts_version/facts_digest/facts_updated_at` 及引用该版本/旧版本的草稿、成稿、报告数量。 `PATCH /api/brands/{id}/facts` 请求为 `{facts,expected_version}`,`facts` 只能包含 `positioning:string`、`one_liner:string`、`facts:string[]`、`faqs:[{q,a}]`、 `differentiators:string[]`、`endorsements:string[]`;未知字段/超过 128 KiB 返回 422, 缺 `expected_version` 返回 428,版本冲突 409;无变化不升版,每次变化追加不可变 revision。 内容草稿、AutoSocial 成稿与报告冻结所用 `facts_version/facts_digest`;后续事实升版只把旧内容派生为 stale, 不改写历史报告。事实为空时内容提示明确禁止编造品牌专属数字、资质、日期和案例。 无官网材料:浏览器优先使用 `POST /api/brands/{id}/materials/upload?filename=...&mime_type=...`, 请求体为原始文件字节(`Content-Type: application/octet-stream`),避免边缘 WAF 对合法文档 multipart 内容的误拦截;`filename` 必填且最长 255 字符,`mime_type` 最长 200 字符。兼容客户端仍可使用 `POST /api/brands/{id}/materials` 的 multipart `file`。两种传输进入完全相同的本地 Account/owner 校验、格式验证、 配额、本地解析和对象存储流程,均支持 PDF、DOCX、MD、TXT、HTML; 单文件 20 MiB,每品牌最多 20 份/合计 100 MiB,PDF 最多 300 页,DOCX 解压后最多 50 MiB, 每品牌参与抽取文本最多 500,000 字符。服务端校验扩展名、MIME、魔数、路径、压缩比、加密/损坏文件、 页数与解析超时;原始 multipart 请求在解析前受 `MATERIAL_UPLOAD_BODY_LIMIT_BYTES` 限制(默认 22 MiB, 生产 Ingress 必须设置为相同或更小值),chunked 请求超过 1 MiB 后转磁盘 spool。隔离解析器受 `MATERIAL_PARSER_CONCURRENCY`(默认 1)、`MATERIAL_PARSER_QUEUE_LIMIT`(默认 4)和 `MATERIAL_PARSER_QUEUE_TIMEOUT_SECONDS`(默认 10 秒)约束;满载/排队超时返回 503 `material_parser_overloaded`。生产必须使用 `MATERIAL_S3_BUCKET`(否则显式使用 `REPORT_S3_BUCKET`),SSE-S3,配置 KMS key 时 SSE-KMS;生产缺少/无法访问对象存储返回 503,绝不静默写入 DB,本地/测试才可 DB 回退。S3 上传先写 pending marker,DB 提交后才清除;Pod 重启后由恢复 worker 按主库精确核验并清理未提交对象,数据库状态不明时保留原件。 恢复扫描由 `MATERIAL_UPLOAD_RECOVERY_INTERVAL_S`(默认 60 秒)、`MATERIAL_UPLOAD_RECOVERY_BATCH` (默认 100)和 `MATERIAL_UPLOAD_RECOVERY_GRACE_SECONDS`(默认 300 秒)控制;本地兜底清单路径可用 `MATERIAL_UPLOAD_RECOVERY_FILE` 覆盖。 `GET /api/brands/{id}/materials?include_archived=true` 可同时读取已归档项; `POST /api/materials/{material_id}/restore` 幂等恢复归档材料并重新检查份数、字节与文本配额。 较早归档若已删除原始二进制,响应为 `original_available:false`,但已解析文本仍会恢复使用; 恢复不会重启或恢复已取消的语义抽取任务。 无法安全持久化返回 503 `material_persistence_failed`。上传即进行免费的有界本地文本解析。`POST /api/materials/{id}/extract` 的 `mode=deterministic` 不向模型发送正文、无需授权、免费;`mode=semantic` 必须 `consent_to_model:true`,可靠入队后返回 202/`queued`,用 `GET /api/material-extraction-jobs/{job_id}` 轮询 `queued|running|retry_wait|succeeded|failed|cancelled`; `POST /api/material-extraction-jobs/{job_id}/cancel` 请求 cooperative cancel;若 provider 已完成且成功结算围栏先胜出, 返回 409 `semantic_job_busy`,表示取消未被接受。归档材料会先持久化取消围栏; 仍在运行时返回 409,待 worker 停止后重试。原件删除失败返回 503,材料保持未归档、可重试。 只有真实调用语义模型时 才按 `material_extract` 30 MP / 10 CU 预留并结算,失败释放。每次 provider attempt 使用 `job_id + attempt` 派生的稳定 UsageEvent id,避免失败重试吞掉最终成功事件;job summary 返回 `usage_event_id/usage_event_state/usage_status/consumed_mp/billing_outcome`;内部核算字段不属于客户合同。 provider 已成功但 settlement intent 暂未持久化时不重复调用 provider,候选照常冻结,worker 从 `error_safe_summary.billing_recovery` 补偿账本。若成功结算围栏已持久化但 Pod 在候选/任务终态提交前硬崩, worker 不重放 provider,而将任务 fail-safe 收敛为 `failed`;无 hold 时错误码为 `semantic_result_lost_after_success_gate`,已有 settle intent 时为 `semantic_result_lost_after_settlement`, 已产生的成功 UsageEvent 和消耗保持不变。任务具备 lease、heartbeat、有限重试与重启恢复; `MATERIAL_JOB_SHUTDOWN_SECONDS`(默认 15 秒,范围 1–60)限制 Pod 优雅退出等待,超时任务由 lease/reaper 恢复。 所有结果只进入 `MaterialFactCandidate`;`POST /api/material-fact-candidates/{id}/review` 用 `{action:"accept",expected_version}` 接受时才经事实乐观锁合并并升版,`action:"dismiss"` 仅驳回。 列表/详情/归档和品牌候选聚合均按 account+owner 隔离。 行动计划:`ActionItem.type ∈ content|technical|distribution|fact_correction|monitoring|other`, `status ∈ draft|ready|in_progress|verifying|done|blocked|cancelled`,优先级 P0–P3, 风险 `low|medium|high|critical`。系统建议、竞品差距、稽核问题只创建 `draft`;用户必须通过 `/transitions` 确认后进入 ready。PATCH/DELETE/transition 使用 `expected_version`(缺失 428、冲突 409), DELETE 为软删除;相同幂等键+相同 payload 返回原对象,不同 payload 返回 409;事件为 append-only。 验收标准 `type ∈ site_signal|metric|placement|manual`。客户端只可提交 manual 证据,不能伪造自动指标、 稽核或投放上下文;Run、SiteAudit、Placement 终态由服务端读取持久化快照后 fail-open 触发。 legacy metric 使用包含问题、模型、采样与 collection profile 的严格签名,不可比时记录 `skipped/run_not_comparable`。配置 metric 标准时调用 `GET /api/action-items/{id}/comparable-runs` 选择 `baseline_run_id`;返回项含 `run_id/created_at/run_type/status/models/samples/web_search/question_count/collection_profile/expected_signature/methodology`, 服务端校验 owner、品牌和主词后冻结 `expected_signature + expected_methodology + baseline_snapshot`, 不要求用户手填 hash。 v5 的可比范围只包含 `question_set/model_set/target_market/language/measurement_mode/samples`; `algorithm_versions/lanes/prompt_templates/source_registry_version/search_policy` 属于可演进方法信息,变化时仍进入验收, 并通过 `methodology_changes` 披露,不再产生 `run_not_comparable`。升级前已创建的标准会从其完整性校验通过的 `baseline_run_id` Manifest 重新得到范围与方法信息,无需迁移数据库。 自动建议任务会在创建时同时创建匹配 `impact_metric_key` 的必需 metric 验收标准;标准冻结 finding 的 `question_ids`,因此同一 Run 的多个任务分别用对应题目的完整已验证回答槽位判断 finding 是否解决;该结果直接决定题级标准 passed/failed,既有 MetricResult 只作为 `metric_value` 辅助证据, 不再作为题级 finding 的第二道数值门禁,也不会创造题级指标。任一预期模型/样本槽位缺失或未验证时保持未评估。 用户把任务标记为已执行并迁移到 `verifying` 后,先调用 `POST /api/action-items/{item_id}/remeasure/estimate` 获取冻结范围与 MIA Points 预估,再调用 `POST /api/action-items/{item_id}/remeasure-runs`(请求含 `estimate_token`、`expected_points`、 `idempotency_key`)启动复用原 Run 问题集、模型集、市场、语言和采样数的 post Run。响应的 `result_url` 可直接打开新 Run;终态 MetricResult 与对应题目的回答证据会更新任务。题级任务的 `current` 含 `resolution:"resolved"|"unresolved"`、`resolved/metric_value/methodology_changes/metric_result_ref/question_ids/evidence_refs`; 基线与目标也使用 resolution/resolved 表达验收进度。非题级 metric 标准继续使用 `current.value`。 自动 check 的 `trigger_ref` 是可排序 opaque 值;真实上游 ID 读取 `input_snapshot.source_ref/source_order`。 placement 可达性使用 SSRF 安全的有界 GET 与每跳校验;无 URL 或无法取得可信结果才 skipped。 verifying 的必选项全通过进入 done;done 后一次可比失败仅 warning, 连续两次失败记录 regressed 并回到 in_progress。自动验收使用持久化 `AcceptanceExecution` 队列,具备 冻结输入、CAS claim、lease/heartbeat、有限重试和重启恢复;验收失败不得改变上游 Run、报告或计费。 `POST /api/action-items/{item_id}/checks/{execution_id}/cancel` 请求 `{expected_version}`;缺失返回 428,版本冲突返回 409。携带当前版本对已终态任务重复取消会幂等返回原对象; queued/retry_wait 可直接收敛为 `cancelled`,running 使用 cooperative cancel fence;终态使用稳定 `terminal_receipt_id` 去重,worker 重启不会重复改变工单或上游结果。 持久站点稽核:旧 `POST /api/audit` 仍是兼容快速调用;`POST /api/brands/{id}/site-audits` 创建品牌归属 job(202),请求 URL 必须等于品牌官网;请求必须在 body 传 `idempotency_key` 或使用 `Idempotency-Key` header(缺失 428、最长 160 字符),同 owner/key/相同 payload 返回原 job并标记 `deduplicated:true`,同 key 不同 payload 返回 409。`mode=quick` 免费固化现有单页信号;`mode=deep` 执行真实有界全站稽核,请求可传 `limits.page_limit`(默认 50,范围 1–200;兼容输入名 `max_pages`)。 deep 同源抓取深度最多 3、并发最多 4,单响应 2 MiB、任务总响应 50 MiB、单请求 8 秒、最多三次重定向; sitemap index 递归深度/文件数受限,并尊重 robots。每次 DNS 解析和每个重定向都重新执行 SSRF 校验并将 公网数值 IP 固定到连接,拒绝用户信息 URL、IPv4/IPv6 私网、DNS rebinding 与跨源页面扩散。9 种 crawler UA 差异只检查首页和最多 3 个代表页,避免九倍全站压力。结果包含 WAF/CDN 差异、sitemap 污染、llms 链接、 hreflang 无回链、重复标题、正文近重复和段落可引性;只保存 hash、结构结果及最长 240 字符证据,不保存正文。 deep 创建时冻结 `deep_audit` 动态价格(无后台覆盖时 50 MP / 15 CU),先可靠持久化账本意图再执行;成功 settle,失败/取消 release,账本重试不会重复抓取或改变已提交的稽核结果。响应 `billing.points` 才是本任务 的实际 MP,客户端不得硬编码默认价。 轮询 `/api/site-audits/{id}`;取消必须提交当前 `expected_version`(即响应的 `status_version`),缺失 428、 冲突 409。非终态为 `queued|running|retry_wait|billing_pending`;任务具备 lease、heartbeat、最多三次尝试、 取消围栏、终态事件围栏与重启恢复;终态仅 `succeeded|failed|cancelled`。结果持久化四层 dependency chain、独立技术信号、爬虫矩阵和 `SiteAuditPage` 短证据,不保存完整网页正文。部署资产:`GET /api/brands/{id}/deploy-assets` 根据 当前确认事实确定性生成 `{llms_txt,jsonld_organization,jsonld_faqpage,html_snippet,notes, applicable,reason,site_mode,facts_version,facts_digest,asset_version}`;`facts_version=0` 时返回 `reason=confirmed_brand_facts_required` 且所有资产为 `null`,不会从名称、行业或地区伪造官网资产。 不支持 Article schema,生成/下载不代表已部署,也不代表向任何外部渠道发布。站点稽核的外部传输失败 必须保留为 `unknown`,不得转成可创建工单的 `fail`;robots 策略未知时 crawler matrix 的 `allowed=null`、 爬取评分不得加分,dependency chain 不得声称通过。deep sitemap 的传输失败写入中性的 `probe_errors` 和 `probe_status=unknown`,不得混入污染 `issues` 或清理建议。已取得 HTTP 响应后确认的缺失或阻断仍为真实失败。 Quick-audit `geo_score` is unavailable (`null`) unless both usable homepage content and robots policy are observed. `fetched.robots_policy_known` records a successful robots response or an explicit 404/410 absence; throttling and server errors never establish an unrestricted policy. Missing homepage content produces no H1/FAQ/schema deficiency. The technical score is not measured AI visibility, answer accuracy, or evidence that a GEO campaign ran. Historical snapshots remain immutable; unsupported content findings cannot create actions. 稽核失败项转工单只能调用可信入口 `POST /api/site-audits/{audit_id}/signals/{signal_key}/action-item`;服务端从已冻结的成功稽核派生证据。 通用 `POST /api/action-items` 拒绝客户端提交 `source_*` 系统来源字段,避免伪造证据。 事实质量:`POST /api/fact-checks` 请求 `{target_type:"run"|"draft"|"content_generation",target_id,mode:"deterministic"|"semantic",consent_to_model_provider?,force?}`, 可靠入队后返回 202。确定性检查免费,覆盖名称、数字、单位、百分比、币种、日期、年份和 URL/domain; 事实列表未显式写“字段名:值”时,以事实中出现的品牌名/别名作为语义锚点;带 `来源:https://...` 的标准事实不会把整句误当字段名。只有同一锚点且存在可比较数字或 URL 时才下确定性结论, 只有相同语义锚点且高置信的确定性 `contradiction` 会阻止草稿进入 done。语义模式必须显式 `consent_to_model_provider:true`,按创建任务时冻结的 `fact_check` 动态定价(无后台覆盖时默认 30 MP / 10 CU) 预留并结算;响应 `billing.points` 是该任务冻结的 MP,客户端不得硬编码默认价; LLM 结果一律为 candidate,不直接阻断。任务状态为 `queued|running|retry_wait|billing_pending|succeeded|failed|cancelled`,具备 lease、CAS、有限重试、取消和 provider 成功后的独立账本恢复,不会为账本重试再次调用模型。semantic 终态按 `fact_check:{job_id}` 幂等写入 UsageEvent:成功记录冻结消费/成本,失败或取消均为 0。Finding `category ∈ consistent|contradiction|unsupported|unknown`,审核状态 `candidate|confirmed|dismissed|resolved|stale`;`PATCH /api/fact-findings/{id}` 请求 `{status:"confirmed"|"dismissed"|"resolved",reason?,expected_version}`。事实升版后旧 finding 派生 stale, 自动重跑免费的确定性检查,不隐式触发语义扣费。草稿正文始终先保存;请求 done 被硬矛盾拦截时返回 409 且 body 含 `saved:true,draft,gate`,客户端必须重载已保存草稿。 客户默认消费“事实风险”聚合:`GET /api/brands/{id}/fact-quality/summary` 返回当前事实版本的 `actionable_total/high_risk_total/confirmed_conflicts/unsupported_claims/pending_review/issue_counts/status_counts/signal_summary/latest`; `GET /api/brands/{id}/fact-findings?cursor=&limit=20&severity=&status=` 只分页返回明确提及品牌/别名的 冲突、缺少依据或已确认问题,`limit` 最大 100。普通数字/日期信号聚合到 `signal_summary`,不制造逐条人工任务。 旧 `GET /api/brands/{id}/fact-quality` 兼容保留但最多返回 500 条,并附 `partial/finding_total/next_cursor`。 人工采样已从客户产品退役;以下历史审计接口仅真实 admin 且显式 `X-MIA-Admin-Mode: 1` 可调用。`POST /api/manual-sample-batches` 必须带 `Idempotency-Key`(1–160 字符),请求含 `keyword_id`、可选 `brand_id/name/period_key/longtail_ids[1..500]/engines/samples[1..20]/template_version`; 创建时冻结品牌、竞品、问题、引擎、采样次数和模板并创建 `Run.type=manual`,矩阵最多 5,000 项。 批次状态 `collecting|reviewing|finalized|cancelled`。网页逐条录入使用 `PUT /api/manual-sample-batches/{batch_id}/items/{item_id}/response`,JSON 请求为 `{expected_version,answer,citations?,environment?,personalized?,sampled_at?,share_url?,notes?}`; `expected_version` 是批次 CAS 版本。回答上限 200,000 字符,引用最多 100 个 HTTP(S) URL, `share_url` 也只接受 HTTP(S) URL,`sampled_at` 必须是含时区 ISO 8601(省略则用服务端当前时间)。 相同内容重复提交即使仍带上一版本也幂等返回;内容不同的过期版本返回 409 `version_conflict`; 非本批 item 返回 404,跨 owner 返回 403,`finalized|cancelled` 返回 409 `batch_locked`。 逐条录入免费,复用相同字面守卫、人工复核和最终聚合口径,不创建额外计费事件。 批量导出 `format=csv|xlsx` 的固定列为 `sample_id,engine,question,answer,citations,sampled_at,environment,personalized,share_url,notes`;导入文件最大 10 MiB/5,000 行;原始 multipart 在解析前受 `MANUAL_IMPORT_BODY_MAX_BYTES` 限制(默认 11 MiB, 生产 Ingress 必须相同或更小)。`expected_version` 为必填 query,整批 preflight,任一错误默认整批不落库,并防 CSV/XLSX 公式注入;XLSX 在隔离子进程解析前校验条目数、路径、加密、单项/总解压大小和压缩比, 超时或资源超限整批拒绝。隔离解析由 `MANUAL_XLSX_PARSE_CONCURRENCY`(默认 1)、 `MANUAL_XLSX_PARSE_QUEUE_LIMIT`(默认 4)、`MANUAL_XLSX_PARSE_QUEUE_TIMEOUT_SECONDS`(默认 10 秒)约束; 满载/排队超时返回 503 `xlsx_parser_overloaded|xlsx_parser_queue_timeout`。 `environment ∈ clean|incognito|dedicated|personal|unknown`;personal、unknown 或 personalized 默认 `needs_review`,批准前不进入指标。`POST /api/responses/{id}/review` 请求 `{action:"approve"|"reject"|"override",reason?,payload?,expected_version}`;override 只允许 `mentioned/rank/sentiment/citations`;`mentioned` 只能确认与已有可核验品牌 span 一致的结果,不能在无正文 证据时人工制造命中(应先更新显式品牌别名并重新判定)。人工复核事件 append-only。rejected 从分母排除; finalize 前不得有 needs_review。collection profile 会进入可比签名,API 与个人账号样本不默认完全可比。 回答内竞品信源差集:`GET /api/runs/{id}/competitive-source-gaps` 只在同一成功回答内按 「实体提及 + citation」确定性聚合品牌独有、竞品独有和共享域;同回答同域去重,输出问题数、回答数、 模型、URL 与证据 ID。它的正式方法名是“回答内共现信源”,只表示共现,禁止解释为因果归因。 `/competitive-source-gaps/evidence` 使用 `domain`、可选 `entity/offset/limit(≤200)` 下钻;只有当前 competitor-only 域可转草稿 ActionItem。 问题攻防:`Longtail.measurement_scope ∈ benchmark|brand_probe|quickwin`; `GET /api/runs/{id}/question-ownership` 只把 benchmark 分类为 `brand_only|competitor_only|shared|neither|unmeasured`,brand_probe/quickwin 不进入 KPI 分母。 competitor_only 可转内容 brief 或 draft ActionItem;所有分析只接受 owner 的 done/partial Run。 搜索补全与动量:`POST /api/keywords/{id}/suggestion-snapshots` 请求 `{providers:[“baidu”|”so360”|”bing”|”sogou”],market?,language?,force_refresh?,seed?}`;shenma/Google 只保留历史 快照读取兼容,不能再创建或刷新。`seed` 可为当前主词下任意待研究 的种子短语,省略时兼容旧客户端并使用当前 `Keyword.text`。服务端以 NFKC、合并空白和 casefold 后的 `seed_normalized` 隔离数据;同一关键词、本地日、规范化 seed、provider 默认只创建一个快照, 不同 seed 在同一天互不覆盖,force_refresh 创建新 generation。任意 seed 始终归属于原关键词与品牌, 接受候选后仍加入该关键词的问题池。默认 allow-list 为 `baidu,so360,bing,sogou`(搜狗走腾讯托管端点 `sor.html5.qq.com/api/getsug`,2026-08-21 已验证),可用 `SUGGESTION_PROVIDERS` 按环境缩减 或置空禁用;只有 allow-list 内 provider 可运行。每个 provider 独立 durable job;同日非 force 请求若 `market/language` 与已冻结快照不同,返回 409 `suggestion_locale_conflict`,不会静默复用错误地区;force_refresh 会原子冻结新的 seed/market/language/digest。 部分失败仍返回 202 且带 `jobs/errors/partial`;状态 `queued|running|retry_wait|succeeded|failed|cancelled`。快照保存 provider、冻结 seed、market、language、 原始/标准化词、排名和 captured_at;最多 100 个候选。候选只有用户调用 `POST /api/suggestion-candidates/{id}/accept` 后才创建 `Longtail.source=vendor`,不会自动修改题库。 至少两个日期快照才有趋势;产品名固定为”补全词动量”,新出现或平均排名提升至少 3 位为 rising, 不得称为搜索量。百度 GB18030 JSONP、360 UTF-8 JSON、必应 UTF-8 JSONP(qsonhs 端点)、搜狗 GBK JSONP 都按固定结构解析,绝不执行返回的 JavaScript;这些公开接口都没有地区参数或 SLA。响应 `providers[].capabilities` 会如实披露。 provider 请求优先使用 Redis(配置 `SUGGESTION_REDIS_URL` 或 `REDIS_URL`)做跨 Pod 限速、请求合并和 15 分钟缓存;未配置 Redis 时 PostgreSQL advisory lock 仍会串行同 provider 的跨 Pod 调用,缓存和熔断 fail-open 到 Pod 内保护。百度/必应/搜狗请求总超时默认 8 秒,360 使用独立的 `SUGGESTION_SO360_TIMEOUT_S`(默认 15 秒、TLS connect 最多 12 秒),且每个 provider 最多尝试 3 次;失败快照的 `error_code` 保留 provider 与阶段,例如 `baidu_timeout`、`so360_timeout`、`bing_timeout`、`sogou_timeout`、 `baidu_payload_invalid`、`bing_payload_invalid`、`sogou_payload_invalid`、`sogou_empty_response` (搜狗对境外网络返回空体,归入可重试的网络类,HTTP 503,不是终态 payload 变化)、`{provider}_network_error`、 `{provider}_rate_limited`、`{provider}_http_4xx|http_5xx`,客户端不得向最终用户直接展示内部错误码。 安全诊断仅保存 `provider_http_status/provider_error_reason/provider_latency_ms/cache_source_snapshot_id`, 不保存第三方响应正文、URL 或 header。合法空数组是成功的 0 个候选。force refresh 会保留同日上一 verified generation 的候选,刷新中或失败时也可作为 stale 回退;否则回退更早的 succeeded/partial 快照。候选与 provider 返回 `stale/stale_reason`,顶层返回 `stale_providers`;stale 候选可由用户明确 接受进入问题池,但绝不冒充本次刷新生成的新结果。 `GET /api/keywords/{id}/suggestions?seed=...` 按规范化 seed 返回快照、候选与趋势;省略 seed 使用当前主词, 顶层同时返回 `seed`、该关键词已有的 `available_seeds[]`,以及每个 provider 最新快照组成的 `jobs[]`; 单个 `job` 仅为旧客户端兼容字段,新客户端必须独立轮询全部 `jobs[]`,不能因一个来源先结束而停止等待另一个。 `GET /api/keywords/{id}/suggestion-trend?seed=...` 使用相同隔离口径,绝不混合不同 seed。suggestions 顶层 `enabled_providers[]` 是当前 allow-list 的权威可提交集合; `providers[].status` 可能来自历史快照,不能用于推断 provider 当前仍启用。 可编辑阵地地图:阵地是品牌在百科、媒体、社区、社交、评测、开发者平台等外部信源中的建设对象, 不等同于发布连接器。`GET /api/keywords/{id}/territories` 返回版本化 catalog 与每个阵地的 system/manual 两层; system 含 `coverage_status/suggested_priority/suggested_plan/evidence_snapshot`,manual 含 `status/priority/goal/target_count/cadence/assignee/content_types/notes`;manual.status 仅允许 `building|maintaining|null`,用于表达团队实际建设阶段,refresh 永不覆盖。system coverage status 为 `unmeasured|absent|present|building|maintaining|not_applicable`,priority 为 `low|medium|high`;来源为 `system|observed|manual`。`category` 为 `owned|partner_media|industry_media|encyclopedia|community|social|review|retailer|competitor_owned|developer|other_source`; `partner_media` 只能由用户对 observed/manual 阵地确认,系统绝不根据一次引用推断合作关系。 refresh 使用最新或指定 owner Run 更新证据和建议,永不覆盖 manual 层;品牌共现域为 present,竞品独有且覆盖至少两题建议 high,未知域自动生成自定义域阵地,materials 品牌的自有官网为 not_applicable。PATCH 必须带 `expected_version`,归档的 query 也必须带;冲突 409。支持自定义、筛选、 归档、恢复、证据下钻和幂等转 draft ActionItem,但不代表向外部平台发布。归档后使用 `GET .../territories?include_archived=true` 查看,并以当前 `version` 调用 `POST /api/territories/{territory_id}/restore`;恢复保留来源分类、负责人、目标与历史证据。 - 检索:`POST /api/keywords/{id}/runs` 发起;`GET /api/runs`、`/api/runs/{id}`、`/api/runs/{id}/status`。 `GET /api/runs` 可选 `brand_id`、`keyword_id`、`task_id`、`monitoring_profile_id`、`before_id` 和 `limit`(1–1000)先在服务端缩小历史范围;默认排除 `diagnostic_for_run_id` 非空的内部分析 Run。 只有真实 admin 显式开启 Admin Mode 后才可传 `include_internal=true`;普通身份传该参数返回 403 `admin_mode_required`。不传 `keyword_id` 时仍返回当前身份全部可见 Run,兼容旧客户端。 指定 profile 时会校验 active、owner 和 keyword,并只返回该 profile 的 Run;指定 keyword 但缺 profile时 同时返回所有原子市场以及 `monitoring_profile_id=NULL` 的历史归档,不再因创建 Profile 隐藏旧记录。每行及 `GET /api/runs/{id}` 详情均返回 `brand_id/keyword_id/monitoring_profile_id/measurement_batch_id`(非批次历史 Run 的 batch 可为 null);选中 profile 尚无 Run 时返回空列表,绝不回退同 keyword 其他 profile。结果按 `id` 倒序,`created_at` 为带时区的 ISO 8601。`before_id` 为严格小于,可与 `limit` 组合做游标翻页;这些筛选不能扩大本地 Account/owner 可见范围。 带冻结 Manifest 的 Run 还返回客户安全的 `customer_scope:{questions,models,samples,markets,expected_slots}`;其中 `expected_slots=questions×models×samples×markets`,当前单市场客户页的 `markets` 固定为 1。缺少冻结题板的历史 Run 不猜测该范围并返回 `customer_scope:null`。 `DELETE /api/runs/{id}` 对执行中的 Run 是 cooperative cancel:返回 HTTP `202` `{"run_id":id,"status":"cancel_pending"}`,worker 在安全点收敛为 `cancelled` 并幂等释放 hold; 对已经 `done|partial|failed|cancelled` 的 Run 返回 409 `run_requires_explicit_erasure`;终态客户内容只能由 Project owner 或获授权的非 Observer admin 通过带确认串和幂等键的 durable erasure job 物理擦除。 Terminal `done|partial|failed` means that all planned slots, some planned slots, or no planned slots produced a usable response body for customer display. This customer completion state is independent from formal measurement eligibility. 每个「长尾×模型×采样」对网络超时、 连接失败、429、5xx 和响应解析异常做分类重试,**含首次最多 5 次**,带抖动退避并受单模型槽位 600 秒总时限约束;豆包 Responses API 对 `incomplete_details.reason=length|max_output_tokens` 按 provider 规则处理,不再由应用侧设置固定输出预算, 也不对模型输入做应用侧截断;确定性的空正文/其它输出上限按 provider 规则定向处理, 401/403、权限/余额/额度、模型不存在、配置缺失等确定性错误立即停止。应用不截断模型输入或输出;最终仍失败则记错且不进入指标分母。 大批量 Run 按稳定的「长尾×模型×采样序号」槽位分批持久化(`RUN_PERSIST_BATCH_SIZE` 默认 12,范围 1–48);`RUN_PIPELINE_QUEUE_SIZE`(默认 36、范围 1–36)严格限制每个 worker 已启动但尚未持久化的槽位, 容量同时计算运行中 Task 与已完成缓冲,检查点先持久化再补充队列;`queue_depth` 埋点记录真实剩余深度而非配置估算。 每个 Provider 调用前先提交确定性 `ModelAttempt(running)` 调用意图。恢复 claimant 按下一尝试时间、 租约到期时间从复合索引取 Run,Pod 重启后跳过已有 Response 的槽位;没有调用意图的槽位自动续跑, 已有调用意图但没有 Response 的槽位标记为 `provider_outcome_unknown` 并禁止自动重放,避免重复调用和计费。 worker/证据事务的非终态异常会立即释放租约并按最长 300 秒的指数退避 重新入队,使其它 Run 仍可公平恢复;对外 `status` 在收敛前保持 `running`,不会伪造终态或指标。`status` 的 `completed/succeeded/failed` 直接反映已持久化进度. For v5 controlled Runs, Points settle only when at least one response satisfies the frozen controlled measurement contract; a fallback-only Run can be `done` while its hold is released. Legacy billing retains its existing body-success rule. Failure placeholders never settle Points. `POST /api/keywords/{id}/runs` 的 v5 监测上下文、双通道、真实性等级、Manifest、错误和指标资格见上面的 “Data Trust v5”。中国大陆受控基准的 Tavily lane 可使用 `country=china`,全球补充 lane 与其它市场生成 独立本地化查询;完整 provider 候选信封会冻结到 RawArtifact,模型来源包与 SourceObservation 则按规范化 URL 去重并保留首个确定性代表,避免追踪参数或重复摘要重复计数;区域策略只记录纳入/排除原因。 `unknown` 绝不包装成“海外”,也不承诺模型供应商或 mia-geo 的出口 IP。 新 Run 始终启用有界检索恢复并在 Manifest 搜索策略中冻结该决定;历史 Manifest 继续按其冻结策略重放, 缺少该字段的历史 Manifest 固定按原始单次查询执行。检索恢复对瞬时 Tavily 失败做有界退避, 对干净空结果执行同查询重试与确定性本地化变体,任一路径最多调用 Tavily 五次。 既有 Manifest 中的历史策略仅用于精确重放,不用于新 Run。每次尝试的策略、查询 SHA-256、状态、候选审计和最终选中包都写入不可变 retrieval RawArtifact;不降低分数/地域阈值、不跨 lane 复用来源,unknown 永不进入受控来源包;规范化查询变体只存在于内部不可变证据,不进入客户状态或日志。 Frozen `shadow` policies run an auditable three-arm, brand-neutral query-shaping experiment: current query, language-label removal, and frozen topic+industry shaping. The planner cannot read the brand name or inject it, records each arm/hash/count in the retrieval artifact, and never adopts a shadow pack. `scripts/replay_retrieval_threshold.py` replays stored candidates at 0.15 entirely offline and emits typed `standard_pack|low_score_pack|evidence_unavailable` decisions. `low_score_pack` is proposal evidence only; runtime adoption is forbidden unless a future Run Manifest explicitly freezes that policy. If all five retrieval attempts still produce no eligible source pack, the RetrievalAttempt remains auditable as `evidence_unavailable` and the adapter is called without a MIA source pack. Adapters with native search use their frozen native-search identity; other adapters produce a memory-only answer. These answers complete the customer Run without a delayed wait, remain excluded from formal controlled-grounding KPI where required, and are not projected as customer-visible errors. An empty `global_supplemental` (customer label: 境外来源视角) pack remains visible in diagnostics but is excluded from customer failure counts, completion state, and answer denominators; target-lane empties retain their normal incomplete-measurement semantics. After a Run, a domain observed as target-lane `unknown` in at least three distinct retrievals creates an idempotent `source_registry_proposal` RawArtifact containing only evidence references. Runtime never edits `SOURCE_REGISTRY` or opens a pull request: external verification, reviewed source change, and version bump are an explicit human merge boundary. 每次尝试的 provider/model/token/延迟/错误会进入内部 `query_attempts` 账本;全部失败释放 hold、用户消费 0, a Run with at least one formally eligible controlled response settles the frozen one-price charge, while fallback-only or all-failed execution releases the hold; provider attempts and failed/fallback model costs remain internal accounting facts and do not create per-retry customer charges. `POST /api/runs/{id}/retry-failures` 只接受 owner 的 legacy_unfrozen `done|partial|failed` Run,只补跑当前失败的 “问题×模型×样本”槽位;可选 `Idempotency-Key`,省略时按当前失败槽生成稳定键。返回 202 `{retry_batch_id,run_id,status,total_slots,processed_slots,succeeded_slots,failed_slots,error_code}`。 每个补跑批次单独预留/结算并写审计,成功覆盖原失败槽且保留旧 `query_attempts`,随后重算指标; 同键重放不重复调用 provider 或计费。非终态 409 `run_not_terminal`,无失败项 409 `no_failed_slots`, v5 在创建批次或计费前返回 409 `v5_versioned_retry_unavailable`,不会覆盖 Response 或删除来源; 跨 owner 403;Admin 观察模式统一 403 `admin_read_only`。 Terminal v5 Runs use a separate immutable recovery generation instead of reopening the Run: `GET /api/runs/{id}/recovery-status` returns a typed frozen-manifest slot count, `recoverable`, `action=recover_missing_slots`, and retryability reason, and `POST /api/runs/{id}/recover-missing-slots` creates an idempotent `RunRetryBatch` successor for `failed|partial|dead_letter` sources. The source status and terminal event remain unchanged. The successor freezes the source Manifest hash and exact missing slot keys, checkpoints `calling` before every provider invocation, and commits one slot at a time. Restart skips committed slots and reports `provider_outcome_unknown` rather than replaying an ambiguous paid call. Customer cancellation and Manifest integrity errors are deterministic non-retryable outcomes. Before a provider call, typed transient database, network, Platform, billing, and provider pipeline failures use a bounded exponential retry (maximum four recovery attempts and 300 seconds) stored in the existing batch checkpoint JSON; the recurring claimant honors that due time. Validation, authorization, configuration, provider auth/model/parse, integrity, and unknown failures do not retry. Once a slot is checkpointed as `calling`, automatic replay is intentionally forbidden unless the Response row proves the call committed; this explicit ambiguity boundary prevents duplicate provider calls and Points settlement without adding a lease or attempt table. Initial Run execution uses the same typed classification and its existing `Run.attempt`/`Run.next_attempt_at` fields. The recovery hold is a separate Account-owned, frozen proportional reservation; same-key admission and worker replay never create a second hold. Existing Run status responses also expose the same object at `progress.recovery`, plus an effective recovered status while retaining `source_status`; it includes `state/automatic/attempt/next_attempt_at/preserved_answers/reason`, and a recurring claimant rechecks stale generations every 30 seconds. New Manifests freeze `algorithm_versions.execution_contract`; compatible releases may resume across `code_revision` changes, while historical Manifests without that field retain their original exact-revision fence. - 回答诊断:`GET /api/runs/{id}` 的 `results[]` 除原问/原答/判定外,返回 `provider`、真实 `model_id`、 `finish_reason`、`in_tokens`、`out_tokens`、`reasoning_tokens`、`latency_ms`、`error_code`、`judge_error`、 `grounding_sources` 与 `citations`,便于区分模型错误、判定降级和真实引用。响应自身还包含 `run_id/brand_id/brand_name/brand_aliases/keyword_id/keyword/keyword_text/task_id/type/status/models/total/created_at/competitors/competitor_entities`; 每项结果稳定包含 `response_id/model_key/model/longtail_id/sample_index/competitors_found/ranking/rank`,以及 `brand_match/query_qualifier/related_brand_candidates/competitor_matches/other_brand_mentions/judge_method/judge_model/judgement_version/decision_reason/text_mentions`。 `brand_match` 即使未命中也返回 `mentioned:false` 与稳定未命中原因;`other_brand_mentions[].configured=false` 明确表示仅为模型发现候选,不能混入已配置竞品出现率。`mentioned` 仍只认规范名或已确认别名的连续字面 证据;繁简体、大小写、全半角、空格和标点可归一。地区前后缀、母子品牌推断和词序重排只进入 `related_brand_candidates[].status="possible_related"`,`counts_toward_kpi=false`;查询中的“香港”“黄金”“就业”等条件 独立进入 `query_qualifier.status=satisfied|missing|not_applicable`,不能反向放宽品牌 KPI。 `brand_match` / `competitor_matches[]` 的 `occurrence_count` 与 `matches[]` 给出全部非重叠正文证据;客户投影中的 `observed_brand_match` / `observed_competitor_matches[]` 同样只描述原文观察,不改变正式判定; 每个品牌 `matches[]` 项都包含 `canonical_name/matched_alias/matched_text/match_type/start/end/rule_version/decision_reason`, 且 `start` 含、`end` 不含,必须满足原回答 `slice(start,end)==matched_text`。`mentioned:true` 只在至少一个 这样的有效 span 存在时成立;缺失或冲突的历史证据在只读投影中降级为未验证观察,不计入正式指标。 `recommended/recommendation_rank/rank_basis` 把“正文出现”和“明确推荐”分开。规则降级只有出现证据, 不用正文顺序伪造排名。`text_mentions.keyword/longtail` 分别给出主词和当前问题的精确原文次数与 offsets。 因而详情页不需要额外拉取列表补元数据。`ranking` 只含明确推荐品牌,标量名次使用 `rank`。普通身份只返回 旧投影只返回有效正文的 `results`,且排除被人工驳回(`review_status=rejected`)的样本;每项结果带 `review_status` 字段(`needs_review` 行保留展示供标注)。`by_model` 仍保留模型切片的兼容 `attempted/succeeded/failed/coverage_rate`;只有一行同时提供完整的 `attempted/completed/succeeded/failed/excluded/missing` 时,客户端才可把整组数据作为同一模型口径使用, 不得把模型切片的 attempted/succeeded 与全平台 summary 的 failed/excluded/missing 拼接, `model_errors[]` 仅在真实 admin 观察模式提供 `{model,kind,count}` 的“回答过长/服务繁忙/网络超时/配置异常/暂未返回”安全分类; 豆包底层错误稳定区分 `byteplus_network_error/byteplus_timeout/byteplus_rate_limited/byteplus_service_unavailable/byteplus_response_incomplete`; 网络、超时、429、5xx 和响应解析异常进入外层最多五次(含首次)退避,带抖动并受单模型槽位 600 秒总时限约束;鉴权、权限、无效模型与余额/配额错误不重试。输出长度由供应商协议决定,应用不再设置固定预算。 最终失败行仍落库但不向客户展示;普通身份的结果投影只返回有可展示正文的行,不返回 `model_errors` 或失败/跳过状态标记。真实 admin 身份同时带 `X-MIA-Admin-Mode: 1` 时返回失败行、`model_errors`,且每条结果额外含 `query_attempts[]` 供诊断。 - 洞察:`GET /api/keywords/{id}/insight?monitoring_profile_id=`(含**综合能见度分 visibility_score 0–100**、提及率、Top3、平均排名、声量排名榜、期间对比)。 profile 会按 active、owner 与 keyword 校验;同 Topic 多个当前正式市场 profile 缺参返回 409 `monitoring_profile_required`,历史非正式市场 profile 不参与默认选择但显式 ID 仍可只读访问。 latest/latest-any/trend 均固定在同一 profile;该 profile 尚无 Run 时返回 `has_data:false,run_status:"not_run"`,不会读取另一市场的最近 Run。 尚无完成 Run 时返回 `has_data:false`、最新 `run_id/run_status`,并把已配置竞品放入 `competitor_ranking[]/competitor_mentions[]`;其数值指标为 `null`,`tracking_status` 为 `running|unavailable|not_run`,避免把「尚未测量」误报成零提及。 客户可见指标名称由 `backend/app/metrics/catalog.py` 统一定义。综合分 v4 = `100 × ((45 / 100) × 品牌提及率 + (30 / 100) × 品牌前三推荐率 + (25 / 100) × 正面回答/成功回答)`; 返回 `score_version:"4.0"`、`positive_presence_rate`,旧 `positive_rate=正面/被提及` 保留兼容。 run 聚合/洞察响应始终返回规范计数 `attempted_answers/completed_answers/succeeded_answers/failed_answers`, 并保留已弃用兼容别名 `attempted/completed/succeeded/failed`;对应值必须严格相等。低于 80% 时有 `coverage_warning`。 `attempted`、`completed`、`succeeded`、`failed`、`valid_count`、`brand_rank`、`rank`、`sov_rank`、`share_of_voice` 和 `brand_share_of_voice` 仅保留一个发布版作为兼容别名;本变更发布且跨表面合同测试确认正式前端、报告和 Agent 消费方均只读取四个规范回答计数、`mention_rank` 与 `tracked_set_share_of_voice` 后删除这些别名。 `GET /api/runs/{id}` 的聚合顶层与 `summary` 还以 **target_market lane** 返回 `excluded/missing/provisional`,以及已弃用兼容别名 `errors`(始终严格等于 `failed_answers`)。 Judge timeout, 429, and 5xx failures make at most 3 attempts. Retry waits use bounded exponential backoff of 1/2 seconds; `Retry-After` raises a wait up to the 4-second per-wait safety cap. 仍不可用或遇到非重试错误时,该回答标记 `judge_unavailable`;成功正文仍进入 legacy 回答率分母但不贡献判定型分子,v5 指标继续按冻结证据门禁返回不可用而不伪造值。 聚合顶层与 `summary.judge_unavailable` 返回对应回答数,回答详情的 `judgement_status/judge_error` 暴露不可用状态。 `failed` 只统计真实完成但失败的模型调用,绝不再按 `attempted-succeeded` 反推;`completed` 只统计已落地的 terminal attempt(legacy 为已落库 Response),`excluded` 是 terminal 但不符合冻结测量资格的 attempt, `missing` 是冻结计划中尚无 selected attempt 的槽位,queued/running 与其它未完成工作不进入 `failed/errors`。 legacy 聚合以全部已落库 Response 计算 `completed`,以 approved benchmark 子集计算 `succeeded/failed`, 两者之差计入 `excluded`;人工 Run 的 `attempted` 等于全部 Response 数,其他 Run 取冻结 `total` 与全部 Response 数的较大值,`missing=max(attempted-completed,0)`。 `provisional:true` 表示 Run 尚未终态,以上计数仍会变化。v5 调用方可把这组 target_market 计数与 `/api/runs/{id}/status` 的 `by_lane[lane="target_market"]` 对照;status 顶层会汇总所有 lane,不能在多 lane Run 中直接与聚合顶层比较。客户端必须直读这些字段,不得把未完成、排除或缺失伪装成失败。 `summary` 还提供精确计数 `mention_count/top1_count/top3_count/top10_count/recommended_count`,以及 `recommendation_rate=有明确推荐位的回答数/成功回答数`; `by_model[]` 每平台提供 `mention_count/top1_count/top3_count/recommended_count`;`recommended_count` 是「有明确推荐位的回答数(任意名次)」,与 Top3 计数不同义。客户端应直读这些计数, 不得再用比率 × 成功回答数反推。run 的 `summary` 与可完整分类的 `by_model[]` 还返回 `visibility_contributions:{score_version,components:[{key,rate,weight,points}]}`;输入不全时为 `null` 并带 `visibility_contributions_unavailable_reason`。顶层 `sentiment_distribution` 返回 `{total,positive_rate,neutral_rate,negative_rate,unavailable_reason}`;`total=0` 是真实零样本,冻结投影缺失则 `total=null`,客户端不得再用情感计数相除重算占比。 普通身份不返回最终失败/跳过行和 `model_errors`;admin 模式可读完整诊断与逐次重试记录。 `competitor_mentions[]` 以**已配置竞品**为对象返回 `{name,rank,mentions,total_mentions,share_of_mentions,answers_mentioned,total_answers,mention_rate,recommended_mentions,avg_position,best_position,platforms[],evidence[]}`; `platforms[]` 给出各模型的提及数/成功回答/提及率/模型内声量/平均与最高排名,`evidence[]` 给出 `response_id/model/model_key/prompt/rank/recommended/rank_basis/matched_text/match_type/occurrence_count/matches/answer_excerpt/created_at`。 配置竞品即使零命中也返回;榜单按提及数降序、再按平均推荐位排序,提供真实 `mention_rate`、`share_of_voice`、`recommended_mentions`、`avg_position`,不再提供跨口径代理分 (兼容字段 `competitor_benchmark=[]`);真实指标完全相同的行并列同一 `rank`,稳定名称排序只用于展示。 `mention_rate=提及该品牌的成功回答数/全部成功回答数`,同一回答可同时提及多个品牌,因此多个品牌的 提及率相加允许超过 100%;`share_of_voice` 才是在本品与已配置竞品提及次数之间分配、合计 100% 的构成份额。 同一回答同一品牌 最多计一次;`total_mentions` 是目标品牌与所有已配置竞品在成功回答中的总提及次数。未配置但被模型发现的品牌通过 `observed_entities[]` 作为无排名“待确认相关实体”返回,不进入竞品 KPI;`observed_competitors[]` 仅为旧客户端兼容。 - 提示词表:单次采样展示「排名 / 实际来源数」;只有同一问题存在多次采样时才展示「平均排名 / 引用覆盖率」。 - legacy_unfrozen 来源分析:`GET /api/keywords/{id}/sources`(**文章/页面级下钻 + 分模型(平台)视角 + 竞品跨平台排名**, `top` 被夹到 1–50),可选 `platform` 与 `region=mainland_china|hong_kong|other|unknown`;非法 `region` 返回 400。响应含 `regions[]`、`region_basis:"domain_suffix_hint"`,且 `by_domain[]`、`by_article[]`、 `by_platform[].top_sources[]` 都含 `region/region_basis`。该地区仅由 `.cn`、`.hk` 或其他两字母域名后缀 保守推断,不是 IP 或正文地理定位;`.com` 等无法可靠推断时为 `unknown`。`/sources` 与 `/sources.csv` 均接受 `monitoring_profile_id`;同 Topic 多 Profile 缺参返回 409,绝不静默选择另一市场。CSV 导出地区提示与判定依据。 来源分析行集与总览 KPI 成功回答同口径:仅统计已批准(`review_status=approved`)、`benchmark` 测量范围且正文非空的回答,`total_online_answers` 即该有效行数;与检索明细的「高频引用来源」同一行集。 - legacy_unfrozen 机会/建议:`GET /api/runs/{id}/opportunities`(机会分;每项含跨 Run/长尾词重生成稳定的 `opportunity_key`)、`/suggestions`(按模型投放策略)、`/content-briefs`(未命中词内容简报)、`/brand-tags`(品牌描述标签,随检索完成自动生成并持久化,读库优先)。`PUT /api/runs/{id}/opportunity-dismissals/{opportunity_key}` 以 `{opportunity_key,question}` 驳回一项;驳回状态归属 Run 的账号空间,并从后续 legacy/v5 派生结果中过滤。 `suggestions.strategy_by_model[]` 对本次 Run 中的每个模型均有独立平台/内容画像;`platforms` 优先保留本批实际引用中 可成稿的平台,再用该模型画像补足至最多 4 个,`source_basis` 明确区分“实测 + 画像补足”和纯参考画像, 不会因只测到一个可投来源而把所有模型错误显示成同一套默认平台。 - legacy_unfrozen run 级兼容报告:`GET /api/runs/{id}/report` 是**只读打包**(聚合 + suggestions/opportunities/brand-tags), 不触发内容简报生成:响应中 `content_briefs` 恒为 `null` 并附 `content_briefs_hint:"use GET /runs/{run_id}/content-briefs"`;工作台不在首屏调用; 新客户端应先读 `GET /api/runs/{id}`,再按需分别请求 suggestions / opportunities / brand-tags。 `content-briefs` 可能触发实时 LLM 生成,只能由用户明确操作触发(走显式端点 `GET /api/runs/{id}/content-briefs`);模型不可用或返回空结果时,响应字段不变并 降级为可编辑的确定性简报,所有数字、引述与品牌特有断言均标记待核验。`materials` 品牌只建议引用已确认材料、 品牌事实或其他权威资料,不会虚构不存在的官网。 - 定时监测已开放:客户可以创建、编辑、启用、暂停、停止、归档任务并使用 `run_now`;创建、编辑和启用本身不扣费。scheduler tick 只物化到期 occurrence,claim worker 在真正执行前完成准入、输入冻结和 Points reserve。 - 计费开启时,每个 scheduled 或 `run_now` occurrence 在执行前冻结 Topic/Profile、问题、模型和价格输入,并以稳定的 `geo:monitor-occurrence:` 对 Platform Account 发起一次 Credit reserve。Worker 使用 GEO Service Account 和不可变 Account/initiator 归因恢复,不持久化、转发或合成人类 OIDC/PAT;完成后按实际 Points settle,未消费则 release,并用同一 occurrence 身份直接提交一次精确美元 Accounting。余额不足使 scheduled occurrence 直接失败并暂停任务,充值后须由用户手动启用;不会创建子 Run 或调用 provider。Platform 暂不可用或 reserve 回执未知时,同一 occurrence 进入有界 `retry_wait` 并重放同一幂等操作,不产生第二个 hold、Run、provider 调用或 report。`run_now` 在请求内 reserve,余额不足返回 402 且不留下 occurrence。 - 修改模型、问题数、市场或频率立即成为未来 occurrence 的任务配置;已经冻结或取得 hold 的 occurrence 继续按原输入完成,绝不被改写。可重试的 provider 技术失败在同一 occurrence 下追加不可变 Run generation,并以 `supersedes_run_id` 指向上一条 sealed 失败 Run;批次状态只按每个 Topic 的最新 generation 汇总,且 occurrence 只结算一次。Account 非 active、Service grant 失效、配置或余额错误阻止 provider 执行;已经取得 hold 的 occurrence 使用持久 Credit 回执完成结算。 - 每个 task 必须用 `monitoring_profile_ids:{"":}` 冻结每个 Topic 的 active Monitoring Profile,且 task 的 `measurement_mode` 必须与所有绑定 profile 一致;同一 Topic/模式存在多个当前正式市场 active profile 而未绑定时返回 409 `monitoring_profile_required`,不会按旧 `Keyword.market` 猜测;历史非正式市场 profile 不参与当前默认绑定,显式绑定仍执行现有正式市场写入校验。一个 task 可以包含多个 Topic,但所有 Topic/Profile 必须属于同一个 `CN|HK|MO` 市场;跨市场绑定返回 422 `single_market_required`。`market_preset` 不能由客户用于新建或更新任务,传入返回 422 `single_market_required`;服务端在该历史列中冻结已校验的单一 Profile 市场,供排程读取。`run_now` 与调度器只传冻结绑定;空绑定只有恰好一个同 owner、同品牌、同 Topic、同模式的当前正式市场 active profile 时才一次性固化,否则在创建 Run、调用模型或计费前 fail closed。配置更新立即用于未来 occurrence;已冻结、running 或 retrying 的 occurrence 继续按其不可变输入完成。`cycle` 支持 `hourly|daily|weekly|monthly`(`hourly` 按 `run_time` 的分钟每小时触发) 任务列表和详情中的 Points estimate 与实际执行共用 benchmark admission;`estimate` 及各 Topic 均返回 `admitted_question_count`、`excluded_question_count`,只有 admitted 数进入计费估算。 - 每个任务列表项、详情和写操作响应都返回客户安全的 `measurement_readiness`:顶层含 `ready/policy_version/required_count/actual_unique_count/missing_count/minimum_per_layer/layers/layer_gaps/reasons/next_action/keywords`; `keywords[]` 逐 Topic 返回同名计数与缺口。该对象不含问题正文、问题 ID、指纹或 opaque Platform ID。 Each Topic is admitted against its exact saved panel of 1–50 unique `benchmark` questions after canonical normalization; `required_count` follows that selected panel, not a fixed twenty-question minimum. Use the current `measurement_readiness` receipt, including the selected-model checks, as the admission authority. `recommendation|comparison|discovery|scenario|purchase_decision` 五类用于展示分布,不构成准入配额。 草稿/暂停任务可以保存;创建或更新为自动启用、`enable`、`run_now` 以及调度执行前都会复用同一准入。 不满足时返回 HTTP 422,`detail.error="measurement_admission_not_ready"`,并在 `detail.readiness` 返回同一安全缺口;不会创建 occurrence、Run、Points hold、用量事件或调用 provider。 历史不合格运行中任务由默认 dry-run 的 `python3 backend/scripts/pause_invalid_monitor_tasks.py` 审计;只有显式 `--apply` 才幂等设置 `status="paused"`、`auto_enabled=false`、`next_run_at=null` 和安全错误码,不自动生成问题。 - 任务购买 entitlement 时冻结当前普通 Platform Account 为 task 自己的 `billing_account_id`,绝不从本地数据范围、owner、email 或历史映射推导钱包。购买按服务端 task+period operation ref 幂等 reserve 并立即 settle;response loss 只恢复同一 operation。`run_now` 继续使用当前请求 Account 的独立 aggregate hold。 - 任务创建/更新和通用排期预估同样在持久化/计价前校验模型与测量模式能力;受控模型不兼容时返回 422 `controlled_benchmark_not_supported` 及 `unsupported_models`,普通客户或 Agent 请求原生模式先返回 422 `native_observation_read_only`。不会保存一条注定无法执行的任务,也不会展示虚假 MIA Points 预估。 - 投放记录:`GET/POST /api/keywords/{id}/placements` 登记与列出投放(字段 `platform/title/url/published_at/status/origin`,`status ∈ planned|generated|published`); `PATCH /api/placements/{placement_id}` 部分更新(`PlacementUpdate` 全字段可选,未传保持不变, `status` 只接受上述枚举,非法值 422);`DELETE /api/placements/{placement_id}` 删除并返回 `{"ok":true}`。三者均做归属校验(跨项目数据 403、不存在 404)。`status` 变为 `published` 或 `url` 变化时服务端 fail-open 重新入队投放验收——验收以 placement 的 `status/url` 持久化快照为事实源, 由成稿登记(初始 `generated` 且无 URL)的投放在标记已发布后即可走通验收闭环。 - 监测任务批次:`GET /api/tasks/{id}/occurrences` 返回执行批次时间线(含 scheduled/manual/legacy); 每个 `runs[]` 行与兼容 `GET /api/tasks/{id}/runs` 都回显该 Run 自身的 `project_id`、`project_name`、`brand_id`、`brand_name`、`keyword_id`、`keyword_text`、 `monitoring_profile_id`、`monitoring_profile_version`、`target_market`、`language`、`measurement_mode`, 以及 `run_id`、`task_id`、状态和 `report_id/report_status`。v5 Run 的项目 ID/名称快照、品牌、Topic、市场、语言与测量模式来自已封存 Manifest(项目身份在 admission 时由绑定 Monitoring Profile 固定);不得用调用前的页面选择或当前可变 Project/Brand/Keyword 重建历史范围。 `GET /api/runs/{run_id}` 顶层返回同一组完整范围字段,因此只有 `run_id` 的深链也能恢复权威上下文;调用方后续 导航必须携带这组范围,而不是继承旧选择。`run_now` 只物化一个 `queued` 的 manual occurrence 并立即返回 `occurrence_id`(不再返回 `run_ids[]`);执行由后台 occurrence worker 池异步完成,调用方轮询 `GET /api/tasks/{id}/occurrences` 直到批次终态。manual occurrence 以请求者当下的 Platform Account authority 冻结计费身份;执行失败只终结该批次,绝不暂停任务的定时排程。 调度 occurrence 与每个主词的子 Run 由唯一键绑定;Pod 重启后若子 Run 已存在,调度器等待该 Run 恢复并 读取其真实终态,不会重复创建、重复调用或把唯一键冲突误报成新的业务失败。即使执行调用在子 Run 已持久化为 `done|partial` 后才抛出驱动或遥测异常,批次也以该持久化终态为准,不会误记为主词失败。 scheduled 与 `run_now` occurrence 只负责创建/复用子 Run 并唤醒统一的分布式 Run claimant, 不在 occurrence worker 内执行 Provider;部署中断继续使用同一个 Run、Manifest 和 Points hold。 若 Profile/模型等非问题准备度 admission 前置校验已失败、尚未来得及创建正常 Run,服务端会为该 Topic 持久化一个 `status="failed"/total=0` 的失败子 Run 与终态事件;它明确表示 provider 未调用、Points 不需冻结或结算, 使 `attempted/completed/failed` 在刷新、重启和任务列表中仍可核验,而不是凭内存异常补数。 问题准备度不足是更早的任务级 422/自动暂停,不创建该 synthetic failed Run。 每个 occurrence 与任务列表的 `latest_batch` 都返回互斥计数 `attempted/completed/full_success/partial_success/failed/running`;`completed` 包含所有终态, `full_success` 只计 `done`,`partial_success` 只计 `partial`,弃用字段 `succeeded` 仅等于 `full_success`。 任一子 Run 仍活跃或状态未知时批次为 `running`;全 done 为 `done`;全 failed/cancelled 为 `failed`; 其余终态混合或任一 partial 为 `partial`。`runs[]` 逐项返回 `report_status` 与 `report_available`;客户端必须以 `report_available` 为准:仅当子 Run 为 `done|partial` (兼容历史 `success|succeeded`)、同身份 Report 为 `ready` 时才是 true,不能把 ready 当成充分条件。 - 意图流量:`GET /api/keywords/{id}/citation-likelihood`(各平台引用可能性) - 舆情趋势:`GET /api/keywords/{id}/sentiment-trend`(按平台/时间的情感占比) - 单题命中趋势:`GET /api/keywords/{id}/prompt-trend?longtail_id=&days=<1..730>&window_days=<1..365>&as_of_run_id=`(`days` 默认 60,`window_days` 默认 `days/2`)—— 一条 SQL 返回时间窗内各已完成批次该问题的 `{"points":[{run_id,occurred_at,hits,valid,mention_rate,mention_rate_unavailable_reason}],window_summary:{window_days,current,previous,delta_mention_rate,delta_mention_rate_unavailable_reason}}`; `current/previous` 各含 `start/end/hits/valid/mention_rate/mention_rate_unavailable_reason`。`as_of_run_id` 可把两个窗口固定到同 Topic/Profile 的某一批次时刻;批次不存在返回 404,Profile 错配返回 409 `monitoring_profile_run_mismatch`。行集与总览 KPI 成功回答同口径(approved、无 error、正文非空),`hits` 为被提及计数。 有有效分母且零命中返回 `mention_rate:0`;无有效分母返回 `null` + `no_valid_answers|no_valid_answers_in_window`; 任一对比窗不可用时 delta 为 `null` + `comparison_window_unavailable`。v5 单题冻结 revision 尚不可用时整个 `window_summary` 为 `null` + `v5_prompt_metric_revision_unavailable`。 纯读、不触发聚合重算,供提示词详情页画日期趋势,替代逐批次拉取 `GET /runs/{id}`。 - 回答明细(API 兼容名 snapshots):`GET /api/snapshots`(可按品牌/主词/run/日期/模型/提及筛原问、原答、 判定与真实引用,列表 `limit` 被夹到 1–500)、`GET /api/snapshots.csv`(**全量流式导出**,按同一筛选分批 取数逐块下发,不截断为前 500 条;首块含 UTF-8 BOM 供 Excel 识别中文)。两端点都接受可选 `tz` (调用方本地时区相对 UTC 的东向分钟偏移,如东八区 `480`,被夹到 ±840;仅影响 `from/to` 的天边界换算, 缺省 0 保持 UTC 天边界,存储始终为 UTC)。工作台顶部是品牌/主词唯一作用域选择器, `model` 同时接受稳定 `model_key`(如 `deepseek`)和响应 `models[]` 中的展示名(如 `DeepSeek`),两种写法 使用同一过滤口径,CSV 导出亦同; 页面内不再重复。**检索分析**针对单个批次聚合提示词与模型表现;**回答明细**针对当前主词跨批次列出逐条 原始回答,不做聚合。普通身份的 JSON 行只返回 `projection_version/run_type/longtail/model/model_key/measurement_control/authenticity_tier/measurement_lane/sample_index/sample_number/answer/mentioned/judgement_status/observed_mentioned/observed_brand_match/observed_competitors/observed_competitor_matches/rank/sentiment/competitors_found/citations/created_at`, 不返回原始回答 ID、批次 ID、Manifest 指纹、判定过程或执行诊断;显式 admin 观察模式保留诊断投影。 两者共享同一份 `Response`。普通身份不返回最终失败行, 也不返回被人工驳回(`review_status=rejected`)的样本——与 KPI 分母口径一致;admin 模式保留全量与调用诊断。 没有持久化 judgement 时,客户的 `mentioned=true|false` 筛选匹配对应的 `observed_mentioned` 字面观察;这不改变正式 KPI 口径。 回答明细是有界只读,不在 HTTP 请求内隐式扫描/重算全历史;品牌/竞品刚修改时应先等待上述 `reconciliation` 任务到 `done` 再按 `mentioned` 筛选或导出。 - 内容成稿(接 AutoSocial):`GET /api/autosocial/{status,skills,platforms}`、`POST /api/keywords/{id}/generate-article` (**202 异步**:POST 先持久化 Account-owned durable admission、稳定 provider 幂等键和加密审计归属;后台 worker 校验不变的 Account/Brand/Keyword 归属边界,冻结并提交完整 prompt 后,使用 GEO Service credential + immutable Account + optional initiating-User audit attribution 调用并轮询 AutoSocial 到终态。GET 仅安全加速同一 durable operation;不转发或恢复 User credential,也不是任务继续执行的前提)、 `GET /api/keywords/{id}/content-generations`、`GET /api/content-generations/{id}`、 `POST /api/content-generations/{id}/retry`、`POST /api/geo/score-content`(GEO 合规打分)。 生成中的请求会先持久化,成功和失败都保留历史;列表支持按平台、状态分页筛选,详情才返回完整正文和请求快照;成功成稿会返回关联的 `action_item_id?`,可直接进入审核/发布工单。 客户端必须以 `lifecycle_status` 而不是粗粒度 `status` 判定时序: `queued|submitting|running|retry_wait|upstream_generating|verifying` 都是非终态,只有 `ready|failed` 是终态; `status=succeeded,lifecycle_status=verifying` 仍须继续轮询。响应同时给出 `support_id`、`attempt`、 `max_attempts`、`next_attempt_at`、`next_retry_at`、`retryable`、`status_version` 和 `error_code`;错误码和文案均为客户安全值,不返回 provider 响应体。任务通常需数分钟,但没有固定的本地完成时限;自动重试及 5/30/120 秒退避可能延长总时长,任务会在后台继续。 AutoSocial discovery 的真实空数组表示当前 Account 没有可用目录;对接鉴权失败返回 502 `autosocial_unauthorized`,SDK/响应契约不兼容返回 502 `autosocial_contract_mismatch`,限流、超时或服务不可用返回 503 `autosocial_discovery_unavailable` 且 `retryable:true`。客户端不得把这些错误投影为空目录。 手工 retry 以 202 创建一个 durable successor,并返回 `parent_generation_id/root_generation_id/retry_depth/successor_generation_id?`;同一 parent 的重复 retry 请求 幂等返回同一 successor 和同一 provider idempotency key,不重复调用或重复计费,只有对终态 successor 再 retry 才追加下一代。非终态 retry 返回 409 `content_generation_still_active`。 `platform` 支持中文展示名或 AutoSocial key(如 `知乎`/`zhihu`),服务端规范化;无法映射 → `400 invalid_platform`。 小红书(`xiaohongshu`/`小红书`/`xhs`)只保留历史记录的显示识别,已从主动推荐、平台清单、writer 映射、新成稿、改写及重试中下架;请求这些值统一返回 `400 invalid_platform`。未传 `platform` 时默认 使用 `zhihu`;即使旧环境仍配置 `AUTOSOCIAL_PLATFORM=xiaohongshu`,服务端也会安全回落到 `zhihu`。 内容评分兼容 `{text,keyword?}`,也接受 `{text,keyword_id}`;传 ID 时按本地 Account/owner 校验并自动加载该主词的品牌/别名、全部长尾和配置竞品,响应的 `text_mentions` 返回 `brand/keyword/longtails[]/competitors[]` 的 `occurrence_count/matches[]`。 草稿重算、内容简报与 AutoSocial 成稿评分自动使用相同上下文。 内容简报与成稿写作规范禁止把无来源支持的数字、费率、等待期、理赔率、排名、产品条款或引述写成事实; 输入未提供可核验依据时,简报可以保留编辑侧的补证槽位,但交给 Auto Social 的社交正文必须改写为自然的通用建议, 不输出“待品牌方核实”、来源清单、裸域名或 GEO 内部术语。来源保留在证据/编辑元数据中,发布前仍需以品牌官网、 已确认材料、已确认事实或正式条款中适用于当前接入模式的来源复核。成稿的结构可提取性分只衡量机器是否容易理解, 与事实新鲜度、引用绑定和 `publish_eligible` 分开展示,不能用结构分替代发布资格。 - 内容评分:`POST /api/keywords/{id}/content-assessments` 请求必须在 `text` 与公开 HTTP(S) `url` 中二选一, 可带目标市场 `target_market`(仅 `CN|HK|MO`,且必须与该 Topic 的市场一致)和最长 120 字符 `idempotency_key`;市场不支持返回 422 `unsupported_target_market`,与 Topic 不一致返回 422 `target_market_mismatch`,显式幂等键重复但正文、URL 或市场不同返回 409 `idempotency_conflict`。 文本与网页可见正文最多分析 12,000 字符,超出会截断并返回 `truncated:true`。URL 抓取使用 DNS 固定的 SSRF 防护,每次重定向重新校验,只接受 `text/html|text/plain`,最多三次重定向、单响应 768 KiB、8 秒请求超时; 抓取失败返回脱敏 422,不返回 DNS、IP、provider 或响应体。 创建接口先免费持久化并返回 `assessment_id/status:"awaiting_confirmation"/instant_result/estimated_points`; `instant_result` 含七维 `mia_readiness_v1.0`(抓取与索引、目标市场、问题匹配、品牌实体、事实证据、 可提取结构、新鲜度)及每维内容内证据;第八维 `observed_retrieval_authority` 不进入 Readiness, 只投影最新同市场正式 Run 中同 Prompt、同模型**真实返回**且标题/摘要/域名/URL 可字面确认的竞品页面。 `comparison_evidence` 把结论固定为 `observed_difference|insufficient`,并返回 `cause_validation_status:"not_verified"`;没有配对诊断与后续正式复测时绝不写成确定因果。AI 可以评估七个维度, 但总分始终由版本化七维权重重算,不接受模型自由填写的总分。用户确认当前 MIA Points 后调用 `POST /api/content-assessments/{assessment_id}/deep-score` 请求 `{expected_points}`,服务端先预留再异步评分; 用 `GET /api/content-assessments/{assessment_id}` 轮询 `queued|running|succeeded|failed`。AI 瞬时失败最多三次退避 重试,最终失败释放预留;成功结算。有 hold 时,worker 会先保存 provider 结果,再持久化不可变的 settle/release 决策,只有 durable 计费事实成功落库后才发布 `succeeded|failed`;持久化暂时失败时保持 `running` 并仅恢复计费, 不重复调用 provider。无 hold 时按正常结果直接终止。预留时冻结当前权威 Platform Account,异步 worker 只按该冻结 Account settle/release;缺少冻结 Account 的历史行安全终止计费恢复,不从本地数据范围、owner 或邮箱推断钱包,也不发起错误远端结算。响应含维度问题、按 high/medium/low 排序的建议、`rewrite_handoff` 与 “评分用于诊断,不保证被模型引用”提示,只返回 MIA Points,不包含内部成本。所有记录按 account+owner 隔离。 - 内容工作室:`GET /api/content/templates`、`/api/keywords/{id}/drafts`(大纲→草稿→优化,含状态与打分) - GEO 技术稽核:`POST /api/audit` { "url" } → AI 爬虫可爬取性矩阵(豆包/字节·百度·通义·DeepSeek·GPTBot·ClaudeBot·PerplexityBot·Google-Extended·Bing)+ 内容就绪度评分 + 建议;该兼容 quick 接口仅运行确定性检查,免费且不预留 Mia Points。每一跳都校验 DNS 后直连已验证的数值 IP(HTTPS 保留原域名 SNI/证书校验),最多 3 次重定向、单响应最多读取 2 MiB。 - AI 配置候选:`POST /api/generate/briefs` 接受 `{brand?:string,industry?:string,region?:string="中国大陆",landing_url?:string,keyword?:string,stage:"topics"|"competitors"|"longtails",regenerate_nonce?:string}` 返回 `{briefs:[{id,label,description,prompt_hint}]}`(4-8 个实时 AI 生成方向;新建检索向导按 **品牌基础 → 市场范围 → AI 生成方向 → 主词 → 长尾词 → 竞品与确认** 六步展示, 用户多选后传入后续 `guidance`;LLM 不可用时返回 503 `recommendation_generation_unavailable`,不返回确定性兜底内容;非法 `stage` 返回 422)。 `GEN_BRIEF_MODEL` 已弃用;只可用 `GEN_BRIEF_TIMEOUT_S` 调整单次等待预算,不能更换模型。 `POST /api/generate/topics` 接受 `{brand?:string,industry?:string,region?:string="中国大陆",landing_url?:string,exclusions?:string[],guidance?:string[],regenerate_nonce?:string}` 返回 `{topics:[{text:string,reason:string,source:"ai"}]}`(成功时 6–20 个,全部来自本次模型响应);`guidance` 为用户勾选的 brief `prompt_hint` 列表,可省略。提供 `landing_url` 时,服务端会先抓取该落地页(复用 GEO 稽核同一条 SSRF 安全通道:校验 DNS 后直连已验证数值 IP、最多 3 次重定向、单响应最多读取 768 KiB),提取标题、 `description`/`keywords`/`og:*` 与可见正文摘录作为品类依据注入生成,使主词描述品牌真实经营的品类而不是 仅凭品牌名猜测;抓取超时或失败时静默降级为仅用品牌上下文生成,不会让整个请求失败。抓取预算由 `GEN_LANDING_FETCH_TIMEOUT_S`(默认 4 秒,上限 10 秒)控制,与模型预算 `GEN_ONBOARDING_TIMEOUT_S`(默认 12 秒,上限 30 秒)分开计量:模型预算在落地页抓取完成后才起算, 慢站点不会挤占生成时间。模型结果经过规范化、去重、目标品牌和排除词过滤,不以泛化模板补足; 失败按可否重试分类——模型完全未响应(超时/凭据缺失)返回可重试的 503 `recommendation_generation_unavailable`;模型已响应但经安全过滤后不足 6 个返回 422 `topic_candidates_insufficient`, 提示补充行业或减少排除词。品牌、行业、方向和落地页都无法提供有效主题上下文时返回 422 `topic_context_required`,不会成功返回一个词或空数组。 对 Chanel(含“香奈儿/香奈爾”等别名)只加强多业务线的实时生成指令,不注入固定主词种子,也不会把 “时尚与奢侈品”机械拼接成所有主词;所有品牌都必须以本次模型响应为唯一推荐来源。 `POST /api/generate/competitors` 接受 `{brand:string,industry?:string,region?:string="中国大陆",keyword?:string,exclusions?:string[],guidance?:string[]}`, 返回 `{brand:{name,aliases},competitors:[{name,aliases,existence_status:"pending_confirmation",existence_validation}]}` (最多 10 个,删除中英文圆括号说明、剔除目标品牌及变体,并把最多 3 个目标市场相关、指向同一实体的名称 变体作为可编辑建议;中国大陆、香港、澳门会过滤日文、韩文等无关脚本,产品/业务线/子品牌不进入别名); `existence_validation` 明示 `method:"not_performed"/provider_calls:0/points_reserved:false`。当前请求只为已计费的 Doubao 生成,不会静默追加未预留 MIA Points 的 Tavily/其他 provider 校验;用户在最终复核页编辑/删除并发起后, 才将当前目录整体确认为测量基线。初始生成不是穷举;Run 中重复观察到的新名称继续通过 suggestion API 提议, 接受后仅从下一次 Run 生效,历史 Run 的冻结实体快照不改写。 旅游/景点类在香港市场会提示兼顾大湾区及内地头部竞品。 `POST /api/generate/longtails` 接受 `{keyword:string,brand?:string,industry?:string,region?:string,market:string,language:string,exclusions?:string[],guidance?:string[],target_count?:integer[1..50]=20,required_count?:integer[1..50],additional_count?:integer[1..30],measurement_goal?:discovery|comparison|planning=discovery,measurement_target?:string,target_aliases?:string[],brand_aliases?:string[],need_topic?:string,landing_url?:string,existing_questions?:{text,intent,cluster?,source?,demand_signal?}[],demand_examples?:{text,provider?,rank?}[],regenerate_nonce?:string}`, The explicit `market` determines the analysis geography, taking precedence over the brand's descriptive `region`. Validate `market` and `language` against `GET /api/markets`; unsupported values return HTTP 422 `unsupported_target_market` or `unsupported_market_language` before the utility lifecycle, even when `existing_questions` already fills the required pool. Output follows the selected language, including Portuguese for `MO`/`pt`. Success returns `{longtails:[...],design,added_count}`. Design plans distinct user decisions for the requested new questions against the existing panel. Clusters are analysis labels; they never require repeating the same decision. Bounded quality top-ups may review surplus candidates, but the returned pool remains exactly within the requested target and the 50-question cap. The new-run wizard starts with 20 questions. To append, send the complete current draft in `existing_questions` and `additional_count=1..min(30,50-existing_unique_count)`; a pool of 21 permits 1–29. Existing rows retain their text, source and metadata. New questions fill only the gap, and exact plus semantic duplicate checks include the existing rows. Invalid capacity is rejected before a new Points hold or provider call. Historical oversized requests are accepted only to recover an existing exact paid request; they cannot start a new generation. Omitted design fields preserve legacy replay identity until retained pre-design requests expire. Public generation uses design → candidate generation → independent semantic review on the same pinned provider. Design separates organisation, measured entity, official domain, audience market and destination geography. Discovery is the default: it expands entity-bearing topics to the upstream user need before target selection. Explicit `need_topic`, targets and aliases can refine that design; the returned design remains visible for review. Selected guidance cannot override neutrality. Discovery rejects entity names, aliases, unrelated topics and leading unique-feature questions. Semantic review scores user-need relevance, not literal keyword overlap. Comparison and planning return `measurement_scope=brand_probe` and remain excluded from the organic benchmark. The new-run wizard can save them to the brand question library without starting a Run. Generated design metadata survives persistence in `demand_signal`; edited generated discovery questions are checked against their measured entities again. `demand_signal.provenance` is `model_synthetic` unless the wording exactly matches a supplied demand example. Supplied examples retain their source; they are not independently verified demand volumes. No proprietary conversation dataset or search volume is fabricated. Candidate generation has at most five bounded rounds under the existing shared 120-second utility deadline and single Account-owned hold. Failed design or review never bypasses the gate or substitutes template questions. The UI preserves the current draft on failure. 四类 AI 推荐固定走 BytePlus 豆包 Mini;brief 成功响应始终为 4–8 个方向,topic 成功响应始终为 6–20 个候选。主词、竞品等短推荐仍受 `GEN_ONBOARDING_TIMEOUT_S`(默认 12 秒,3–30 秒)约束; 长尾生成最多调用同一个 provider 五轮且共享 120 秒应用层截止时间;超时返回可重试的 `recommendation_generation_timeout` 并释放同一 Points hold。 provider 明确不可用时返回 `recommendation_generation_unavailable`;安全筛选后仍未达到最低正式问题池时返回 `longtail_candidates_insufficient`。前端先完成竞品生成再开始长尾词生成,在重新生成期间保留当前有效问题, 新题通过完整审查后追加,原有问题保留;长尾 provider 失败不显示内部诊断或伪造题目。 进程启动和 `/ready` 只校验可确定的 BytePlus 凭证/模型配置,不调用生成模型;provider 可达性、延迟和内容质量均由真实业务请求处理,不能因模型内容波动让 Pod 进入重启循环。 Before showing generation consent, authenticated clients call `POST /api/generate/estimate` with `{kind:"briefs"|"topics"|"competitors"|"longtails"|"plan",units:1..4}`. It performs no provider call and creates no hold; success returns only `{operation:"generate_assist",kind,units,unit_points,total_points,unit:"MIA Points"}`. Pricing unavailability is a retryable 503 `pricing_unavailable`; no fiat or provider cost is exposed. 这些生成接口及其前端请求均使用 `no-store`。`briefs|topics|competitors|longtails` 与 `/skill/plan` 共用 `generate_assist` 一口价(默认 15 MIA Points,动态定价可覆盖):服务端先创建当前 Platform Account-owned hold,再调用 provider;成功 settle,模型、超时或输入验证失败 release。调用方应发送 1–160 字符 `Idempotency-Key`;同一 User + Account + 路由下,同键同 payload 在 60 分钟内重放保存结果,不重复调用 provider 或冻结 Points,同键不同 payload 返回 409 `idempotency_conflict`。网络结果不确定时沿用原 key;收到明确失败并要重新生成时换新 key。 竞品超时返回空清单,不补造默认竞品;前端提示用户手动添加至少 3 个不同的有效竞品,并在不足时阻止发起正式测量。 四个计费生成路由要求 `current_user`,只在 `SkillJob` 保存 60 分钟幂等/结算状态,不创建品牌/Topic/Run, 也不会跨 account 修改配置;`generate/quickwins` 不属于本计费 lane。正式测量的 estimate/start 路径仍以统一 admission policy 做后端 correctness guard;onboarding 不向用户展示问题数量或意图配额门槛,也不要求手工补齐或重新验证。 - 监测任务不存在预付周期、entitlement 或 slot 购买 API;每次 occurrence 独立预扣和结算。历史暂停任务在功能恢复后仍保持暂停,不自动补跑或启用。 - 报告:每个 `done` / `partial` Run 完成后自动、幂等归档(同一 `run_id` 仅一个 Report 容器);`failed` 不生成指标或报告。 legacy Run 使用 2.4 ReportDocument;v5 Run 使用 Data Trust v5 小节定义的 append-only ReportRevision,二者不得互相回退。 legacy Run 已绑定 Monitoring Profile 时,ReportDocument 的 `market/region/test_language/measurement_mode` 必须读取该不可变 Profile;只有历史未绑定 Run 才回退到 Keyword 字段,禁止报告正文与 Run/任务范围不一致。 定时监测按周期标记时/日/周/月报,临时检索标记「自动报告」,无需普通用户手动生成。 `GET /api/keywords/{id}/reports?limit=1..100&offset>=0` 列表;`GET /api/brands/{id}/reports?limit=1..100&offset>=0` 品牌下全部主词报告;默认每页 50 条,响应体保持兼容的数组,并通过 `X-MIA-Report-Has-More: true|false` 表示是否还有下一页; 列表项附带 `task_id`/`task_name`/`occurrence_id`/`run_created_at`/`keyword_text`。 `GET /api/reports/{id}/document?lang=cn|en` 读版本化 `ReportDocument` JSON; `GET /api/reports/{id}/view.html?lang=cn|en` 返回与 PDF 同模板的在线 HTML,`/app/reports/{id}` 安全嵌入该模板; 该端点附带 `download=1` 时以 HTML 附件返回(优先命中预生成缓存,未命中兜底现渲); `GET /api/reports/{id}/download.pptx?lang=cn|en`、 `GET /api/reports/{id}/download.pdf?lang=cn|en`、`GET /api/reports/{id}/download.xlsx?lang=cn|en` 导出。 **下载计费**:HTML(`download=1`)/PPTX/PDF/XLSX 按冻结报告版本和格式幂等执行一次 `report_export` 预留并结算(默认 20 MIA Points,动态定价可覆盖);制品 ready 后才计费,同一版本、语言、 格式已有 completed settle intent 时不再 reserve,只返回同一制品;错误区分 `insufficient_credit|points_unavailable|report_artifact_preparing`, 在线预览(不带 `download`)免费; admin 观察模式只读缓存、不计费。 普通身份读取/在线查看/导出时保留失败模型/回答占位及覆盖率,但移除原始 provider 错误;admin 模式保留冻结快照内的诊断。 HTML / PPTX / PDF / XLSX 全部消费同一份冻结快照(legacy 为 **2.4**,v5 为 **5.0**),展示 `schema_version` / `generator_version`;只有模型、提示词和采样数完全可比的上期才计算增减,缺失响应按目标矩阵格 计失败。报告问题集合以 Run 当时保存的 `Response.longtail_text` 与 `Run.total` 冻结;运行结束后新增、删除或 改名的主词问题不会改写旧报告的样本数或分母。引用指标分别给出唯一 URL、唯一域名和回答引用覆盖率;竞品使用真实提及率、声量份额与 平均排名,不以代理分冒充可见度。HTML/PDF/PPTX 均分页展开完整明细证据;XLSX 以 data-only sheets 保存完全相同的 backing rows。品牌提及数等 KPI 全部直读冻结快照的权威值,四格式口径一致,不再各自重算。 legacy 报告在 Run 完成/定时任务/首次新建检索时**按 owner / admin 双受众异步并行预生成四格式**并缓存 (可落 S3,无 S3 配置回退 DB);v5 报告本 hotfix 按 owner、格式和当前 `document_hash` 做 DB 按需缓存, 首次渲染成功后后台写入,后续命中时不重复渲染。owner 受众为脱敏版本、admin 受众为完整诊断版本,普通 owner 只会命中脱敏缓存,严禁串读;缓存 miss 时从同一冻结 ReportDocument 按对应受众即时渲染,不会重算业务指标。 `question_changes` 冻结每一题的 previous/current/delta、两期回答证据 ID 与不可比原因;逐题展示提及率、 Top3、平均排名、情感和引用覆盖的“可比期间相关变化”。没有严格可比上期时保持 `comparable:false`, 不制造 delta,也不把变化表述为工单造成的因果结果。四种导出与在线 HTML 读取同一冻结 section。 **ReportDocument 2.4 决策叙事模块**(与参考 GEO 全方位报告对齐):执行摘要仪表板(含状态与 优先建议)· 客户旅程×提问矩阵(发现认知/比较考量/决策行动/信任验证/转化行动;分店/地址/到店类 问句归转化行动;**影响度**列口径:高影响=有提问但 0 提及且 0 引用,快速赢利=有引用无提及, 中影响=提及覆盖率 <70%,低影响=≥70%,无法评测=该阶段无追踪 prompt)· 旅程下「竞品×旅程」表(本品占位率 vs Top 竞品出现率 + 差距提问)· MIA GEO Visibility Index v5.2 兼容评分卡 (品牌回答出现率 35 / 来源权威度 25 / 品牌推荐位置 15 / 来源多样性 10 / 带显式来源回答占比 15; 推荐位置按明确推荐平均位次的倒数计算,来源多样性按不同域名数 / 归一化引用出现次数计算;任一必需证据缺失时不生成综合分,也不重新归一化隐藏缺口)与 90 天目标 · 竞品动态(按可比上期 **SoV ±3pp** 判定 rising/falling;表列「态势/建议」, 不再用提及次数驱动,也不暴露英文 rising)· 声量分布与定位机会 · 业务驱动因子/需求场景组地图 · 引用源分析 · 风险与机会四象限 · 90 天执行路线 · 已确认事实偏差 · 方法与口径。事实偏差章节只冻结报告事实版本一致、 且经人工 `confirmed` 的 contradiction/unsupported;candidate、旧事实版本和未确认结果不进入报告。 旅程由长尾 `intent` 或问句启发式映射。 **指标口径(销售可读)**:冻结跟踪集合声量份额 (SoV)=本品牌与**已配置竞品**在成功回答中被点名的构成份额(同答同品牌最多计 1,所有跟踪品牌合计 100%); 未配置但答案里出现的大牌进入 `observed_competitors`,**不稀释 SoV 分母**——对比大牌前先确认其在竞品列表,并同时看本品提及率(绝对覆盖)。 提及率=被点名回答÷全部成功回答(分母含未提任何品牌;同一回答可点名多个品牌,所以品牌间提及率可重叠且不要求相加为 100%);有排名的回答数=答案给出明确推荐位才计; 均位=有推荐位时的平均名次(越小越靠前;声量高≠均位靠前);出现≠推荐(字面「未提到 X」仍可计提及); AI 最信任来源 X%=该域名在引用清单中的次数占比。 需求场景组(cluster)是共享同一已保存 cluster 标签的一组问题,并非独立主题、市场或因果结论。 `drivers[].brand_mentions` 与 `drivers[].rival_matrix[].mentions` 均为同一需求场景组内成功回答的 实体提及次数;`driver` 优先取长尾问题已保存的 cluster,缺失时归入未分组需求场景。综合能见度是 0–100 加权诊断分(提及率 45% + 明确 Top3 率 30% + 正面命中/成功回答 25%),不是行业百分位, 内部行动区间为 0–34「优先修复」、35–59「建设中」、60–100「优势区」;当前不提供未经可靠 样本验证的行业均值。 报告信源权属:Owned=品牌官网域名/子域,Partner=已登记投放 URL 的域名,Third-party=其余真实 引用域名;来源还以目标受众市场标记 `official/local/regional/international/unknown`,通用 `.com/.org` 没有地理证据时必须保持 `unknown`,不得包装成海外。来源列表是回答显式引用,不是网页热度/流量排行, 也不能证明中国大陆用户直接访问或使用翻墙。唯一 URL 与唯一域名都按规范化值跨成功回答去重,且与引用次数分列。90 天目标是内部 规划阈值(当前值+最低抬升并受 floor/ceiling 限制),不是行业基准或结果承诺;其中自有引用份额 与评分卡、90 天路线共用 `current + 8pp`、floor 12%、ceiling 50% 的同一目标,不会固定写成 12%。 HTML/PDF 使用 demo 级版式(编号章节、水平条形图、双条评分卡、四象限、SVG 定位散点), 回答与证据中的 Markdown(标题/列表/加粗/引用)会安全渲染为可读排版。所有面向用户的 SPA、HTML、PDF、 PPTX、XLSX 和回答 CSV 会移除供应商 ``、``、`` 私有推理包装;数据库原始 `Response.answer` 不改写,供服务端审计和人工复核。 在线 HTML 在 01–10 之后保留**明细附录**(平台矩阵、完整提示词/回答、引用 URL、代表证据)。 PDF 的 `MIA_REPORT_PDF_ENGINE=auto|weasyprint|reportlab` 默认 `auto`:小报告用 WeasyPrint 打印同一 HTML,HTML UTF-8 字节数超过 `MIA_REPORT_WEASY_MAX_HTML_BYTES`(默认 3000000,范围 50000–5000000;HTML 不再内嵌语义清单正文,只声明其 hash) 时主动走 ReportLab;WeasyPrint 不可用也回退 ReportLab 的同源 01–10 + 附录结构(不再使用旧版摘要表)。 PPTX 为 11 页决策叙事(封面含报告日期、监测品牌、市场范围、行业、测试语言、AI 平台、Prompt 数量、 竞品品牌与监测周期;执行摘要+建议、旅程、评分卡、竞争定位、驱动因子、信源权属、 平台矩阵、风险矩阵、90 天路线、方法),以原生形状/进度条渲染,**并在方法页之后附明细证据页** (平台表现矩阵、逐题 Prompt 证据、引用 URL 明细、正/负面代表回答卡,内容与在线 HTML 附录对齐、按需分页)。 冻结快照 `report_context` 字段集中携带上述封面元数据(`generated_at`、`brand_name`、`keyword`、 `market`、兼容 `region`、`brand_region`、`measurement_mode`、`industry`、`test_language`、`platforms`、 `prompt_count`、`competitors`、`period`)。`competitors.metric_semantics` 明确提及率可重叠、SoV 合计 100%; `sources.evidence_type/notice/market/measurement_mode` 说明来源证据和旧口径限制。 全部格式支持中英双语。 判定输入变化会把报告置为 `stale`、 旋转报告输入代际并撤销旧生成 lease,旧生成器不能覆盖新代际。读取或下载版本过期的报告时**立即返回 已有的旧版冻结快照**(保证总能拿到文件),同时在后台异步收敛/重生成到当前生成器版本,下次读取即获新版; 绝不在读/下载请求内同步重生成(这是历史 504 的根因,已根治)。仅当报告完全无可用快照时返回 `generating`(`GET /api/reports/{id}/document` 返回 409),客户端应稍后重试。真实渲染失败会保留失败信息。 `POST /api/reports/{id}/retry` 由**报告所有者**(或 admin 角色)对 `failed` 报告重试生成;admin 观察模式 为全局只读,POST 被拒。该路由同时是**制品修复入口**:它先为报告的每个 ready 修订补齐缺失的 HTML/PDF/PPTX/XLSX 渲染任务、把终态失败的任务重新排队(重置尝试计数并错峰),再尝试重新生成文档。 制品修复只依赖冻结修订,不重算指标、不追加新修订、不改变导出计费引用,因此不会让客户为已拥有的 报告再次付费;即便文档因指标缺失无法重建,只要有制品被补建或重排,仍返回 200 并在响应 `artifacts:{revisions,created,requeued}` 中说明。读取制品的 `GET /api/reports/{id}/artifacts/{fmt}` 只按冻结修订绑定制品,优先返回 ready 且渲染器版本匹配的一份,旧渲染器版本的 ready 制品仍可读; 它永不创建任务。 `GET /api/keywords/{id}/export.pptx?monitoring_profile_id=` 作为旧 PPTX 地址的兼容别名,**仅下载所选 Profile 的既有报告** (`ready|stale` 均可;同 Topic 多 Profile 缺参返回 409, stale 走旧文档兜底 + 后台刷新);报告尚未生成/生成中/失败时返回 409 `{"error":"report_not_ready","message":"报告仍在生成中,请稍后到报告中心下载"}`,绝不在下载请求内同步重建; 趋势:`GET /api/keywords/{id}/trend?monitoring_profile_id=`;`report`、`sentiment-trend`、`prompt-trend` 也接受相同 Profile 参数并执行 active/owner/Topic 校验;管理员观察模式同样把 Profile 与测量模式传入只读聚合。legacy 每个点保留旧 `positive_rate`,并返回按成功回答分母还原的 `positive_presence_rate`(优先读 RunMetric 固化真值,历史快照缺列时回退 `mention_rate×positive_rate` 乘积)、`visibility_score` 与 `score_version:"4.0"`;每点还带样本量 `succeeded_answers`(旧别名 `succeeded`,表示该期成功回答数, 历史快照可为 `null`)。 legacy 趋势以最新 Run 为基准调用报告同源的 `report_signatures_comparable`;问题、模型、采样数或采集环境 任一变化的历史 Run 不进入曲线,并在 `excluded[]` 返回原因。趋势点保留兼容 `date`,新客户端使用统一 `axis_label=MM-DD` 画轴、`occurred_at` 在 tooltip 展示精确时间;每点含 `measurement_mode`,不同口径不得 直接环比。v5 至少两个 Run 的精确签名相同时返回 `trend_status=available` 并绘制描述性趋势;不足两个同签名点时 Dashboard 将 `trend` 置空。方向性显著变化仍须等待冻结的 paired-bootstrap delta revision,不得由点估计冒充。 决策看板 BFF:`GET /api/keywords/{id}/dashboard?mode=summary|full&platform=&trend=10&compare=&measurement_mode=&monitoring_profile_id=`; `measurement_mode` 支持 `controlled_benchmark|native_observation|market_context|legacy_unspecified`。 profile 的歧义、owner、keyword 与模式校验同 `/insight`;summary/full、latest/latest-any、趋势、情感和 compare 使用同一 profile,响应回显 `monitoring_profile_id/target_market/language/measurement_mode`。 `insight` 同步返回 `data_state/execution_breakdown/publication_eligibility/baseline/safe_reason/available_actions`; 页面只可渲染 `available_actions` 中的操作,sealed v5 即使存在失败槽也不得自行显示失败项补跑。 Dashboard 以同一 profile + measurement mode 下任意状态的最新非诊断 Run 作为新鲜度权威。若它不是终态 `done|partial`,响应不得回退展示较旧 publishable Run。非终态仍 fail-closed,只返回状态与证据入口。 终态 `done|partial` 且进度证明至少一个成功回答时,响应返回客户安全的完整本次记录 `insight.current_snapshot`:除 `available/attempted/completed/answered` 和兼容标量外,还包含冻结或持久化聚合产生的 `summary/by_model/share_of_voice/competitor_ranking/competitor_mentions/top_sources/sentiment/search_context/opportunities`。 这些字段只描述同一 Run 的实际结果,不授予趋势、增减、稳定基线或正式报告资格;缺失值保持 `null`,缺失模块由客户端省略, `detail` 仍为 `null`。趋势和跨期结论只在正式响应包含至少两个相同监测口径的真实历史点时展示。 没有已测量成功回答时仍返回 `insight.has_data:false`。 对达到发布门槛的 sealed v5 Run,Dashboard 在完整分析图表上叠加 Trust Receipt、Mia Visibility、Readiness 和 Data Confidence; 模型、竞品、情感、来源和问题图表读取 `customer_analytics_snapshot_v1.5`,并兼容历史 v1.0–v1.4;不回退 mutable v4 点估计。 publishable 响应的 `summary` 与每个 `by_model` 项都返回 `visibility_contributions:{score_version,components:[{key,rate,weight,points}]}`;三项输入不全时该字段为 `null`,并返回 `visibility_contributions_unavailable_reason:"visibility_components_unavailable"`。 publishable `insight.sentiment_distribution` 与 run 聚合使用同一后端投影,字段为 `{total,positive_rate,neutral_rate,negative_rate,unavailable_reason}`;Dashboard 只格式化这些比率,不按计数二次相除。 `by_model` 与 `competitor_ranking[].platforms[]` / `competitor_mentions[].platforms[]` 还返回模型内精确 `share_of_voice/share_of_voice_numerator/share_of_voice_denominator/share_of_voice_unavailable_reason`;分母只是该模型中 目标品牌 + 已配置竞品的冻结提及数。冻结分模型投影缺失时 reason 为 `per_model_tracked_set_projection_unavailable`,真实零分母时为 `tracked_set_has_no_mentions`;两者都返回 `share_of_voice:null`,不伪造 0。 publishable `insight`、`summary` 与可完整分类的 `by_model` 项返回后端求和的 `quality_gap_count=failed+excluded+missing`;任一分类缺失时返回 `null` + `quality_gap_count_unavailable_reason:"quality_gap_counts_unavailable"`。`insight.comparison` 返回 `{basis:{selector,run_id,occurred_at,visibility_score},delta_visibility_score,metric_deltas,unavailable_reason}`,其中 `compare=1|2|-1` 分别表示上一期、上上期、最早一期;真实持平为 `0`,基线不足、分数缺失、方法不可比或不支持的 selector 均为 `null` 并带对应 reason,客户端不得自行相减。`metric_deltas` 固定包含 `tracked_set_share_of_voice/mention_rate/avg_rank/recommendation_rate/mention_rank/succeeded_answers`; 每项为 `{delta,unit,unavailable_reason}`,比例差使用百分点,排名差使用名次,回答差使用计数;历史投影不具备 对应权威值时该项 `delta:null`,不能把缺失值当作持平。 未通过发布门槛的指标只显示 `exploratory|insufficient` 及原因,回答和实际来源组成可从 Run/Receipt 下钻。 浏览器“竞品与来源”页可在门槛前展示同一终态 Run 的目标市场当前测量事实(已冻结竞品提及、实际引用来源和已有情感记录), 但不得把它们提升为行动结论:只有 `data_trust.official_reporting_eligible:true` 时才挂载待争取问题、竞品占优来源、阵地行动和内容机会模块。 未达门槛时这些模块直接省略,不向客户展示内部资格原因或修复提示。 `summary` 返回最近完成 Run 的决策指标、模型、竞品、错误类别和趋势;`full` 只增加既有来源、情感、 问题归属和机会分析,不发起模型、计费或写库;`full` 的来源与情感明细跟随 `insight` 使用同一个 run (measurement_mode 切换旧 run 时不混入最新 run 的数据)。旧 `/insight` 保留。 `insight` 增加 `decision_summary/measurement_mode/comparison_compatible`,趋势只读取相同测量口径。 `decision_summary.state ∈ strong|growing|weak` 按综合能见度分分档:≥60 `strong`、≥35 `growing`、 其余 `weak`,与报告行动区间「0–34 优先修复 / 35–59 建设中 / 60–100 优势区」同源。趋势窗口被 `trend` 参数截断时 `insight.trend_earliest` 携带全历史真实最早一期的趋势点(未截断时为 `null`, 「较最早一期」应回退 `trend[0]`)。 共享 Tavily 联网材料与模型实际引用分字段呈现,不能互相冒充;provider request ID 与错误样本只对真实 Admin 观察者返回安全诊断。 文章改写:`POST /api/keywords/{id}/content-rewrites` 请求中 `source_text`(最多 12,000 字)与 `source_draft_id` 必须二选一;必填 `client_request_id`(8–64 位 URL-safe 稳定幂等键);可选 `goals:[clarity|geo_extractability|fact_safety|citation_readiness]`、 `preserve_tone`、`additional_instructions`(最多 500 字)、`platform`、`skill_id`。返回 202 `{generation_id,status:"queued",generation_kind:"rewrite",input_geo_score}`,之后复用 `GET /api/content-generations/{id}` 轮询。改写由 AutoSocial 按显式 Account 计费,固定 4,000 字符 prompt 预算按 “不得编造/安全→GEO 规则→用户目标→原文→精选品牌事实”保序裁剪;原草稿永不覆盖,保存新版本必须 再调用既有草稿创建接口。改写标题或首段自然锚定当前主词;当原文已点名当前品牌时,成稿至少自然 保留一次规范品牌名,但不得借此添加原文或已确认品牌事实之外的新主张。创建请求的校验错误均为 结构化 `detail:{error,message}`(`message` 为中文人话): 503 `content_rewrite_unavailable`、400 `invalid_platform`、404 `draft_not_found`、403 `draft_forbidden`、 400 `rewrite_source_required`、422 `rewrite_source_too_long`。内容评分的 `rewrite_handoff` 只用于带入原文和 修改目标;切换到改写页不自动提交或产生第二次 MIA Points 请求。 完整交付包:`POST /api/reports/{report_id}/delivery-bundles` 请求 `{lang?:"cn"|"en",audience?:"owner"|"admin"}`,可靠入队后返回 202;普通身份只能创建 owner 受众, admin 受众要求真实 admin + `X-MIA-Admin-Mode: 1`。legacy 创建时冻结 ReportDocument、行动/验收、部署资产、 阵地地图、回答证据和归因资产;行动/验收和阵地严格使用报告 owner,而不是代为导出的 admin 身份。 v5 bundle 只使用绑定的 ReportRevision 与其中的冻结证据;尚未进入 revision 的行动/阵地/部署/归因资产标为 `not_frozen_in_report_revision`,不读取当前配置补齐。 相同 `report_id/lang/audience/snapshot_digest/generation=1` 幂等复用。用 `GET /api/reports/{report_id}/delivery-bundles` 列表、`GET /api/delivery-bundles/{bundle_id}` 轮询 `queued|running|retry_wait|billing_pending|succeeded|failed|cancelled`,取消请求为 `POST /api/delivery-bundles/{bundle_id}/cancel {expected_version}`,缺失 428、CAS 冲突 409;携带当前版本对 已终态任务重复取消幂等返回原对象。`failed|cancelled` 可调用 `POST /api/delivery-bundles/{bundle_id}/retry {expected_version}` 创建新 `generation`;旧终态记录保持不变, 新任务使用新的 id 与计费 external ref,缺失版本返回 428,旧版本、非可重试状态或已被后代取代返回 409。 余额不足会直接终止为 `failed/insufficient_credit`;其他预留错误默认最多尝试 3 次(配置范围 1–10), 耗尽后终止失败。成功后从 `GET /api/delivery-bundles/{bundle_id}/download` 下载 ZIP。任务具备 lease、heartbeat、有限重试、取消、 重启恢复和稳定终态事件;对象存储优先使用 `DELIVERY_S3_BUCKET`(兼容旧 `DELIVERY_BUNDLE_S3_BUCKET`)、`DELIVERY_S3_REGION`、`DELIVERY_S3_KMS_KEY_ID`,未配时分别回退 `REPORT_S3_BUCKET/REPORT_S3_REGION/REPORT_S3_KMS_KEY_ID`;对象前缀为 `DELIVERY_BUNDLE_S3_PREFIX`。 `APP_ENV=prod|production|staging|sit` 必须存在 S3 bucket 且上传/下载失败时 fail-closed;只有本地/测试可 DB 回退。 ZIP 固定含 manifest、HTML/PDF/PPTX/XLSX、行动与验收 CSV、部署资产及 DEPLOY.md、回答/来源/逐题 变化 CSV、阵地地图和 AI 归因资产;文件路径白名单净化,CSV 防公式注入,每个文件记录大小与 SHA-256, 缺失资产在 manifest 说明原因。报告四格式必须复用同 revision 的 ready `ReportArtifact`,不在交付包 worker 内重复渲染;缺失时进入 `report_artifact_preparing` 重试。取消请求立即回显 `cancel_pending`,每个制品读取之间 检查取消,release 完成后 reservation state 为 `released`,终态不可保留 `held`。相同冻结输入可确定性重放。整个 ZIP 仅按一次现有 `report_export` 动态价格 预留/结算,不按内部文件重复扣费;失败或取消释放。owner 包移除 provider 管理诊断,admin 包不会被普通 owner 读取。本功能只生成交付文件,不发布到外部渠道。 报告 ready 后除 HTML/PDF/PPTX/XLSX 外,会把 owner/admin 双语冻结正文以 canonical JSON 双写到现有 `ReportArtifact`。生产 JSON 快照只允许写 S3,上传失败不会伪装成已迁移,旧 `document_cn/document_en` 在至少两个稳定版本内继续保留用于回滚;回填命令逐份读取后校验 SHA-256。所有预生成对象使用 generation/audience-scoped S3 key;失效 generation 和迁移降级留下的对象不会被 当前缓存查询命中。Alembic 不执行外部对象删除,运维只可清理数据库已无 `report_artifact.s3_key` 引用且 超过 14 天观察期的报告缓存;不得把材料原件或客户交付 ZIP 纳入同一清理规则,也不得对共享 reports 前缀 使用无条件递归删除。 AI 流量归因资产:`GET /api/brands/{brand_id}/attribution-assets` 返回版本化 referrer catalog 的清单、 文件名、媒体类型、大小和 SHA-256;`GET /api/brands/{brand_id}/attribution-assets/{asset_key}` 下载单个 固定白名单资产。资产包括 GA4 自定义渠道组 regex/操作说明、`ai-referrers.csv/json`、仅在客户本地读取日志 且不联网的 Nginx/Apache 分析脚本,以及 UTM/验证指南。它不会读取或上传客户日志,不会修改 GA4、服务器、 网站或外部渠道;direct/unknown、应用内浏览器和隐私代理可能缺失 referrer。归因结果不得混入 Visibility Score。接口免费、确定性生成、owner 隔离,响应 `private,no-store`。 - 额度/用量:`GET /api/credit/balance` 只查询当前请求经 MIA Platform 验证的 Account,绝不从 GEO 本地数据范围、邮箱或历史映射选择钱包;计费未启用时返回 `{enabled:false}`,生产计费已启用但 Platform 快照不可达或缺少 Points 字段时返回 503,不伪装成“未接入计费”。成功响应直接转交 `as_of/version/balance_points/held_points/available_points` 并附 `manage_url`;GEO 不重建或缓存余额, 响应使用 `private,no-store`。累计用量使用 `GET /api/usage/summary`;预估使用 `POST /api/estimate`。 - 用量账本:检索、技术稽核、报告生成/导出、Skill 与 AutoSocial 成稿写入 append-only `UsageEvent`; 客户合同只返回消费、预留或可用的 MIA Points;历史补录标记 `estimated`,未知 token / 创建者保持未知而不猜测。 - 管理后台:`/api/admin/*` 同时要求产品 admin/superadmin 与 `X-MIA-Admin-Mode: 1`,否则 403。 包括今日/周/月用量、成功率、活跃用户,按用户/账号/操作/模型/provider/品牌/主词/任务/状态/日期筛选与下钻, 以及活跃/卡住任务、模型错误、调度/报告/outbox 失败监控;内部核算字段不属于公开 Agent 合同。 管理员在普通模式下仍严格限定本人 owner 数据;只有显式 admin 模式 + `scope=all` 才能在列表中查看全平台。 显式 Admin Mode 对客户业务数据是**观察模式且只读**:项目、Run、内容和报告等客户资源的 POST/PUT/PATCH/DELETE 返回 403 `admin_read_only`。Admin Console 仅显式放行用户 role/status、 GEO 本地操作定价和既有维护端点;每个端点仍独立校验管理员权限并记录审计。role 进出 admin、 停用/恢复本地 admin 会逐请求复验配置型 superadmin,且拒绝 PAT/Platform Key/service credential; 普通 admin 的 status 治理仅限 owner/user。报告下载等有副作用的 GET 只允许命中已生成缓存; 缓存未命中同样返回 `admin_read_only`,不得创建任务、提交外部调用、扣费或写数据库。普通用户伪造该头 不会提权,也不会改变其原有本地 Account/owner 边界。 Run erasure POST 不是 Observer 例外;跨账号操作改用非 Observer 请求中的 `X-MIA-Admin-Operation: run_erasure` 精确匹配端点,不能与 `X-MIA-Admin-Mode` 混用。 该操作头不开放 `/api/admin/*`、不授予普通用户任何权限,数据负责人对本人项目数据的普通模式能力不变。 停用/恢复本地 `role=admin` 同样会改变有效 admin 能力,因此也只允许当前配置型 superadmin; 普通 admin 仍可停用/恢复 owner/user。配置型 superadmin 永远不能被本地 role/status 入口修改。 资源列表/详情在对应数据可确定时返回 `account_id`、`owner_user_id`、`owner_email`、`owner_name`; 无法可靠推断的历史归属保持空值并显示「历史数据」,个人模式不展示,且不会错误归给账号首位管理员。 常用 Admin 运营端点:`GET /api/admin/overview`、`GET /api/admin/usage`、 `GET /api/admin/usage/details`、`GET /api/admin/usage/export.xlsx`、`GET /api/admin/monitoring`; `POST /api/admin/reports/backfill`、`POST /api/admin/reports/{id}/retry`、`POST /api/admin/usage/backfill` 用于受控的历史补齐/失败重试。 用量汇总的消费可信度统一由 `billing_status=verified|unknown` 表达。非 Run 事件只有非估算 UsageEvent 才可参与 verified 消费;每个 Run 则必须按完整、未过滤的 Run 账本额外满足全部条件: `billing_finalization_points` 非空且等于全部事件的 `consumed_mp` 合计;全部事件非估算、消费分配非负; 每条 detail 都有相同的正整数 `expected_usage_event_count`,且它等于完整事件数;durable finalization 只能是 `action="settle" + state="settled|replay" + points>0`、 `action="release" + state="released" + points=0`,或 `action=null + state="not_required" + points=0`。过滤后的 Admin 查询仍以完整 Run 账本判定,不能只看 筛选命中的子集。旧 marker 缺失、部分写入、金额不一致、estimated、`pending/usage_pending`、 `forfeited`、`already_settled` 或非零 `not_required` 一律 `unknown`,此时 `consumed_mp` 与 `gross_margin_rate` 为 `null`;完整账本下的空范围是 `verified` 且消费为 `0`。Admin GET 的历史自愈 只补 terminal legacy Run,不补 active Run 或 frozen v5/pending Run,也不推进 live finalization;真实 accounting 写入会为同一 Run 的每个事件写入完整数量 marker,部分崩溃重试补齐所有缺行并核对消费/内部 成本合计;推进前还必须在 Run 锁内让两项合计分别精确等于持久化的 finalization 消费与内部成本权威值, 且 `confirmed:*` marker 与 action 合法对应。Tail 为避免永久挂起可收敛 `settle→settled|replay|forfeited`、 `release→released|already_settled`、`action=null→not_required`,但这不扩大前述 Admin `verified` 集合。 全部条件通过后才可从 `usage_pending` 推进。任一 page evidence、outbox 或 ledger 尾部失败都会保留 confirmed outcome,递增 attempt 并设置有上限的指数退避;maintenance 只重试已到期 Run,因此一个永久 冲突不会阻塞后续 accounting tail。概览 KPI 另按 today/week/month 返回同口径的 `*_billing_status` 与 `*_consumed_mp`;趋势、操作、模型、用户聚合和 `GET /api/admin/users` 的每行也带 `billing_status/consumed_mp`,不提供含义混淆的历史消费别名, 也绝不以旧 Run 核算值回填。 `GET /api/admin/usage/details` 的 `status` 是执行状态,消费可信度必须读取独立的 `billing_status`;CSV/XLSX 导出在消费未知时保持空值,不把管理员核算列复制到消费列。 `GET /api/admin/users/{user_id}/billing?page=&page_size=` 返回 `{user,total,page,page_size,rows[]}`,每行包含 `run_id/created_at/type/models/run_status/status/consumed_mp/brand`。 `status="verified"` 时 `consumed_mp` 来自满足上述完整账本与 durable finalization 证明条件的 Run UsageEvent 消费账; 历史 Run 没有权威账本(包括只有 estimated 补录)时 `status="unknown"`、`consumed_mp:null`,绝不把旧 Run 的内部成本复制成客户消费。真实 admin + 显式 Admin Mode 的管理诊断面可另带内部成本核算, 但它不属于公开 Agent 字段合同且普通身份不可见;消费字段只使用上述 `status/consumed_mp`, 公开合同也不包含法币、币种或 provider 价格。 为使该持久化证明成立,平台 settlement 的窄协议栅栏要求 `outcome=settled|replay` 时返回的 `settledPoints` 必须与本次请求的正整数 `actualPoints` 精确相等;不相等按 `unconfirmed` 处理,Run 保持 `pending` 并按同一幂等身份重试,不写 confirmed marker 或 UsageEvent。该栅栏不改变其他计费合同。 ### Agent 可调用路由清单 以下清单与 `backend/app/main.py` 实际注册的全部业务路由模块(含 `admin_api.py`)的真实装饰器保持一一对应;路径参数名称按代码写法列出。 除 Skill API 与不读取客户数据的 readiness probe 外,都使用上面的 `current_user` 身份轨与个人 owner 隔离。 ```text # 身份、模型、额度 GET /api/ready GET /api/auth/me GET /api/observability/browser-config POST /api/observability/test-error POST /api/product-analytics/collect GET /api/models GET /api/credit/balance GET /api/usage/summary POST /api/estimate GET /api/markets GET /api/monitoring-profiles POST /api/monitoring-profiles PATCH /api/monitoring-profiles/{profile_id} `/api/observability/browser-config` 与 `/api/product-analytics/collect` 都要求 `current_user`;后者只接受浏览器同源、显式目录事件。`/api/observability/test-error` 还要求 `current_admin`,且仅在 staging 与受控验证开关同时启用时可用。 # 项目、品牌、主词、问题、竞品 GET /api/projects POST /api/projects PATCH /api/projects/{project_id} DELETE /api/projects/{project_id} GET /api/projects/{project_id}/overview GET /api/brands POST /api/brands GET /api/brands/{brand_id} PATCH /api/brands/{brand_id} GET /api/brands/{brand_id}/facts PATCH /api/brands/{brand_id}/facts GET /api/brands/{brand_id}/deploy-assets GET /api/brands/{brand_id}/attribution-assets GET /api/brands/{brand_id}/attribution-assets/{asset_key} GET /api/brands/{brand_id}/materials POST /api/brands/{brand_id}/materials POST /api/brands/{brand_id}/materials/upload GET /api/brands/{brand_id}/material-fact-candidates GET /api/materials/{material_id} DELETE /api/materials/{material_id} POST /api/materials/{material_id}/restore POST /api/materials/{material_id}/extract GET /api/material-extraction-jobs/{job_id} POST /api/material-extraction-jobs/{job_id}/cancel GET /api/materials/{material_id}/fact-candidates POST /api/material-fact-candidates/{candidate_id}/review DELETE /api/brands/{brand_id} GET /api/brands/{brand_id}/keywords POST /api/brands/{brand_id}/keywords GET /api/keywords/{keyword_id} PATCH /api/keywords/{keyword_id} DELETE /api/keywords/{keyword_id} POST /api/keywords/{keyword_id}/longtails PATCH /api/longtails/{longtail_id} DELETE /api/longtails/{longtail_id} PUT /api/keywords/{keyword_id}/competitors POST /api/keywords/{keyword_id}/competitors POST /api/keywords/{keyword_id}/competitor-merges/{audit_id}/undo GET /api/reconciliation-tasks/{task_id} POST /api/reconciliation-tasks/{task_id}/retry POST /api/entity-suggestions/{suggestion_id}/accept POST /api/entity-suggestions/{suggestion_id}/revert GET /api/entity-resolution-candidates POST /api/entity-resolution-candidates/{candidate_id}/review POST /api/entity-resolution-candidates/bulk-review # 检索、生成与分析 POST /api/keywords/{keyword_id}/run-preflight POST /api/keywords/{keyword_id}/runs GET /api/runs GET /api/runs/{run_id} -> top-level frozen navigation scope: project_id/project_name/brand_id/brand_name/keyword_id/keyword_text/ monitoring_profile_id/monitoring_profile_version/target_market/language/measurement_mode/task_id A client opening only this run_id must replace any stale page scope with these values before rendering or following links. GET /api/runs/{run_id}/status -> 保留原有 attempted/completed/succeeded/failed/excluded/missing/by_lane 字段,并增加兼容性 progress; progress 的状态、阶段、300 秒停滞语义及安全边界与批次接口一致,但不含 markets[]。 顶层同步返回 data_state/execution_breakdown/publication_eligibility/baseline/safe_reason/available_actions; `progress.safe_message` 是与稳定 `safe_reason` 对应的客户文案;v5 `available_actions` 不含 `retry_failed_items`,legacy 仅在后端明确返回该动作时才可展示失败项补跑。 响应不会返回 Prompt、回答正文、内部异常、供应商价格或任何内部法币字段。 GET /api/runs/{run_id}/entity-suggestions -> 返回该 Run 从 `other_brand_mentions` 生成的竞品别名建议及其字面证据计数;建议范围受冻结竞品目录约束。 只有置信度至少 0.9 且来自至少 2 个不同回答的建议才可自动接受,其余必须人工接受。 接受与撤销都会追加审计记录并进入确定性判定重算,不修改冻结 Manifest 或原始回答。 POST /api/runs/{run_id}/entity-suggestions/materialize GET /api/runs/{run_id}/retrieval-rechecks POST /api/runs/{run_id}/retrieval-rechecks GET /api/runs/{run_id}/explainability GET /api/runs/{run_id}/action-plan-status GET /api/runs/{run_id}/manifest GET /api/runs/{run_id}/metrics GET /api/runs/{run_id}/metrics/{metric_key}/receipt GET /api/runs/{run_id}/responses/{response_id}/evidence POST /api/runs/{run_id}/recomputations POST /api/runs/{run_id}/erasure-jobs GET /api/erasure-jobs/{job_id} DELETE /api/runs/{run_id} GET /api/runs/{run_id}/answers.csv GET /api/runs/{run_id}/suggestions GET /api/runs/{run_id}/opportunities PUT /api/runs/{run_id}/opportunity-dismissals/{opportunity_key} GET /api/runs/{run_id}/brand-tags GET /api/runs/{run_id}/content-briefs GET /api/runs/{run_id}/report POST /api/runs/{run_id}/retry-failures GET /api/runs/{run_id}/recovery-status POST /api/runs/{run_id}/recover-missing-slots GET /api/runs/{run_id}/competitive-source-gaps GET /api/runs/{run_id}/competitive-source-gaps/evidence POST /api/runs/{run_id}/competitive-source-gaps/action-item GET /api/runs/{run_id}/question-ownership POST /api/runs/{run_id}/question-ownership/{longtail_id}/content-brief POST /api/runs/{run_id}/question-ownership/{longtail_id}/action-item POST /api/runs/{run_id}/diagnostics/estimate POST /api/runs/{run_id}/diagnostics GET /api/measurement-batches/{batch_id} POST /api/measurement-batches/estimate POST /api/measurement-batches POST /api/measurement-batches/resolve POST /api/query # dev/test allowlist + GEO_ENABLE_DIAGNOSTIC_QUERY + admin mode; anything else returns 404 POST /api/generate/competitors POST /api/generate/estimate POST /api/generate/longtails POST /api/generate/topics POST /api/generate/briefs POST /api/generate/quickwins POST /api/measurement-batches/prompt-readiness GET /api/model-market-capabilities GET /api/action-plans/{plan_group_id} POST /api/action-plans/{plan_group_id}/refresh GET /api/keywords/{keyword_id}/insight GET /api/keywords/{keyword_id}/dashboard GET /api/keywords/{keyword_id}/market-scopes GET /api/keywords/{keyword_id}/geo-overview GET /api/keywords/{keyword_id}/citation-likelihood GET /api/keywords/{keyword_id}/sources GET /api/keywords/{keyword_id}/sources.csv GET /api/keywords/{keyword_id}/trend GET /api/keywords/{keyword_id}/sentiment-trend GET /api/keywords/{keyword_id}/prompt-trend GET /api/snapshots GET /api/snapshots.csv POST /api/audit POST /api/brands/{brand_id}/site-audits GET /api/brands/{brand_id}/site-audits GET /api/site-audits/{audit_id} POST /api/site-audits/{audit_id}/cancel POST /api/site-audits/{audit_id}/signals/{signal_key}/action-item # 事实质量、人工采样与补全词动量 POST /api/fact-checks POST /api/runs/{run_id}/fact-check POST /api/drafts/{draft_id}/fact-check POST /api/content-generations/{generation_id}/fact-check GET /api/fact-checks/{job_id} GET /api/fact-checks/{job_id}/findings POST /api/fact-checks/{job_id}/cancel GET /api/brands/{brand_id}/fact-quality GET /api/brands/{brand_id}/fact-quality/summary GET /api/brands/{brand_id}/fact-findings PATCH /api/fact-findings/{finding_id} GET /api/drafts/{draft_id}/completion-gate POST /api/manual-sample-batches GET /api/manual-sample-batches GET /api/manual-sample-batches/{batch_id} GET /api/manual-sample-batches/{batch_id}/export POST /api/manual-sample-batches/{batch_id}/import PUT /api/manual-sample-batches/{batch_id}/items/{item_id}/response POST /api/manual-sample-batches/{batch_id}/finalize POST /api/manual-sample-batches/{batch_id}/cancel POST /api/responses/{response_id}/review POST /api/keywords/{keyword_id}/suggestion-snapshots GET /api/keywords/{keyword_id}/suggestions GET /api/keywords/{keyword_id}/suggestion-trend GET /api/suggestion-snapshots/{snapshot_id} POST /api/suggestion-snapshots/{snapshot_id}/cancel POST /api/suggestion-candidates/{candidate_id}/accept POST /api/keywords/{keyword_id}/content-assessments POST /api/content-assessments/{assessment_id}/deep-score GET /api/content-assessments/{assessment_id} POST /api/keywords/{keyword_id}/content-rewrites # 可编辑阵地地图 GET /api/keywords/{keyword_id}/territories POST /api/keywords/{keyword_id}/territories/refresh POST /api/keywords/{keyword_id}/territories PATCH /api/territories/{territory_id} DELETE /api/territories/{territory_id} POST /api/territories/{territory_id}/restore POST /api/territories/{territory_id}/action-item # 行动工单与验收 GET /api/action-items POST /api/action-items GET /api/action-items/{item_id} PATCH /api/action-items/{item_id} DELETE /api/action-items/{item_id} POST /api/action-items/{item_id}/transitions GET /api/action-items/{item_id}/events GET /api/action-items/{item_id}/criteria GET /api/action-items/{item_id}/comparable-runs POST /api/action-items/{item_id}/remeasure/estimate POST /api/action-items/{item_id}/remeasure-runs POST /api/action-items/{item_id}/criteria PATCH /api/action-items/{item_id}/criteria/{criterion_id} DELETE /api/action-items/{item_id}/criteria/{criterion_id} POST /api/action-items/{item_id}/verify GET /api/action-items/{item_id}/checks POST /api/action-items/{item_id}/checks/{execution_id}/cancel # 监测任务与投放 GET /api/tasks POST /api/tasks GET /api/tasks/{task_id} PATCH /api/tasks/{task_id} DELETE /api/tasks/{task_id} POST /api/tasks/{task_id}/control # enable|pause|stop|archive|run_now GET /api/tasks/{task_id}/runs GET /api/tasks/{task_id}/occurrences GET /api/keywords/{keyword_id}/placements POST /api/keywords/{keyword_id}/placements PATCH /api/placements/{placement_id} DELETE /api/placements/{placement_id} # 内容与 AutoSocial GET /api/autosocial/status GET /api/autosocial/skills -> {"skills":[{"id","ref","name","description","capability":"rewrite"}]} Only AutoSocial content-generation writer skills are returned; each `id`/`ref` is valid as `skill_id`. Dispatch/compliance `platform-style-*@version` refs are not returned and must not be sent as generation `skill_id`. GET /api/autosocial/platforms POST /api/keywords/{keyword_id}/generate-article Async: returns 202 with the queued generation record immediately; a durable worker drives AutoSocial, so poll GET /api/content-generations/{id} until `lifecycle_status ∈ ready|failed`. Active states are `queued|submitting|running|retry_wait|upstream_generating|verifying`; coarse `status=succeeded` can coexist with `verifying` and is not a terminal signal. Generation usually takes minutes but has no fixed local completion deadline; automatic retries and 5/30/120-second backoff can extend total elapsed time. Admission persists encrypted, non-secret Platform User + Account attribution only. A worker restores that identity and validates the unchanged local data-scope/owner/Keyword/Brand relationships before loading business inputs. It never stores, renews, reconstructs, or infers User authorization from the identity snapshot, email, Tenant, slug, history, or local data. AutoSocial receives GEO's own Service credential plus the immutable Account and optional initiating-User audit attribution; that audit field never grants User authority. If the identity snapshot cannot be persisted, creation returns 503 `account_authority_unavailable` and no generation job or provider request is created. Generation uses AutoSocial `web_search:auto`. Automatic retries on one row reuse the same JSON `idempotency_key`, so they do not create or charge a duplicate provider batch. A stable `client_request_id` (8-64 URL-safe characters) is required for one user create action. Replaying the same ID and payload returns the same durable row; changing the payload returns 409 `content_generation_idempotency_conflict`. New generations and rewrites accept publishable platforms only. Xiaohongshu aliases remain recognizable for historical display but are rejected for all new or retried writer work; the server default is `zhihu`. GET /api/keywords/{keyword_id}/content-generations GET /api/content-generations/{generation_id} POST /api/content-generations/{generation_id}/retry -> 202 durable successor. Response includes parent_generation_id, root_generation_id and retry_depth. Repeating retry for the same parent returns the same successor/provider idempotency key; retrying an active generation returns 409 content_generation_still_active. POST /api/geo/score-content GET /api/content/templates GET /api/keywords/{keyword_id}/drafts POST /api/keywords/{keyword_id}/drafts PATCH /api/drafts/{draft_id} DELETE /api/drafts/{draft_id} # 版本化报告与导出 GET /api/keywords/{keyword_id}/report GET /api/keywords/{keyword_id}/reports GET /api/brands/{brand_id}/reports POST /api/keywords/{keyword_id}/reports GET /api/keywords/{keyword_id}/export.pptx GET /api/reports/{report_id}/document GET /api/reports/{report_id}/artifacts/{fmt} GET /api/reports/{report_id}/artifacts/html/preview -> Same-origin, authenticated, checksum-verified HTML preview with a strict Content-Security-Policy. This endpoint never returns a storage URL. GET /api/reports/{report_id}/view.html GET /api/reports/{report_id}/download.pptx GET /api/reports/{report_id}/download.pdf GET /api/reports/{report_id}/download.xlsx POST /api/reports/{report_id}/retry DELETE /api/reports/{report_id} GET /api/reports/{report_id}/delivery-bundles POST /api/reports/{report_id}/delivery-bundles GET /api/delivery-bundles/{bundle_id} POST /api/delivery-bundles/{bundle_id}/cancel POST /api/delivery-bundles/{bundle_id}/retry GET /api/delivery-bundles/{bundle_id}/download # Skill 候选规划与 legacy 测量门禁 POST /api/skill/visibility POST /api/skill/visibility/async POST /api/skill/plan POST /api/skill/run POST /api/skill/run/async GET /api/skill/jobs/{job_id} # Admin:需产品 admin/superadmin + X-MIA-Admin-Mode: 1;admin 角色授予/撤销另需配置型 superadmin GET /api/admin/overview GET /api/admin/usage GET /api/admin/usage/details GET /api/admin/usage/export.csv GET /api/admin/usage/export.xlsx GET /api/admin/users PUT /api/admin/users/{user_id}/role PUT /api/admin/users/{user_id}/status GET /api/admin/users/{user_id}/billing GET /api/admin/runs GET /api/admin/pricing POST /api/admin/pricing/{operation} POST /api/admin/pricing/{operation}/calibrate DELETE /api/admin/pricing/{operation} GET /api/admin/audit GET /api/admin/content GET /api/admin/monitoring POST /api/admin/reports/backfill POST /api/admin/reports/{report_id}/retry POST /api/admin/usage/backfill POST /api/admin/data/repair-trust ``` ### 常用请求模型与边界 - `BrandIn`:`name` 与正整数 `project_id` 必填;可选 `aliases[]`、`landing_url`、`region`(默认中国大陆;带 `project_id` 且省略或传空白时仍按中国大陆兼容)、`language`(默认简体中文)、 `industry`、`primary_platform`、`site_mode ∈ website|materials|hybrid`、`keywords[]`。其中 `aliases` 是同一品牌实体的 显式名称变体,不得放产品、业务线、子品牌或地区分支; `site_mode=materials` 表示使用品牌材料而非自有官网作为事实入口;`KeywordIn` 含 `text`、 `competitors[]`、`longtails[]`、`market`(2–16 字符,默认 `CN`)、`language`(2–16 字符,按市场 Registry, CN 新建默认 `zh-Hans-CN`,输入 `zh-CN` 仅作历史兼容并在新建时规范化),以及 `question_selection_mode ∈ merge|replace`(默认 `merge`)。`replace` 只用于提交用户最终审阅的完整问题集: 复用同品牌、市场和语言的 Topic 时,它按 NFKC/casefold 身份更新保留问题的 intent/scope 等字段、归档取消选择的问题、 恢复重新选择的历史问题,并在付费监测周期冻结的问题将被编辑或取消时返回 409 `prepaid_config_change_pending`;`merge` 保留兼容的追加语义。长尾含 `text/source/intent/cluster/demand_signal`。`BrandUpdate.project_id` 省略表示不移动品牌;一旦提供也必须为正整数, 历史未分组品牌可用它单向迁移进项目。品牌同名冲突仅限同一 owner + project;同名品牌跨项目合法, 冲突响应为 409 且说明具体项目范围。Topic 复用按 NFKC/casefold + 市场 + 语言身份,并在 active Brand 行锁内串行化; 历史重复身份或不一致市场返回 409,不会合并、归档或改写历史行。复用响应包含 `normalized_topic`、 `question_selection` 的 created/updated/restored/archived ID 回执。`replace` 同时把竞品最终选择作为权威全集,问题与竞品在同一事务内通过付费周期门禁并落库;竞品变化时同事务创建判断重算任务。响应为 `competitor_selection.disposition=applied`、`authoritative=true`、`method/path=null`,并在需要时返回 `reconciliation`,调用方不得再发送第二次竞品 PUT。兼容 `merge` 不替换竞品,响应为 `disposition=unchanged`。 写入模型在 API 层按数据库容量拒绝超长输入:品牌/Topic/实体名 200、URL 500、问题 400、cluster 80;aliases 最多 20、品牌 Topics 最多 50、Topic 竞品最多 50、问题最多 200,超限返回 422 且不截断。 `KeywordUpdate` 同样允许更新 `market/language`,但 `text` 仍须为单一主词;已绑定 Monitoring Profile 或历史 Run 后 不可原地改写 Topic 文本、市场或语言,需新建 Topic/Profile。 - `RunIn`:`models?: string[]`、`type ∈ baseline|post|scheduled|manual`(默认 `baseline`;`post` 为投放后 回测,`scheduled/manual` 通常由监测任务与人工验证内部创建)、`web_search=true`、 `monitoring_profile_id?:positive int`、 `measurement_mode="controlled_benchmark"`(默认值;旧 `market_context` 仅兼容映射)。普通客户或 Agent 传 `native_observation` 返回 422 `native_observation_read_only`。`baseline|post|scheduled` 共用 `selected-question-panel-v4` 正式准入;分类只用于分析分布、不设最低数量,不合格时在 Profile、Run、Points hold 前返回 422 `measurement_admission_not_ready` 与 `readiness`。`manual` 是探索性证据,不进入正式基线。创建后立即返回 running,必须轮询。 - Customer first measurement is always one `cycle=once` Run. Project, Brand, Topic, Monitoring Profile creation and every estimate are non-billable and create no hold. Confirmed Run admission creates exactly one Account-owned, idempotent reserve intent; the same Run reaches exactly one idempotent settle-or-release terminal. Retrying an uncertain response reuses the durable Run/external reference and never creates a second hold. - 长尾 `measurement_scope` 默认 `benchmark`;`brand_probe` 和 `quickwin` 仅做探索,不进入问题攻防 KPI。 `PATCH /api/longtails/{id}` 可修改或清空 `intent/cluster`;`intent` 持久化为 `recommendation|comparison|discovery|scenario|purchase_decision`,并兼容常见中文别名;未提供或无法推断分类时使用 `recommendation`。`cluster` 最长 80 字符。`GET /api/runs/{id}` 的每项结果返回当前 `intent/cluster`,供客户端按模型命中率和业务驱动因子筛选;修改分类只影响分析归组,不改写历史回答正文。 - FactCheck 创建的 `target_id` 必须为正整数;semantic 必须显式授权,`force=true` 才创建同输入的新 epoch。 Finding 审核、fact-check 取消、人工批次 finalize/cancel、人工回答 review、拓词快照取消和阵地编辑均使用 响应中对应的 version/status_version 做 CAS;缺失前置版本通常为 428 或 422,冲突统一为 409。 - 人工样本导入引用 URL 只保存、不在导入请求内抓取;单回答最多 200,000 字符。导出前置模板中的 `sample_id/engine/question` 是冻结身份,导入时必须原样匹配。 - `MonitorTaskIn` requires `brand_id` and at least one `keyword_id` or `keyword_ids[]`; `keyword_ids` 最多 20 个, `questions_per_model` 为 1–20;支持 `models`、逐 Topic 的竞品配置、 `web_search`、`cycle`、`run_time`、`week_days`、`month_days`、 `auto_enabled`、`auto_report`、`measurement_mode` 和逐 Topic 的 `monitoring_profile_ids:{"":}`。Profile 必须 active、归当前 owner、属于同品牌/Topic 且模式一致; 多 Profile 歧义返回 409,跨 owner 403,Topic/品牌错配 422,模式错配 409。任务详情/列表返回同一映射,并在 `keywords[]` 回显 `monitoring_profile_id/target_market/language/measurement_mode/profile_status`。 历史 `platform_caps`(含 `deep_thinking`/`share_link`)、任务级 `screenshot`/`deep_thinking` 字段 仅为 API/存储兼容保留,当前测量引擎不消费这些配置;客户端不要展示为可执行能力或传入来改变模型行为。 未传 Profile 时,服务端按 Topic 市场 Registry 创建或复用 canonical Profile;CN 的新 Profile 始终使用 `zh-Hans-CN`,即使引用的历史 Keyword 仍以 `zh-CN` 保存和回显,也不会改写该历史 Keyword。 未传 `auto_enabled` 时默认 true;显式 false 创建为 `paused` 且没有 `next_run_at`,非 stopped 任务在更新此开关时同步切换 `running|paused`,暂停任务仍允许 `run_now`。 新建任务的 `measurement_mode` 只允许 `controlled_benchmark`;传原生模式返回 422 `native_observation_read_only`。编辑历史原生任务会把未来 occurrence 迁移到统一受控 Profile,既有冻结 occurrence 和 Run 保持原样。 - Monitor competitors have one execution authority: the Topic's canonical active `Competitor` directory, also used by new-run measurements. `GET /api/tasks/{id}` and task lists return `competitors_by_keyword:{"":[{name,aliases}]}` and `competitor_revisions:{"":}`. To edit future runs, `PATCH /api/tasks/{id}` submits explicit confirmed entities (or manual name strings) in `competitors_by_keyword` and the displayed revision in `competitor_revisions`. Edits are atomic with the task save; retained string names preserve their existing aliases and removed competitors are archived. This changes future measurements of **every task sharing that Topic**, never sealed Manifests or past reports. Generated objects must be confirmed before persistence; an unconfirmed candidate returns 409 `pending_confirmation`. A stale or absent revision returns 409 `competitor_configuration_changed`: reload, review the current directory, then explicitly resubmit. Account/Brand/selected-Topic mismatch and ambiguous input return `competitor_topic_scope_mismatch` or `competitor_configuration_ambiguous`. Legacy flat `competitors` can initialize an empty **single-Topic** directory or retain an identical existing name set. They cannot silently replace a nonempty directory (`competitor_configuration_confirmation_required`) or fan out over multiple Topics (`competitor_topic_scope_required`). Historical task JSON is returned as `legacy_competitors` for explicit review; it is never auto-imported. `competitor_baseline_too_small` means fewer than three distinct valid configured competitors, counting names and aliases conservatively. It does **not** require historical competitor Runs, provider verification, cache clearing or a schedule to have fired. Fix the configured directory and retry admission. - `TaskControlReq.action` 只允许 `enable|pause|stop|archive|run_now`。 `enable|run_now` 在任何状态修改或执行副作用前要求 `measurement_readiness.ready=true`;否则返回 422 `measurement_admission_not_ready` 或 `competitor_baseline_too_small` 和安全 `readiness`,任务保持原状态且不物化任何 occurrence。 run_now 的模型/主词/计费校验同样先于物化:校验失败时不留下任何 occurrence 或绑定变更。 - `DELETE /tasks/{id}` 在存在 `running|retry_wait` 批次时返回 409 `task_execution_in_progress` (附 `occurrence_id`),需等批次终态后重试;仅 `queued` 批次不阻断。`PATCH /tasks/{id}` 仅将尚未冻结准入、没有 hold 的 queued 批次终结为 `failed`(`occurrence_superseded`):编辑后的配置只作用于未来批次, 已在执行的子 Run 仍按其封存 Manifest 完成。 - `MonitorTaskIn.cycle ∈ hourly|daily|weekly|monthly`;`hourly` 使用 `run_time` 的分钟作为每小时偏移。 - `GET /tasks/{id}/occurrences` 返回 `{items,total}`;每项含 `status/source/runs[]` 聚合;每个 Run 行返回 `run_id/task_id/report_id/report_status` 及完整冻结导航范围 `project_id/project_name/brand_id/brand_name/keyword_id/keyword_text`、 `monitoring_profile_id/monitoring_profile_version/target_market/language/measurement_mode`。兼容 `GET /tasks/{id}/runs` 返回同一 Run 行契约。v5 的 Manifest 快照优先于可变 Brand/Keyword;`GET /runs/{run_id}` 也在顶层回显同一范围,供 run_id-only 深链原子恢复 store、顶栏和后续 URL。 - 每个 occurrence 还含 `status/source/runs[]/attempted/completed/full_success/partial_success/failed/running` 聚合;报告身份只在对应的 `runs[]` 项中返回,不存在 occurrence 顶层 `report_id`; `runs[]` 还含 `report_status/report_available`;客户端以 `report_available` 为准,只有子 Run `done|partial`(兼容历史 `success|succeeded`)且同身份 Report ready 时才可查看。弃用的 `succeeded` 仅等于 `full_success`,不能用作 completed 或 terminal 数量。 - `QueryReq`:`prompt`、`brand` 必填;可选 `competitors[]`、`models[]`、`web_search`。 - Admin 产品定价只由 `/api/admin/pricing` 管理 Mia Points 操作价。供应商模型成本属于 内部核算,不对 Admin Console 暴露,也不作为用户任务准入条件;成本覆盖不足必须显式 标记,不能阻断已有合法身份、权限和 Points 的任务。 - 生成请求:竞品 `{brand,industry?,region?,keyword?}`;长尾 `{keyword,brand?,industry?}`;quick wins `{brand,keyword?,industry?}`;技术稽核 `{url}`;内容评分 `{text,keyword?,keyword_id?}`,`keyword_id` 存在时优先按本地项目主词上下文评分,越权返回 403。这里的 `/geo/score-content` 是不持久化的兼容结构检查; 新内容诊断闭环使用 `/keywords/{keyword_id}/content-assessments`,创建参数为 `{text?|url?,idempotency_key?}` 且 text/url 必须二选一,深度评分确认参数为 `{expected_points}`。 - 草稿 `template ∈ blog|listicle|comparison|howto`,`status ∈ draft|reviewing|done`;把已完成的文章改写保存为草稿时, `POST /keywords/{keyword_id}/drafts` 传 `source_generation_id`,服务端从该同 Account、同主词的 rewrite 终态复制正文、 冻结结构可提取性与品牌事实版本,不接受客户端重算结果;PATCH 可带 `rescore=true`。成稿必填 `client_request_id`,并支持 `topic/platform/skill_id/ai_answer/brief/outline/must_include/ brief_longtails/improve/template/competitors`;文章改写同样必填 `client_request_id`。客户端应对一次用户创建 动作生成稳定的 8–64 字符 URL-safe ID;相同 ID + 相同 payload 重放会返回同一 durable row,相同 ID 改参数返回 409 `content_generation_idempotency_conflict`,避免响应丢失或双击造成重复计费。 成稿为 **202 异步**:立即返回入队的生成记录 (含 `generation_id/status/lifecycle_status/created_at`),轮询 `GET /content-generations/{id}`;客户端必须 以 durable `lifecycle_status` 为准:`queued|submitting|running|retry_wait|upstream_generating|verifying` 全部继续刷新,只有 `ready|failed` 终止。粗粒度 `status` 仍为 `generating|succeeded|failed`,因此 `status=succeeded,lifecycle_status=verifying` 不是终态。`GET /keywords/{keyword_id}/content-generations` 接受 `platform?`、`status?`、`page>=1`、`1<=page_size<=100`,返回 `{items,total,page,page_size,platforms}`; 列表项只有 `text_preview`,`GET /content-generations/{generation_id}` 才返回完整 `request/text`;内部 `_lineage` 不会出现在 request。列表与详情都返回 `support_id/attempt/max_attempts/next_attempt_at/next_retry_at`、`retryable/status_version/error_code/error` 以及 `parent_generation_id/root_generation_id/retry_depth/successor_generation_id?`。错误字段只允许稳定安全码和 通用文案,绝不返回 provider 异常正文、凭证或原始响应体。 详情还返回 `upstream_batch_id/upstream_item_id`、`structural_extractability`(旧 `geo_score` 为兼容别名)、`quality_status/facts_freshness/citation_status/publish_eligible` 与脱敏 `evidence_receipt`,并返回 `action_item_id?`(成稿成功后关联的审核/发布工单)和 `facts_version_used/current_facts_version`。读取时按当前品牌事实版本和 digest 动态计算 `facts_freshness`;事实升级后旧稿立即显示 `stale`,即使历史状态尚未回填也不得显示为当前。 成稿成功会幂等建立 `planned` Placement;真实发布仍须用户确认。 `publish_eligible` 只在结构、当前品牌事实、claim 检查和绑定 citation 全部通过时为 true;citation 的 URL 必须匹配同一证据回执中的来源 URL(允许片段、默认端口和尾斜杠规范化),任意外部 URL 不构成绑定证据。 `POST /content-generations/{generation_id}/retry` 返回 202,并用原冻结 payload 追加一个 successor;同一 parent 的重复请求确定性返回同一 successor 与同一 provider idempotency key,不会重复生成或重复计费。对 successor 到达终态后再次 retry 才产生下一代;非终态 retry 返回 409 `content_generation_still_active`。每个真正接受的 successor 是一次新的 AutoSocial Account 成稿操作,mia-geo 不再另行结算第二份本地 MIA Points。 两个端点都按主词所属 account 校验,越权 403;失败记录只暴露脱敏错误码和通用文案。 成稿由持久 worker 在调用 provider 前先冻结并落盘完整 prompt/评分上下文,执行一次提交并保存 `upstream_batch_id/upstream_item_id`,之后短轮询 AutoSocial v2;通常需数分钟,但 `upstream_generating` 没有固定的本地完成时限,自动重试及 5/30/120 秒退避会延长总时长。 Pod 重启后按上游 ID 和冻结输入恢复;自动重试与手工 successor 都复用同一冻结 prompt, 不会让同一 provider idempotency key 携带变化后的品牌/主词 payload。 The durable job freezes immutable Account and optional initiating-User attribution; it never persists or reconstructs human authorization. Before resumed business-data access, the worker validates unchanged Account/Keyword/Brand ownership and the encrypted attribution. A creator session or membership is not execution authority. AutoSocial calls use GEO's Service key plus the immutable Account and optional initiating-User audit attribution. The worker never relays a User credential and never falls back to identity selectors, a reconstructed credential, `PLATFORM_KEY`, or an AutoSocial-owned credential. If admission cannot persist the identity snapshot, creation returns HTTP 503 `account_authority_unavailable` and creates no job. Platform transport failures never authorize reconstruction or fallback. An already accepted `upstream_item_id` remains immutable, so any authorized continuation polls that same item and never resubmits or charges a duplicate generation. HTTP/网关超时不会再把仍在上游生成的成稿标成失败。只有真实 provider 错误才消耗失败重试预算, 可重试错误按 5/30/120 秒退避(默认最多 3 次),终态错误码进入历史记录: 超时类 `autosocial_generation_timeout|upstream_timeout`,上游失败 `autosocial_generation_failed`, 确定性失败 `invalid_platform|prompt_too_long|resource_not_found`、认证失败 `autosocial_unauthorized` 与契约失败 `autosocial_contract_mismatch` 不重试直接终态; `autosocial_unavailable`(429/5xx/网络)可重试。旧记录中的 `autosocial_generate_rejected` 仅保留展示兼容,不再由新提交或轮询产生。 生成正文后进入 `verifying`;若事实核验 admission 自身不可用,记录安全降级为 `status=succeeded,lifecycle_status=ready,quality_status=verification_unavailable,publish_eligible=false`,不会永久卡在 verifying,也不会把未核验正文标成可发布。 AutoSocial 未接入时创建请求返回 503 `{"error":"autosocial_unavailable"}`;非法平台返回 HTTP 400,错误码 `invalid_platform`。 - `/suggestions` 策略项含 `platforms`(可成稿)、`platform_keys`、`citation_sources`(只读引用洞察)、 `platform_rules[]`;国外 citation 域不会进入可成稿平台。 - 报告下载的 `lang` 默认 `cn`;任何以 `en` 开头的值归一为英文,其余归一为中文。报告生成 `POST /keywords/{keyword_id}/reports` 虽在路由清单中,但代码要求 admin 模式;普通用户报告由 `done|partial` Run 自动、幂等生成。 - HTML/PPTX/PDF/XLSX 只从与身份受众匹配的不可变 revision 制品读取;报告 ready 后持久化 `queued` 制品任务,独立 worker 按 cn HTML→PDF→PPTX→XLSX、再 en 的顺序生成脱敏 `audience=owner` 与完整 `audience=admin`。数据库唯一键和 S3 key 都包含 revision 与 audience,严禁串读。 owner 缓存移除原始 provider 错误、判定原因和 judge 错误;显式 Admin Mode 才读取 admin 缓存。 `GET /api/reports/{id}/artifacts/{fmt}?lang=cn|en` 未就绪时返回 `202 {status:"preparing",retry_after}`;就绪时返回 `{status:"ready",url,expires_in:120,size,sha256}`。URL 是私有 S3 的短时预签名 GET,禁止写日志。 Web 进程不读取制品字节、不即时渲染;iframe 直接使用 URL,下载使用直接链接,不使用 `srcdoc`、`blob()` 或时间戳破缓存。对象键为 `{prefix}{account_id}/{report_id}/{revision_public_id}/{audience}/{locale}/report.{fmt}`, 对象声明 `private, max-age=31536000, immutable`,数据库不保存新制品 bytes。 - 同一 image 由 `MIA_ROLE=web|worker` 拆分进程职责;默认 `web` 只服务请求、鉴权、状态与签名, `worker` 启动 scheduler 和全部 durable workers(报告 renderer 并发 1 起配)。非法 role 启动失败。 - 站点稽核、验收执行和交付包都是 durable job。创建 job 的 202 表示已经可靠入队,不代表执行成功; 客户端必须轮询到终态。所有 cancel 接口只接受当前资源 owner;需要 `expected_version` 的请求缺失前置条件 返回 428,版本或状态冲突返回 409。错误摘要只含安全错误码/通用说明,不回显 URL 正文、材料、问题、回答、 草稿或证据片段到日志。 - 归因资产的 `{asset_key}` 只接受清单返回的稳定 key,未知 key 返回 404;下载响应带实际 SHA-256,调用方可 在落盘后核验。交付包的 `manifest` 是冻结快照的一部分,不能把重新生成的当前业务数据拼入历史 ZIP。 - 列表中的 `scope=all` 只有真实 admin 身份 + Admin Mode 才生效;普通用户/PAT 伪造该参数不会越权。 - `POST /api/admin/data/repair-trust` 是有界、可续跑的历史数据修复:`limit` 1–5000、 `after_run_id>=0`、`regenerate_reports` 默认 true、`dry_run` 默认 true。dry-run 按 `data_version` 返回 citations/Source 数量和计划移动/删除数但不写库;执行写入必须显式传 `dry_run=false`。 2.0→2.1 只重算判定/指标/报告,绝不移动真实 citations 或删除 Source;只有明确 legacy 版本执行旧引用迁移。 `/api/v1/mia/dashboard/*` is MIA Data Platform's dedicated Account-scoped pull surface. It accepts only MIA Platform's admitted calling Service credential plus explicit `MIA-Account-Id` and optional `MIA-Initiated-By-User-Id` audit attribution. It rejects PATs, relayed human credentials, static pull credentials, identity selectors, and alternate protocols, and is not a general Agent API. ### 项目 Run 的 MIA Points 预估与确认合同 `POST /api/estimate` 的当前完整响应是 `mia-geo.run-estimate.v3`,客户/Agent 边界只有 MIA Points, 不会返回 provider 成本、法币或内部会计字段。准备发起单个项目 Run 时应传 `keyword_id`,服务端会按该用户的 owner 边界读取主词、当前 benchmark 问题数、有效模型配置和 DB 最新 `run` 单价;请求中的 `total_questions` 在这种模式下不会覆盖服务端事实。 estimate 与随后 `POST /api/keywords/{keyword_id}/runs` 使用同一个显式协商头: `X-MIA-GEO-Estimate-Schema`。当前支持 `mia-geo.run-estimate.v1` 与 `mia-geo.run-estimate.v3`;两个请求必须声明同一版本。未发送该头的既有消费者按兼容窗口返回 `v1`,显式发送值 `mia-geo.run-estimate.v3` 才返回完整当前字段;未知版本在任何 Run/Points 副作用前返回 406 `unsupported_run_estimate_schema`,不会静默选择其它版本。官方 GEO 工作台始终 显式请求 v3。 v1 是只为已部署 Mia Agent 保留的传输投影:`schema_version` 为 `mia-geo.run-estimate.v1`、`pricing_mode` 为 `flat_per_run`,`per_round_points` 则仍是下述当前 per-unit 规则算出的完整确认总额。签名 token 继续冻结协商版本以及 v3 的模型、问题、补检、实体核验、Account、 输入指纹和 Points 上限;reserve/settle/release 也只走当前统一实现。它不会恢复旧的一口价算法。 v3 将费用组成显式返回:基础测量单元为 1 个模型 × 1 个长尾问题;延迟 retrieval recheck 已停用, 其兼容字段固定为 0;每个计划回答槽位由当前配置的两个独立 entity verifier 核验,并在 `entity_resolution` 中单列调用上限、单价和 Points。**默认部署下 entity verifier 不单独计费**: `entity_resolution.unit_points` 与 `points` 为 0,`verifier_calls` 只是供应商调用上限。 `billing_units` 等于基础单元,`per_run_points` 是全部计费组成的合计上限,默认等于基础测量点数。 `measurement_lanes` 与 `samples` 不重复放大基础/补检单元,但会增加回答槽位及其 verifier 调用上限。 结算只计实际成功的基础测量单元,未使用差额随 hold 释放,因此实扣点数可能低于 `expected_points`, 但绝不会高于它。 `billing_breakdown` 是客户确认使用的服务端权威组成,分别给出基础测量与实体核验的单元、单价和 Points, 并用 `maximum_points` 重申本轮合计上限;浏览器不得用基础 `billing_units × unit_points` 冒充完整总价, 也不得因为某个组成为 0 就省略它。 该端点有两种明确模式: - **keyword-bound Run 确认**:传 `keyword_id`(正整数)。`models=[]` 时读取主词保存的模型; `total_questions`、`samples`、`runs_per_round`、`week_days`、`month_days` 均不覆盖服务端事实, 单次 Run 固定 `samples=1`、`runs_per_round=1`,问题数来自当前 benchmark 问题。此模式返回 `input_fingerprint`、`prompt_readiness`、冻结 `question_ids`、`estimate_token`、`expires_at`,可用于下面的付费 admission。正式问题不足或模型集合为空时返回 422 `measurement_admission_not_ready`; 少于 3 个不同的有效竞品时返回 422 `competitor_baseline_too_small` 及 `competitor_readiness`; `competitor_readiness` 还带**非阻塞**的 `entities_missing_market_script`(名单)与 `missing_market_script_count`:目标市场为中文市场而品牌或竞品没有任何含汉字的名称/识别名时列出, 因为提及统计只做字面匹配,这类实体在该市场回答里会被记为零提及。同一信息以 `warnings:[{code:"entity_missing_market_script",severity:"advisory",entities,message}]` 下发, `ready` 与 Points 均不受影响;失败不会创建 Monitoring Profile、Run 或 Points hold,也不会冻结账本记录。 - **通用排期投影**:不传 `keyword_id`。用于任务编辑等未绑定单个 Run 的展示,接收 `models`、`total_questions`(非负整数,默认 0;可汇总多个 keyword Run,因此不套单 Run 2000 上限)、`samples`(1–100,默认 1)、 `runs_per_round`(1–100,默认 1)、`cycle`(`once|hourly|daily|weekly|monthly`,默认 `once`)、`week_days`(最多 7 个不重复的 0–6)、`month_days`(最多 31 个不重复的 1–31)、`run_type`(`baseline|post|scheduled|manual`,默认 `baseline`)、`web_search` (默认 true)、`measurement_mode`(普通客户/Agent 只允许 `controlled_benchmark`,默认同值;请求 `native_observation` 返回 422 `native_observation_read_only`)、可选 `monitoring_profile_id`。 `models=[]` 表示当前全部可用模型。它只做 MIA Points 投影,返回 `keyword_id=null`、`input_fingerprint=null`,且**不返回** `estimate_token`/`expires_at`, 不能授权创建 Run。 两种模式都禁止未知字段(422)。`per_round_points = per_run_points × runs_per_round`; 月轮次按 `once=1`、`hourly=720`、`daily=30`、`weekly=4×week_days 数量(空列表按 1)`、 `monthly=month_days 数量(空列表按 1)` 计算。 真正创建时仍由每个 keyword Run 独立执行 `问题数 × 模型数 × samples × 测量通道数 ≤ 2000` 的硬门禁; 该上限容纳基线最多 100 个问题 × 8 个当前可选目录模型 × 2 条受控通道。 请求示例: ```json { "keyword_id": 42, "models": ["deepseek", "qwen"], "cycle": "once", "run_type": "baseline", "web_search": true, "measurement_mode": "controlled_benchmark", "monitoring_profile_id": 7 } ``` 响应核心字段: ```json { "schema_version": "mia-geo.run-estimate.v3", "unit": "MIA Points", "pricing_mode": "per_unit", "billing_unit": "model_question", "keyword_id": 42, "models": ["deepseek", "qwen"], "total_questions": 8, "samples": 1, "run_type": "baseline", "web_search": true, "measurement_mode": "controlled_benchmark", "monitoring_profile_id": 7, "monitoring_profile_public_id": "", "monitoring_profile_key": "<64-char SHA-256>", "monitoring_profile_version": 1, "target_market": "CN", "language": "zh-Hans-CN", "measurement_lanes": ["target_market", "global_supplemental"], "input_fingerprint": "<64-char SHA-256>", "unit_points": 30, "billing_units": 16, "base_billing_units": 16, "retrieval_recheck_billing_units": 0, "retrieval_recheck_rounds": 0, "round_billing_units": 16, "entity_resolution": { "component": "entity_resolution_verifiers", "verifier_calls": 192, "unit_points": 0, "points": 0, "included_before_execution": true }, "billing_breakdown": { "unit": "MIA Points", "maximum_points": 480, "measurement": { "component": "measurement", "billing_units": 16, "base_billing_units": 16, "retrieval_recheck_billing_units": 0, "unit_points": 30, "points": 480 }, "entity_resolution": { "component": "entity_resolution_verifiers", "verifier_calls": 192, "unit_points": 0, "points": 0, "included_before_execution": true } }, "per_run_points": 480, "runs_per_round": 1, "per_round_points": 480, "runs_per_month": 1, "monthly_points": 480, "estimate_token": "v1..", "expires_at": "..." } ``` 当 keyword-bound 请求显式选择或唯一解析到 Monitoring Profile 时,响应和签名 token 都包含 `monitoring_profile_id/public_id/key/version/target_market/language`,让 Agent/旧客户端在确认 Points 前明确知道 实际采用的地域身份;Run 创建响应也回显这些字段。`input_fingerprint` 是服务端对主词/品牌/竞品/benchmark 问题/模型及执行开关所做的 SHA-256, 不包含可逆客户文本。`estimate_token` 用服务端签名,默认 15 分钟有效,并绑定调用身份、 `keyword_id`、该 fingerprint、采样数和当前 MIA Points。 若尚无 active Monitoring Profile,服务端只会在 Topic 已保存且枚举有效的 `market` 与 `language` 能够唯一确定上下文时幂等固化一个同 owner、同品牌的 profile;不会使用默认值或跨 owner/品牌复用。 字段缺失、legacy 未指定、枚举不匹配或归属不一致时,在签发 estimate、创建 Run 或预留 Points 前返回 409 `monitoring_profile_context_required`。存在多个 active profile 且未指定 ID 时仍返回 409 `monitoring_profile_required`,调用方必须明确选择,不能猜测;历史非正式市场 profile 不参与默认候选。 运维可用 `RUN_ESTIMATE_TOKEN_TTL_SECONDS` 配置有效期,默认 900 秒,并强制 clamp 到 60–3600 秒。`POST /api/keywords/{keyword_id}/runs` 中 `expected_points` 必须是 0–1,000,000,000,000 的整数;`estimate_token` 只要求满足签名 token 的最小结构长度,不设置应用侧最大长度。两者均为未知字段禁止模型的一部分。 `POST /api/keywords/{keyword_id}/runs` 必须在创建 Run/预留额度前收到: - 推荐:`estimate_token` + `expected_points`(两者都校验); - 迁移兼容:只传整数 `expected_points`,服务端仍以完全解析后的当前入参重新算价并比较。 `estimate_token` 不能单独提交;缺少配对的 `expected_points` 会在请求模型校验阶段返回 HTTP 422, 且顶层校验信封稳定标记 `"fields":["estimate_token"]`。 缺少两者返回 HTTP 428: `{"detail":{"error":"estimate_confirmation_required","message":"...","estimate":{最新预估}}}`。 价格、模型、问题数、身份/主词或其他签名输入发生变化,以及 token 过期/签名错误,均在创建 Run 或 hold **之前**返回稳定 HTTP 409: `{"detail":{"error":"estimate_changed","message":"...","estimate":{按本次协商版本返回的最新预估}}}`。 签名输入包含 DB-fresh Monitoring Profile 的 `id/public_id/profile_key/version/target_market/language/measurement_mode`;因此任何并发/旧代码导致的 Profile 身份漂移都会让旧 token 在创建 Run/预留 Points 前返回上述 409。公开 PATCH 本身不允许把同一 Profile ID 从 CN 改成 US;新市场必须 POST 得到不同 ID,不能沿用旧市场确认或历史 Run。 请求模型禁止未知字段(422),因此调用方不能发送一个被静默忽略的“确认”字段。 启用计费时,email 始终只是可选展示资料,不参与估算、token 绑定或钱包选择;当前请求缺少 有效 Platform Account authority 才会在预估和 admission 前返回 HTTP 409 `billing_account_authority_required`。权威定价表不可读则 fail-closed 返回 HTTP 503 `pricing_unavailable`,不会回退 env/default 价格完成确认或 hold。 v1 响应投影与 `expected_points` 单字段兼容由 mia-geo owners 负责,只允许当前已部署 Mia Agent 继续使用,不接受新的 v1 调用方。删除条件是 Mia Agent 已在生产显式发送协商头并使用 v3、GEO 的 SIT 跨服务 smoke 固定验证该组合,且连续 90 天没有 v1 请求。该删除必须作为独立受审变更执行。 客户端在收到 409 后 必须把 `detail.estimate` 展示给用户重新确认,不能自动用新价格重试。 浏览器工作台同样先展示本次 MIA Points、问题数和模型数,只有用户点击确认后才提交签名 token; 若 admission 返回 409,必须再次展示 replacement estimate 并要求第二次点击。 ### 工作台检索是「发起 → 轮询」的异步契约(供理解产品行为) 1. 先按上一节完成估价确认;`POST /api/keywords/{id}/runs` 成功后**立即返回** `{"run_id":123,"status":"running","estimate_schema_version":"<本次协商版本>","confirmed_points":30}`, 实际检索在后台任务里跑。 2. 轮询 `GET /api/runs/{id}/status` → `{run_id,status,total,done,attempted,completed,succeeded,failed,excluded,missing,by_lane,status_source,model_errors,brand_id}`, 用 `completed/attempted` 看进度。v5 终态只从最新 `brand_answer_presence_rate` MetricResult 的冻结 attempt receipt 统计,`status_source="metric_result_receipts"`;指标尚未生成时仅统计测量控制与执行身份均已验证的 terminal ModelAttempt,其他 terminal 项进入 `excluded`,缺少 selected attempt 的计划槽进入 `missing`;queued/running ModelAttempt 尚未 `completed`,也不进入 `failed`。绝不因存在非空 Response 就算成功。legacy Run 保持旧响应兼容。 轮询错误中 `terminal:true` 必须停止,`terminal:false` 或 `retryable:true` 必须退避重试;无 HTTP 状态的网络错误、 HTTP 5xx、408、425、429 可重试,其他未显式声明可重试的 HTTP 4xx(包括普通 403/404)必须停止。 3. `status == "done"` 或 `"partial"` 后聚合类端点才有有效数据;`partial` 必须同时展示缺失模型警告。 `failed` 表示无成功回答,不生成指标/报告,也不应画 0 分柱。 4. 单次 run 扇出 = 长尾数 × 模型数 × 采样数 × 测量通道数,**硬上限 2000**(超出 → HTTP `422`, 错误码 `run_query_capacity_exceeded`)。 与 `/api/skill/*` 不同,这条路径**会预扣并结算 Mia Points**:额度不足 → `402` `{"error":"redirect_required","reason_code":"insufficient_credit","redirect_url":…,"available_points":…}`; 计费服务不可用 → `503 platform_unavailable`。 5. 工作台只在取得真实 `run_id` 后跳转检索分析,并在 URL 中保留项目、品牌、主词、 `monitoring_profile_id`、市场、语言、Run 和 measurement batch;结果页按这组冻结身份 fail-closed, 不能静默回退到同品牌旧主词或旧 Run。用户在确认前取消、estimate 失败或服务端明确返回 `batch_created:false` 时停留在确认页,显示“配置已保存、检索未发起”及 0 Points hold;若 admission 返回已创建批次,则展示批次 ID 与已确认/待核验的 hold、settle、release 状态。远端结果未知时不得宣称 未发起或已释放,必须允许使用同一资源 ID 和幂等键重试;不得跳转到没有 Run 的空结果页。 ### 枚举值 `run.status ∈ admitting|queued|running|cancel_pending|cancelled|done|partial|failed` · `run.type ∈ baseline|post|scheduled|manual` · `sentiment ∈ positive|neutral|negative|null` · `verify(judge) ∈ rule|llm|llm+guard` · `report.type ∈ automatic|weekly|monthly|daily|hourly|manual` · `report.status ∈ stale|generating|ready|failed` · `content_generation.status ∈ generating|succeeded|failed` · `content_generation.lifecycle_status ∈ queued|submitting|running(legacy recovery)|retry_wait|upstream_generating|verifying|ready|failed` · `placement.status ∈ planned|generated|published` · `template ∈ blog|listicle|comparison|howto` · `intent ∈ recommendation|comparison|discovery|scenario|purchase_decision` · `task.cycle ∈ hourly|daily|weekly|monthly` · `task.status ∈ running|paused|stopped` · `occurrence.status ∈ queued|running|retry_wait|done|partial|failed|succeeded` · `measurement_scope ∈ benchmark|brand_probe|quickwin` · `fact_finding.category ∈ consistent|contradiction|unsupported|unknown` · `fact_finding.review_status ∈ candidate|confirmed|dismissed|resolved|stale` · `manual_batch.status ∈ draft|collecting|reviewing|finalized|cancelled` · `territory.coverage_status ∈ unmeasured|absent|present|building|maintaining|not_applicable` · `territory.manual.status ∈ building|maintaining|null` · `territory.category ∈ owned|partner_media|industry_media|encyclopedia|community|social|review|retailer|competitor_owned|developer|other_source` · `site_audit.mode ∈ quick|deep` · `site_audit.status ∈ queued|running|retry_wait|billing_pending|succeeded|failed|cancelled` · `acceptance_execution.status ∈ queued|running|retry_wait|passed|failed|skipped|error|cancelled` · `delivery_bundle.audience ∈ owner|admin` · `delivery_bundle.status ∈ queued|running|retry_wait|billing_pending|succeeded|failed|cancelled` ## 护城河 ① 中文模型原生 + 分平台视角;② 双保险验证(LLM 判定 + 字面文字核对,杜绝幻觉误判); ③ Agent 原生(canonical `SKILL.md` 同源派生 `/llms.txt` 与 `/skill.md`;支持 Skill 候选规划与项目 v5 API, 项目 API 的 Agent 自动化使用 Account-bound PAT)。 ### Frozen report evidence `GET /api/keywords/{keyword_id}/insight` exposes additive `report_evidence` (`geo-report-evidence/v1`) for v5 runs. It copies target-market MetricResult receipts, exact per-model counts and denominators, tracked entities, and frozen question cohorts. Receipts identify the Run, manifest, lane, revision, algorithm and collection time. The same envelope is frozen in new report revisions. Analytics v1.5 persists the eligible denominator for per-model ranking rates and explicit top1 counts for tracked entities. Older receipts leave missing fields null; clients must not reconstruct denominators from rounded rates or infer a first-place count from average rank. Brand-named and competitor-named questions remain admissible when explicitly supplied, but cannot support unaided-discovery claims. Literal presence is not answer correctness. Controlled API observations do not prove consumer-app behavior or training-data causes. Client report sources must point to measurement evidence, never to a brand homepage as metric proof. ### Monitor occurrence progress Task occurrence/list projections expose `planned` (frozen topic count) and `pending` (topics without a materialized Run) separately from `attempted` (materialized latest-generation topic Runs). Display completed/planned progress; legacy unfrozen batches use their actual attempted count. Real child Run rows are never synthesized. An active occurrence remains active even when every currently created child is terminal, so clients must keep polling until the occurrence and required report artifacts finish. A completed prefix does not prove batch success or aggregate Points settlement. ### Report answer-count semantics The sealed v5 customer projection `v5.customer.3` retains every stored answer slot, including failed target-market slots and separate supplemental-market samples. Each row exposes `answer_status: returned|unavailable`, `measurement_eligibility: eligible|excluded`, closed `model_execution_exclusion_reasons`, and an optional closed `answer_failure_reason`. A v2 row may additionally expose `retrieval_recovery:{attempts,limit,outcome}` with integer counts 1–5 and closed outcomes `succeeded|empty|unconfirmed|access_denied|configuration|timeout|capacity_exceeded|unavailable`. When retrieval prevented the model call, the failure distinguishes empty evidence, unconfirmed result, access denial, configuration, timeout, rate limit and unavailable service. These counts are request ordinals, including an unconfirmed dispatch; they do not claim a confirmed physical supplier send count. Failed attempts expose no partial answer body and no supplier error prose. An unavailable answer has no mention verdict; it is not a brand miss. Supplemental answers remain outside target-market metrics and never replace failed target slots. These fields are read-only: formal metric receipts, Run execution, Points and frozen reports do not change. The insight read projection adds `count_semantics_version: geo-report-counts/v1` and `answer_counts` to `report_evidence`. Planned slots, raw returned answers, metric-eligible answers and evidence exclusions are separate populations within one Run and lane. Exclusions can include returned answers and execution failures; `eligible_failures` is not the raw failure total. Never subtract eligible answers from planned slots and call the difference failed executions. Platform aliases make legacy `succeeded` explicit as `metric_eligible_answers`; legacy `failed` means `not_included_slots`. Question `successful_answers` likewise counts only eligible answers. `no_eligible_answer_evidence` does not prove no raw answer was returned. Ranking-rate eligibility and positive brand-rank counts are separate denominators. No non-mention total is supplied; do not invent one. The additive semantics are API-read-only. Frozen v1 ReportRevision documents, metric receipts and their integrity checks retain their original bytes. Legacy keys remain for existing consumers; remove them only in a versioned contract after all callers and retained v1 evidence readers have migrated. Question generation accepts optional `research_objective` and `audience`, returns a coverage receipt when supplied, and distinguishes destination choice from planning an already selected trip. Treat generated questions as hypotheses; retain user, GEO-generated and Agent-drafted source labels rather than attributing them to observed searches.