mcpField
R
Rafid Intelligence Network

Rafid Intelligence Network

远程服务
api.rafidsystem.com商业与营销11 周前更新

关于 Rafid Intelligence Network

Rafid Intelligence Network 为 AI Agent 提供房地产、企业、供应商、文档、风险、车辆和物流等领域的结构化信息与付费 API。它面向 Agent 设计,支持按次调用,并提供机器可读结果和公开的工具发现目录。

主要功能

  1. 01覆盖多个商业情报领域
  2. 02提供结构化机器可读结果
  3. 03按次付费调用能力
  4. 04公开工具目录与输入输出模式
  5. 05支持 MCP、OpenAPI 和 REST

适用场景

  1. 01查询房地产或企业相关信息
  2. 02评估供应商与风险数据
  3. 03调用车辆或物流领域工具

MCP 相关

平台支持 MCP,并提供 stdio 传输;启用远程选项时也可通过 Streamable HTTP 使用。MCP 客户端可发现并调用其情报工具。

配置

远程服务无需本地安装。选择你使用的客户端,复制配置或使用一键安装。

服务地址https://api.rafidsystem.com/mcp
一键复制安装命令
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

命令在终端中运行;JSON 粘贴到对应客户端的 MCP 配置后重启。需要授权的服务会在首次连接时提示登录。

工具

74
参数类型必填说明
checksarray否
companystring否
countrystring否
websitestring否

详细介绍

Rafid Intelligence Network — 面向 AI Agent 的结构化情报与付费工具

Rafid Intelligence Network 为 AI Agent 提供涵盖房地产、公司、供应商、文档、风险、车辆和物流的结构化情报与付费 API。**AI Agent 是该平台的主要使用者。**平台支持 MCP、OpenAPI、REST、x402 和 API Credits 等访问渠道。

平台架构

Rafid Intelligence Network
├── Property Intelligence
├── Company Intelligence
├── Supplier Intelligence
├── Document Intelligence
├── Risk Intelligence
├── Vehicle Intelligence
└── Logistics Intelligence

现有的房地产专用能力契约、路由和 MCP 工具名称保持不变。

Agent 的预期流程为:发现能力 → 选择合适的工具 → 通过 x402 按次付费(或使用 API 密钥进行身份验证)→ 执行 → 获取结构化、机器可读的结果。两种访问模式都无需账户、仪表板或订阅,也没有计划提供这些功能——详见下文“设计约束”。

Agent 发现

网站规划和检查可通过付费能力 website_project_estimate($0.25) 和 website_audit($0.75)使用。有关契约、限制、预览调用以及审计到估算的串联示例,请参阅网站能力。

Agent(或 Agent 市场/目录爬虫)可以从以下任一入口开始;这些入口均公开、无需身份验证、始终可用,且不包含机密信息(没有钱包私钥、API 密钥或其他客户的使用数据):

端点用途
GET /agent.json完整的 Agent 清单:产品身份、所有支持的协议(MCP、x402、REST)及其用途、x402 条款,以及包含完整输入/输出 JSON Schema 的工具目录。请从这里开始。
GET /.well-known/ai-plugin.json采用旧版 OpenAI ChatGPT 插件规范的清单,供仍通过这种方式发现服务的工具使用。
GET /.well-known/agent.json遵循 Agent2Agent(A2A)协议规范的 Agent Card,其中将每个工具列为一项 skill。
GET /llms.txt面向基于 LLM 的 Agent 的纯文本简介:Rafid 的功能、所有工具及其调用方式、定价、x402 模式和已知限制——无需解析 JSON。
GET /api/v1/capabilities以机器处理为先的能力注册表:每个工具的名称、描述、whenToUse、价格、Schema 和示例——专为模型判断应调用什么而优化,而非供人阅读。
GET /api/v1/mcp/statusMCP 状态的实际信息:哪些传输方式已启用(stdio 始终启用,http 仅在 MCP_REMOTE_ENABLED=true 时启用)、工具数量,以及远程端点路径(如有)。
GET /api/v1/agent旧版服务元数据端点(为向后兼容而保留);已由上面的 /agent.json 取代。
GET /api/v1/pricing完整价格表(USD,按次付费),来源于所有代码路径共用的单一目录——绝不会重复或不同步。
GET /api/v1/tools旧版工具目录(为向后兼容而保留);已由上面的 /api/v1/capabilities 取代。

以上所有内容均由 src/domain/capabilities.ts 中的单一能力注册表生成——详见下文“能力注册表”——因此它们彼此之间,以及它们与实际调用行为之间,绝不会出现不一致。

选定工具后,可使用 X-API-Key 请求头调用;或者在 X402_ENABLED=true 时,不提供密钥,改为调用对应的 /api/v1/x402/... 端点并在链上按次付费(详见下文“通过 x402 按次付费”)。

通过浏览器访问 / 的用户会看到简短的落地页(包含 Agent 集成示例、工具列表、定价和文档链接);发送 Accept: application/json 的 Agent 和脚本仍会收到原有的 JSON 发现数据——详见下文“落地页”。

设计约束

本产品有意以 Agent 为先,而非面向人的 SaaS 仪表板。它目前没有、也不计划提供用户账户、仪表板、订阅、Stripe 账单或账单门户。X-API-Key 路由系列是为以这种方式进行身份验证的调用方提供的兼容传输方式,并不意味着应围绕它构建账户管理功能。

客户存储阶段

现已支持基于 PostgreSQL 的客户、哈希密钥、用量跟踪和客户级别的限制。请参阅激活和管理。现有的基于环境密钥的开发模式保持不变;生产环境需要设置 AUTH_MODE=postgres。除 x402 外,尚未实现收款功能(见下文)。

在 Vercel 上部署

设置 VERCEL=1 时,Express 入口会导出应用;本地运行时则保留常规端口监听器。vercel.json 将函数固定在靠近当前美国东部 Neon 数据库的 iad1 区域,并将请求时限限制为 15 秒。.vercelignore 可防止上传本地 .env、缓存和生成的文件。

生产 API:https://api.rafidsystem.com
落地页:https://api.rafidsystem.com/(浏览器)/ 同一 URL 搭配 Accept: application/json(Agent)
健康检查:https://api.rafidsystem.com/api/v1/health
OpenAPI:https://api.rafidsystem.com/openapi.json
Agent 元数据:https://api.rafidsystem.com/api/v1/agent

将代码库目录连接到 Vercel 项目,关联 Neon Marketplace 数据库,并配置以下生产环境变量:

AUTH_MODE=postgres
NODE_ENV=production
LOG_LEVEL=info
X402_ENABLED=false
DATABASE_URL=<sensitive pooled Neon URL with sslmode=verify-full>

Neon 集成可能会注入 DATABASE_URL;请确认它指向已轮换的凭据和连接池端点。手动添加的数据库值请标记为敏感信息。请勿上传 .env 或使用此前已泄露的密码。使用 Vercel CLI 或已连接的 Git 代码库进行部署,然后验证 /api/v1/health、/openapi.json、/api/v1/agent、经过身份验证的分析请求以及 PostgreSQL 用量记录。此 Express 函数不会部署本地 stdio MCP 进程。

如需同时接受按次付费的加密货币付款,请在生产环境中设置 X402_ENABLED=true 和 X402_WALLET_ADDRESS=<a 0x-prefixed EVM address you control>。在准备好接收真实付款之前,请保持 X402_ENABLED=false(默认值);请参阅下方的 Configuration 和“通过 x402 按次付费”部分。在 Base 主网上(eip155:8453),还需设置 CDP_API_KEY_ID/CDP_API_KEY_SECRET(见 Configuration)。

前提条件和快速开始

使用 Node.js 24+ 和 npm。在代码库根目录运行:

Set-Location 'C:\Projects\rafid-agent-api'
npm.cmd ci --cache .npm-cache
if (-not (Test-Path .env)) { Copy-Item .env.example .env }
node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"

将生成的密钥复制到 .env 中的 RAFID_API_KEYS。请妥善保管。密钥为空或过短时,HTTP 服务将无法启动。多个密钥以逗号分隔。在 macOS/Linux 上使用 npm 和 cp .env.example .env。

npm.cmd run dev

开发模式会加载 .env 并监视 TypeScript 源文件。如需运行已编译版本:

npm.cmd run typecheck
npm.cmd run build
npm.cmd test
npm.cmd start

npm test 会先构建,然后运行服务、实时 HTTP、Agent 市场、OpenAPI 契约和已编译的 MCP stdio 测试。npm start 会加载 .env 并运行 dist/server.js;依赖版本已固定在 package-lock.json 中。

配置

变量默认值/行为
PORT8787
NODE_ENVdevelopment;接受 development、test、production
RAFID_API_KEYSREST 必需;随机密钥,至少 24 个字符
API_KEY当 RAFID_API_KEYS 为空时使用的旧版回退选项
LOG_LEVELinfo;仅记录 HTTP/工具 5xx 完成事件的错误日志;silent 会禁用请求日志
X402_ENABLEDfalse;true 会在 /api/v1/x402/... 启用按调用付费的加密货币支付(此处无需 API 密钥),并要求设置 X402_WALLET_ADDRESS。禁用(默认值)时绝不会影响普通的 API 密钥调用——x402 路由系列不会存在,也不会列入发现信息或 /openapi.json
X402_NETWORKeip155:84532(Base Sepolia 测试网);公共 facilitator 仅为 EVM 结算此网络。其他任何网络(例如 eip155:8453(Base 主网))都需要设置下方的 CDP_API_KEY_ID/CDP_API_KEY_SECRET
X402_WALLET_ADDRESS空;X402_ENABLED=true 时必需,接收付款的 EVM 地址,须以 0x 为前缀
X402_FACILITATOR_URLhttps://x402.org/facilitator;必须是 https URL
CDP_API_KEY_ID空;Coinbase Developer Platform API 密钥 ID(Ed25519 Secret API Key)。除 Base Sepolia 之外的任何网络都必须与 CDP_API_KEY_SECRET 一起设置
CDP_API_KEY_SECRET空;Coinbase Developer Platform API 密钥 secret。两个 CDP 变量都设置后,它们将接管 facilitator(此时会忽略 X402_FACILITATOR_URL)
MCP_REMOTE_ENABLEDtrue;在 /mcp 挂载 Streamable HTTP MCP 传输。false 会完全卸载它(返回 404,且所有 manifest/llms.txt 仅声明 stdio)——无需修改代码即可回滚
RAFID_LOGO_URL空;/.well-known/ai-plugin.json 中的 logo_url。留空而不伪造
RAFID_CONTACT_EMAIL空;/.well-known/ai-plugin.json 中的 contact_email
RAFID_LEGAL_INFO_URL空;/.well-known/ai-plugin.json 中的 legal_info_url
USAGE_REPOSITORYconsole(默认:每次调用向 stderr 输出一行 JSON);memory(仅供本地检查,重启后丢失);postgres(持久化的 rafid_agent_usage 表——需要 DATABASE_URL,与 AUTH_MODE 无关)
RATE_LIMIT_ENABLEDtrue;按进程运行、按 IP 限制发现、x402 和远程 MCP 路由的速率(各有独立的额度)。false 会禁用此功能(开发/测试)
RATE_LIMIT_WINDOW_MS60000(一分钟)
RATE_LIMIT_MAX每个 IP、每个路由组在每个时间窗口内 60 个请求
OMAN_PROPERTY_DATA_MODEmanual(默认);database;composite。决定 analyze_oman_property 实际查询的阿曼房产数据源——参见下文“生产环境中的阿曼市场数据”
OMAN_MARKET_DATABASE_URL空;未设置时回退到 DATABASE_URL。可按需将市场数据数据库与客户/计费数据库分开
OMAN_MARKET_STALE_DAYS365;如果可比样本的中位年龄超过此值,则会标记 staleMarketData: true,并确定性地降低其置信度分数。此设置独立于且短于 comparables.ts 的 540 天硬性时效截断(超过此截断的记录会完全排除在数据池之外)
OMAN_MARKET_CACHE_TTL_MS空/0(禁用);设置后,DatabaseOmanPropertyDataProvider 会将可比样本池查询结果缓存在内存中,缓存时长为此毫秒数——参见下文“生产环境中的阿曼市场数据”
OMAN_RECENT_SALES_DAYS730(约 2 年);analyze_oman_property 的 historicalSalesContext.recentComparableSales/recentMedianPricePerSqmOMR 将此时间窗口视为“近期”——与用于当前市场估值的 comparables.ts 固定 540 天截断相互独立,且绝不会影响该截断。参见下文“历史销售背景”
NCSI_API_BASE_URLhttps://map.ncsi.gov.om/ODPAPI(已验证的实时基础 URL——参见下文“官方市场背景(NCSI)”)
NCSI_REQUEST_TIMEOUT_MS8000
NCSI_CACHE_TTL_MS21600000(6 小时);设为 0 可禁用官方背景查询缓存
NCSI_REAL_ESTATE_DATASET_ID空;无默认值——为何必须由运营方验证、绝不能猜测,参见下文“官方市场背景(NCSI)”
NCSI_FIELD_MAP_JSON空;JSON 对象,将此功能的字段名映射到已配置数据集的实际字段名——参见下文“官方市场背景(NCSI)”
MARKET_DATA_INTERNAL_API_KEY空;无默认值。用于保护 GET /api/v1/internal/market-data/status 和 .../partners 的共享密钥——参见下文“合作伙伴数据馈送”。未设置时,这两个路由始终返回 503,绝不会成为无保护的端点
PARTNER_FEED_STALE_DAYS7;如果合作伙伴最近一条已接受记录的时间早于此值,GET /api/v1/internal/market-data/partners 会报告 stale: true——这只是针对单个合作伙伴的监控信号,绝不会影响数据导入、analyze_oman_property 或其他合作伙伴的陈旧状态
VEHICLE_MARKET_DATA_MODEnone(默认:无车辆市场数据提供程序——vehicle_value_estimate 返回 insufficient_market_data);database 会启用 vehicle_market_records 提供程序
VEHICLE_MARKET_DATABASE_URL空;回退到 DATABASE_URL
VEHICLE_MARKET_COUNTRIES空(数据库提供程序不声明任何市场);逗号分隔的 ISO alpha-2 国家代码,表示导入数据涵盖的地区,例如 OM,AE,或 *
VEHICLE_MARKET_PROVIDER_TIMEOUT_MS4000;单个提供程序的超时时间——响应缓慢的提供程序会报告为 timeout,绝不会阻塞请求
VEHICLE_MARKET_CACHE_TTL_MS21600000(6 小时);提供程序搜索结果缓存(估值本身绝不缓存)
VEHICLE_MARKET_MAX_LISTING_AGE_DAYS365;绝不使用早于此时间的证据
MARKETCHECK_API_KEY空;设置后会启用 MarketCheck 提供程序(美国/加拿大的二手车经销商挂牌信息,需授权/付费)。绝不记录或返回
MARKETCHECK_PAGES2;每个市场、每次未命中缓存的搜索返回 50 条挂牌信息,分页数为 1–10
VEHICLE_MARKET_FEEDS_JSON空;合作伙伴 HTTPS 数据馈送的 JSON 数组 [{"id","url","countries","format","authHeaderEnv"}]——参见“vehicle_value_estimate → 市场数据提供程序”
VEHICLE_FX_SOURCES空(不进行货币转换);按顺序尝试的货币对转换源,以逗号分隔:ecb、exchangerate_api
EXCHANGERATE_API_KEY空;使用 ExchangeRate-API 的需密钥端点,而非受速率限制的开放访问端点
VEHICLE_FX_CACHE_TTL_MS21600000(6 小时)
VEHICLE_VIN_DECODERnone(仅离线 VIN 检查);nhtsa 使用 NHTSA vPIC 解码 VIN(会将 VIN 发送给 NHTSA)
VEHICLE_VIN_DECODER_TIMEOUT_MS3000
应用会优先使用已有环境变量,而非 .env。不包含有效凭据。.env、依赖项、构建输出和 npm 缓存均已加入 Git 忽略列表。切勿提交或记录 CDP_API_KEY_SECRET、X402_WALLET_ADDRESS 的私钥(本应用从不请求或存储该密钥)或 DATABASE_URL。

架构

src/
  api/app.ts             Express application factory; no listener side effects
  api/agent.ts           Agent-marketplace discovery, pricing, tool-catalog and capability-registry builders
  api/manifest.ts        /agent.json, /.well-known/ai-plugin.json and /.well-known/agent.json builders
  api/llms-txt.ts        /llms.txt plain-text briefing builder
  api/landing.ts         Dependency-free HTML landing page for browsers
  api/openapi.ts         OpenAPI 3.1 built from shared Zod schemas
  domain/capabilities.ts The single AgentCapability registry every consumer above reads from
  domain/roadmap.ts      Planned-but-not-implemented future tools (metadata only, nothing callable)
  domain/financial.ts    Percentage/rounding helpers and maintenance defaults
  domain/oman/types.ts          Oman domain vocabulary (property types, furnished status, source types)
  domain/oman/locations.ts      Muscat area/wilayat normalization (English + Arabic aliases)
  domain/oman/comparables.ts    Comparable selection: size/bedroom/furnished tolerances, IQR outlier removal
  domain/oman/confidence.ts     Deterministic confidence scoring (never model-invented)
  domain/oman/dataProviders.ts  OmanPropertyDataProvider abstraction: Official (real NCSI context) + Listing (stub) + Manual + Database + Composite
  domain/oman/fixtures.ts       Curated/demo Muscat rental & sale benchmark dataset (clearly labeled, not live data)
  domain/oman/marketRepository.ts PropertyMarketRepository interface (engine-agnostic) + MemoryPropertyMarketRepository
  domain/oman/importPipeline.ts Validating CSV/JSON import pipeline for production market records
  domain/oman/dataQualityScore.ts Deterministic, non-LLM per-record data-quality scoring (Partner Data Feed)
  domain/oman/partners.ts       PropertyDataPartner/PartnerRepository abstraction + MemoryPartnerRepository (Partner Data Feed)
  domain/oman/cache.ts          ComparableCache interface + MemoryComparableCache (Redis-ready shape, not yet wired)
  domain/oman/config.ts         OMAN_PROPERTY_DATA_MODE / staleness threshold / cache TTL / NCSI / Partner Data Feed config readers
  domain/oman/officialContext.ts       OfficialMarketContext type + field-map-driven NCSI record adapter (never a hardcoded field name)
  domain/oman/officialContextCache.ts  OfficialMarketContextCache interface + MemoryOfficialMarketContextCache
  services/ncsi/ncsiClient.ts   NcsiClient: catalog discovery, dataset records, retries, timeout, normalized errors
  db/marketSchema.ts    PostgreSQL schema for property_market_records (independent migration ledger)
  db/marketStore.ts     PostgresPropertyMarketRepository: the production PropertyMarketRepository implementation
  db/partnerSchema.ts   PostgreSQL schema for data_partners + property_market_records partner columns (migration v2)
  db/partnerStore.ts    PostgresPartnerRepository: the production PartnerRepository implementation (shares its Pool with PostgresPropertyMarketRepository)
  api/marketDataRoutes.ts Partner Data Feed ingestion + internal status/health routes (never in the capability registry)
  middleware/partnerAuth.ts Per-partner bearer-token auth (X-Partner-Token) + internal shared-secret auth (X-Internal-Api-Key)
  marketImportCli.ts    `npm run market:import -- <file> [PARTNER_ID]` CLI entry point
  ncsiDiscoverCli.ts    `npm run ncsi:discover` CLI: lists NCSI's live catalog and flags real-estate-relevant datasets
  schemas/              Strict shared input and output contracts
  services/property.ts  Validated calculations shared by both transports
  services/omanProperty.ts Deterministic Oman property-analysis pipeline (analyze_oman_property)
  middleware/auth.ts    API-key authentication
  middleware/rateLimit.ts In-memory, IP-keyed rate limiter for public agent endpoints
  billing/catalog.ts    Indicative prices and injectable authorization boundary (single source of truth)
  billing/service.ts    BillingService: the only place a price is read; usage recording
  billing/usage.ts      UsageRepository abstraction (Console/Memory/Postgres implementations)
  billing/x402.ts       Real x402 payment middleware + always-on x402 info builder
  config/env.ts         Validated environment configuration
  utils/                Safe errors and metadata-only logging
  mcp/server.ts         MCP registration factory (shared by both transports)
  mcp/remote.ts         Remote Streamable HTTP MCP handler (/mcp) + /api/v1/mcp/status builder
  mcp.ts                Local stdio entry point
  server.ts             HTTP listener and shutdown
  calculators.ts        Compatibility re-exports

服务会验证直接调用和传输调用。新增功能必须加入共享目录,并提供输入/输出架构和服务函数;该目录为 REST、MCP 和 OpenAPI 提供驱动。HTTP 或 MCP 处理程序中不包含计算逻辑,计算服务中也不包含定价逻辑——每项价格都通过 BillingService 读取,而 BillingService 会读取 billing/catalog.ts。

功能注册表

src/domain/capabilities.ts 导出一个数组 capabilities: AgentCapability[],该数组是唯一记录工具名称、路径、描述、whenToUse/useCases(用于建议层的提示,说明哪种情况下应使用哪个工具)、输入/输出架构、示例、价格、货币、支付协议、幂等性和副作用标记的地方。以下所有使用方都会读取此数组,而不会各自维护一份:

  • REST 路由注册(api/app.ts)——包括 X-API-Key 和 x402 两组路由
  • OpenAPI 文档(api/openapi.ts)——operationId 使用功能自身的 name(analyze_property、compare_properties、estimate_maintenance、analyze_oman_property),请求/响应示例来自 capability.example/capability.execute(capability.example)
  • MCP 服务(mcp/server.ts)——工具名称、架构,以及由 capability.description + capability.whenToUse 构建的描述
  • x402 支付门控和 BillingService(billing/x402.ts、billing/service.ts)——billing/catalog.ts 的 prices 对象由 capabilities 派生(Object.fromEntries(capabilities.map(c => [c.name, c.price]))),绝不另行定义字面量
  • 所有 Agent 市场/发现端点(/api/v1/agent、/api/v1/pricing、/api/v1/tools、/api/v1/capabilities、/agent.json、两个 /.well-known/... 清单、/llms.txt)

将功能添加到这个数组后,它便会立即在所有地方生效,无需另行通知其他组件。src/domain/roadmap.ts 中有一个独立且规模小得多的未来工具列表(plannedCapabilities),包含 estimate_property_rent、analyze_lease、check_contract_risk、diagnose_maintenance_issue、estimate_repair_cost、generate_property_report。这些条目仅包含元数据:没有路由、MCP 注册、架构、价格,也不可调用。它们的作用是让 /agent.json 和 /llms.txt 能告知 Agent 即将推出的功能,同时避免 Agent 将列表中的名称误认为可用端点。要将其中一项变为实际功能,必须在 capabilities 中添加完整条目;这是唯一驱动实际行为的注册表(analyze_oman_property 在本阶段之前也曾列于此列表中;现在它已有完整条目,并已从 plannedCapabilities 中移除)。

REST API

REST 基础 URL:http://localhost:8787。

方法端点身份验证
GET/公开 — HTML 落地页(浏览器)或 JSON 发现信息(Accept: application/json)
GET/agent.json公开 — 完整 Agent 清单(协议、x402 条款、完整工具目录)
GET/.well-known/ai-plugin.json公开 — OpenAI 插件风格清单
GET/.well-known/agent.json公开 — A2A 风格 Agent Card
GET/llms.txt公开 — 面向基于 LLM 的 Agent 的纯文本简介(text/plain)
GET/api/v1/capabilities公开 — 机器优先的能力注册表(架构、定价、适用场景)
GET/api/v1/agent公开 — 旧版 Agent 市场元数据(已由 /agent.json 取代)
GET/api/v1/pricing公开 — 完整的按调用计费价格列表
GET/api/v1/tools公开 — 旧版工具目录(已由 /api/v1/capabilities 取代)
GET/api/v1/x402公开 — x402 协议/定价信息(始终可用,不受 X402_ENABLED 影响)
GET/api/v1/x402/status公开 — 实际运行状态:enabled、mode、network、asset、facilitator、walletConfigured、paymentEnforcement
POST/api/v1/property/analyzeX-API-Key
POST/api/v1/property/compareX-API-Key
POST/api/v1/maintenance/estimateX-API-Key
POST/api/v1/oman/property/analyzeX-API-Key
POST/api/v1/risk/company-reputation-checkX-API-Key(x402:/api/v1/x402/risk/company-reputation-check;L402:/api/v1/l402/risk/company-reputation-check)
POST/api/v1/risk/business-risk-scoreX-API-Key(x402:/api/v1/x402/risk/business-risk-score;L402:/api/v1/l402/risk/business-risk-score;MPP:/api/v1/mpp/charge/business_risk_score)
POST/api/v1/documents/facts-extractX-API-Key(x402:/api/v1/x402/documents/facts-extract;L402:/api/v1/l402/documents/facts-extract;MPP:/api/v1/mpp/charge/document_facts_extract)— JSON 请求体限制为 1 MB
POST/api/v1/finance/invoice-anomaly-checkX-API-Key(x402:/api/v1/x402/finance/invoice-anomaly-check;L402:/api/v1/l402/finance/invoice-anomaly-check;MPP:/api/v1/mpp/charge/invoice_anomaly_check)— JSON 请求体限制为 1 MB
POST/api/v1/automotive/vehicle-value-estimateX-API-Key(x402:/api/v1/x402/automotive/vehicle-value-estimate;L402:/api/v1/l402/automotive/vehicle-value-estimate;MPP:/api/v1/mpp/charge/vehicle_value_estimate)
POST/api/v1/intelligence/research-companyX-API-Key
POST/api/v1/intelligence/find-companiesX-API-Key
POST/api/v1/intelligence/analyze-company-riskX-API-Key
POST/api/v1/procurement/oman-supplier-checkX-API-Key
GET/api/v1/health公开存活检查
GET/openapi.json公开原始 OpenAPI 文档
POST/api/v1/market-data/importX-Partner-Token(合作伙伴数据馈送——基础设施,并非 Agent 能力;见下文)
GET/api/v1/internal/market-data/statusX-Internal-Api-Key(合作伙伴数据馈送;见下文)
GET/api/v1/internal/market-data/partnersX-Internal-Api-Key(合作伙伴数据馈送;见下文)

旧版 /v1/... POST 路由和 /health 仍可用。**响应迁移:**旧版路由现在返回与规范路由相同的封装;从 response.data 读取结果。未知字段会被拒绝,名称去除首尾空格后必须唯一,并且不再允许以未验证身份的状态启动。

成功:

{
  "success": true,
  "data": {},
  "meta": { "requestId": "server-generated-uuid" }
}

工具调用(四项 POST 能力,无论通过 API 密钥还是 x402 路由系列调用)还会在 meta 中补充工具名称、价格和货币,以便 Agent 无需再次查询即可确认收费金额:

{
  "success": true,
  "data": { "grossYield": 8.47 },
  "meta": { "requestId": "server-generated-uuid", "tool": "analyze_property", "price": 0.01, "currency": "USD" }
}

错误:

{
  "success": false,
  "error": {
    "code": "INVALID_INPUT",
    "message": "Input validation failed",
    "details": [{ "path": "propertyValue", "message": "Property value must be at least 0.01" }]
  },
  "meta": { "requestId": "server-generated-uuid" }
}

响应包含 X-Request-ID。状态码涵盖:400 表示验证失败/JSON 格式错误,401 表示身份验证失败,404 表示端点未知,413 表示请求正文超过 32kb,415 表示内容类型/编码不受支持,500 表示已清理的内部故障;429 则用于已配置的限流适配器。PostgreSQL 模式会强制执行客户的每分钟限额和每月配额;环境密钥开发模式默认不启用限流器。/openapi.json 会有意返回原始规范,供发现工具使用。

Agent 发现、定价和工具目录
curl -s https://api.rafidsystem.com/api/v1/agent
{
  "success": true,
  "data": {
    "name": "Rafid Intelligence Network",
    "description": "Property and facility intelligence tools for AI agents",
    "version": "0.1.0",
    "mcp": true,
    "docs": "/docs",
    "openapi": "/openapi.json",
    "health": "/api/v1/health",
    "pricing": "/api/v1/pricing",
    "tools": "/api/v1/tools",
    "x402": "/api/v1/x402",
    "x402Enabled": false,
    "endpoints": ["/api/v1/property/analyze", "/api/v1/property/compare", "/api/v1/maintenance/estimate", "/api/v1/oman/property/analyze"]
  },
  "meta": { "requestId": "..." }
}

GET /api/v1/pricing 返回 { "currency": "USD", "model": "pay-per-call", "tools": { "analyze_property": 0.01, "compare_properties": 0.03, "estimate_maintenance": 0.02, "analyze_oman_property": 0.25 } }——与 billing/catalog.ts 定义的对象完全相同,不会重新计算或重复定义。GET /api/v1/tools 会为每项能力返回一个条目,其中包含完整的输入 JSON Schema 和派生的输出摘要,使 Agent 无需阅读任何文档即可构造有效调用。

落地页

访问 / 的用户会看到一个轻量、无依赖的 HTML 页面(无需构建步骤,也不加载外部资源),页面以面向 Agent 的定位语开篇:“为 AI Agent 打造的房地产智能服务。发现。按次付费。执行。”页面包含“Agent 集成”部分,其中提供可复制的 MCP/x402/OpenAPI/manifest 示例、工具列表、定价,以及指向 /docs、/openapi.json 和 /api/v1/health 的链接。API 密钥/REST 访问被说明为兼容选项,而非主要信息。这纯粹是对 JSON 端点所返回相同数据的展示层;API 的行为不会因此改变。任何发送 Accept: application/json 的客户端(包括显式设置此标头的普通 fetch()/curl,或任何现有集成)都不受影响,仍会收到原本提供的 JSON 发现数据。

房产分析

PowerShell(请替换密钥占位符):

$headers = @{ 'X-API-Key' = '<your-api-key>' }
$body = @{ propertyValue = 85000; annualRent = 7200; serviceCharge = 650; maintenanceCost = 400 } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri http://localhost:8787/api/v1/property/analyze -Headers $headers -ContentType application/json -Body $body

返回的 data 包含 grossYield: 8.47、netYield: 7.24、annualOperatingCost: 1050 和 annualNetIncome: 6150。

可选输入:serviceCharge、maintenanceCost、otherAnnualCosts、vacancyRatePct(0–100)。接受旧版别名 maintenance,但不能与 maintenanceCost 同时提供。

计算公式:

  • 年毛收入 = annualRent。
  • 年有效租金 = annualRent × (1 − vacancyRatePct / 100)。
  • 运营成本 = serviceCharge + maintenanceCost + otherAnnualCosts。
  • 年净收入 = 年有效租金 − 运营成本。
  • 毛收益率 = annualRent / propertyValue × 100。
  • 净收益率 = 年净收入 / propertyValue × 100。
  • 简单回收期 = propertyValue / 年净收入;若收入不为正,或数值过大而无法安全表示,则为 null。

所有金额均以 OMR 计。使用 JavaScript 数值四舍五入到小数点后两位,保留 MVP 原有约定(并非结算账本)。有限输入金额上限为 1e12;房产价值必须至少为 0.01。净收入为负数也是有效值。保留旧版输出字段 grossYieldPct、netYieldPct 和 annualOperatingCosts。不包含融资、税费、交易费用和升值。

房产比较
$body = @{ properties = @(
  @{ name = 'A'; propertyValue = 85000; annualRent = 7200 },
  @{ name = 'B'; propertyValue = 100000; annualRent = 7000 }
) } | ConvertTo-Json -Depth 5
Invoke-RestMethod -Method Post -Uri http://localhost:8787/api/v1/property/compare -Headers $headers -ContentType application/json -Body $body

需要 2–20 套房产,名称必须唯一且非空(最多 120 个字符)。返回计算后的 properties 和按净收益率降序排列的 sortedByNetYield 名称;净收益率为四舍五入后的数值,相同则保留输入顺序。此排序仅用于指标比较,不构成投资建议。

维护费用估算
$body = @{ propertyValue = 100000; ageYears = 12; units = 1 } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri http://localhost:8787/api/v1/maintenance/estimate -Headers $headers -ContentType application/json -Body $body

此示例返回年维护费用 1300 OMR、月度储备金 108.33 OMR 和维护费用百分比 1.3,并附带 assumptionsUsed。

默认年费率:房龄低于 5 年 → 0.6%;5 至不足 10 年 → 0.9%;10 至不足 20 年 → 1.3%;20 年及以上 → 1.8%。默认值:房龄 0,单元数 1。每增加一个单元,年费增加 35 OMR。支持的房龄为 0–200 年,单元数为 1–10000。

annual estimate = propertyValue × annualRatePct / 100
                + (units − 1) × additionalUnitCost

可在每次请求中通过 "assumptions": { "annualRatePct": 1.5, "additionalUnitCost": 50 } 覆盖假设。返回的百分比包含额外单元费用。接受旧版 annualRent,但不会使用。尚未对房产类型和面积建模,因此会将它们作为未知字段拒绝。这样既保留了现有启发式方法,也避免臆造缺乏依据的本地成本因素;在用于商业估算前,需要先进行校准。

阿曼房产分析(马斯喀特)

analyze_oman_property(每次调用 $0.25)是 Rafid 首个价值源自本地市场证据、而不只是对调用方自有数据进行算术运算的功能。它通过结构化且有来源依据的证据回答“这处马斯喀特房产作为出租投资而言,定价是否合理?”,绝不会给出硬编码的“好投资”或“坏投资”结论。

$body = @{ governorate = 'Muscat'; area = 'Al Mouj'; propertyType = 'apartment'; bedrooms = 2; sizeSqm = 130; askingPriceOMR = 118000 } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri http://localhost:8787/api/v1/oman/property/analyze -Headers $headers -ContentType application/json -Body $body

必填项:governorate、area、propertyType(apartment | villa | townhouse)、sizeSqm、askingPriceOMR。可选项:wilayat、bedrooms、bathrooms、furnished(furnished | semi_furnished | unfurnished)、optionalAnnualServiceChargeOMR、optionalAnnualMaintenanceOMR。

**覆盖范围(MVP):**仅限马斯喀特省。支持的区域:Al Mouj、Muscat Hills、Qurum、Bausher、Azaiba、Al Khuwair、Madinat Al Irfan、Ghubrah(domain/oman/locations.ts 中的 MUSCAT_AREAS)。英文拼写和常见阿拉伯语拼写都会解析为相同的规范区域(例如,الموج / Al Mouj / the wave → Al Mouj)——刻意不采用模糊或语音匹配,因此未列出的地名会报告为 matchType: "unmatched",而不会进行猜测。

**可比房源筛选(domain/oman/comparables.ts):**绝不跨房产类型进行比较;按区域、房产类型和 ±20% 的面积容差(SIZE_TOLERANCE_PCT)筛选,以得出报告中的 comparableCount,然后在精确匹配卧室数量和家具配置的房源至少有 3 套(MIN_COMPARABLES)时,进一步缩小筛选范围;精确匹配的房源过少时,才会放宽到卧室数量相差 ±1,或接受“任意家具配置”(通过 bedroomToleranceApplied / furnishedFilterRelaxed 报告,并显示在 riskFlags / assumptions 中);超过 540 天的记录会在此之前直接作为过时数据排除;随后,标准的 1.5×IQR 规则会从剩余数据中剔除统计离群值(sampleSizeUsed)。年租金报价会先统一换算为月租金,再进行比较。

**置信度(domain/oman/confidence.ts):**采用固定且有文档说明的加权公式,根据样本量、数据新鲜度、与目标房产的面积和卧室数量相似度,以及价格离散程度(变异系数)计算——绝不是由模型臆造的数值。若使用的可比房源少于 MIN_COMPARABLES,置信度始终为 { score: 0, level: "insufficient" }。

**回退行为:**如果可比房源(租赁端)少于 MIN_COMPARABLES,或省份/区域不受支持,则返回 insufficientMarketData: true,unavailableOutputs 会准确列出被省略的字段(例如 market.estimatedMonthlyRentOMR、investment.grossYieldPct),而无需市场数据即可确定的数值(pricePosition.askingPricePerSqmOMR、investment.estimatedOperatingCostOMR)仍会照常计算。绝不会为了填补数据缺口而捏造任何内容。 Rafid 不会抓取房产网站,也绝不会以此方式获取数据。 OfficialOmanDataProvider 和 ListingDataProvider(domain/oman/dataProviders.ts)是为未来官方/授权数据源预留的真实接入点,目前返回空数据,而不是伪造或抓取数据。生产环境中的市场数据预计来自以下来源之一:政府官方/统计来源、获授权的房地产数据供应商、合作伙伴数据共享协议,或客户自己的房源/交易数据导出。所有这些数据都会以相同方式进入分析流水线——通过下方的导入流水线作为记录导入 property_market_records 表——因此,这个代码库中不存在、也不会计划加入任何抓取代码。

数据来源: 每个响应都包含一个 provenance 数组——{ sourceType, sourceName, sourceDate, recordCount }——以便调用方区分 official_statistics、listing_asking_price、partner_feed 和 manual_benchmark,并判断价格是挂牌价还是已确认的成交价。内置的 ManualDatasetProvider(domain/oman/fixtures.ts)是一套小型、明确整理的基准数据集,每个 sourceName 都清楚标注其为演示/MVP 数据——绝非官方统计数据,也绝非已确认的成交价。只有在某个回答所使用的可比数据中实际包含 manual_benchmark 记录时,riskFlags 才会包含 demo_dataset_not_live_market_data;该标记按响应计算,而非硬编码,因此完全基于数据库中的生产记录构建的回答(官方统计数据、获授权的房源数据源或合作伙伴数据源)不会带有此标记。

数据时效性: 每个响应都包含一个 dataQuality 区块——{ latestDataDate, dataFreshnessDays, sampleSize, sourceTypes, staleMarketData }——根据实际用于生成回答的可比数据计算,即使样本太少而无法得出完整结果也是如此(绝不会隐藏数据时效性)。当 dataFreshnessDays 超过 OMAN_MARKET_STALE_DAYS(默认值为 365 天)时,会设置 staleMarketData: true、添加 stale_market_data 风险标记,并在置信度公式(domain/oman/confidence.ts)中应用固定且有文档说明的惩罚值。因此,使用此 API 的 Agent 仅凭 dataQuality/confidence/riskFlags 就能判断回答的可信程度,无需检查原始记录。

输出结构:normalizedLocation、subjectProperty、market(预估月/年租金范围、可比数据/样本数量、数据时效性)、investment(毛/净收益率、运营成本、净收入)、pricePosition(挂牌单价与观测到的可比成交价格范围的比较,below_market/at_market/above_market/insufficient_data)、comparablesSummary(实际使用样本的单位面积租金最低值/中位数/最高值)、dataQuality、historicalSalesContext、riskFlags、confidence、provenance、assumptions、insufficientMarketData、unavailableOutputs。严格输出架构见 schemas/omanOutputs.ts。 历史销售背景: 每个响应还会包含 historicalSalesContext — { available, recordsAvailable, recentComparableSales, medianHistoricalPricePerSqmOMR, recentMedianPricePerSqmOMR, oldestRecordDate, latestRecordDate, sourceTypes, priceSemantics, phaseBreakdown } — 这是一个独立的附加字段,用于呈现长期已完成销售历史(例如,跨越 2006-2026 年的多年合作方数据流);它与上文的 market/pricePosition/comparablesSummary/confidence 并列,但绝不会与之混合。上述字段仍完全由当前可比房源池驱动(comparables.ts 的 540 天 MAX_DATA_AGE_DAYS 截止值保持不变)。recordsAvailable/oldestRecordDate/latestRecordDate/medianHistoricalPricePerSqmOMR 通过真实数据库聚合(PropertyMarketRepository.getHistoricalSaleStatistics())计算,而不是复用 findSaleComparables() 返回的、数量受限且按最新优先排列的候选池,因此即使历史记录超过该上限,统计结果仍然准确。recentComparableSales/recentMedianPricePerSqmOMR 是真正经过匹配的子集:在可配置的 OMAN_RECENT_SALES_DAYS 时间窗口内(默认 730 天),按区域、房产类型、面积相似度(±20%)以及可用时的卧室数进行匹配;由 comparables.ts 的 selectRecentSaleRecords() 计算。这是有意独立于 selectComparables() 的选择路径:不仅因为近期窗口不同,还因为已完成销售记录通常没有 furnished 状态记录(目前观察到的每条合作方数据流销售记录都是如此);而 selectComparables() 要求该字段,否则针对这些数据,近期销售数会被悄无声息地报告为零。phaseBreakdown(仅适用于 Al Mouj 这类分期开发项目的合作方数据流)按阶段列出 { phaseName, recordCount, medianPriceOMR, medianPricePerSqmOMR, oldestRecordDate, latestRecordDate },但只包含达到其他地方同样使用的 MIN_COMPARABLES 最低样本量要求的阶段;样本不足时绝不报告阶段趋势;最多列出最繁忙的 10 个阶段。如果地点不受支持,或当前提供方没有该地点真实的交易级记录存储(手动/演示模式、官方统计数据、尚未集成的房源数据流),则 available: false,且其他所有字段均为空默认值 — 绝不会将演示/基准数据冒充为真实历史销售情报。priceSemantics 只报告源数据本身记录的内容(例如 "contracted_unit_price")— 除非数据源确实支持该说法,否则绝不报告 verified_conveyance_price、government_registered_transaction 或 final_title_transfer_price。

Rafid Agent 情报:公司研究、发现与风险(research_company、find_companies、analyze_company_risk)

这是超越阿曼房产/商业数据、开展更广泛扩展的第 1 阶段:提供三项能力,通过公开网络搜集公司证据(身份识别还会使用 Rafid 自有的阿曼公司注册数据),而非仅根据调用方提供的数字进行计算。这三项能力默认都完全不启用 — 在操作人员明确配置提供方之前,不会发起外部网络调用,也不会产生费用;请参阅 src/intelligence/config.ts 和 .env.example 中的 WEB_SEARCH_PROVIDER/TAVILY_API_KEY/INTELLIGENCE_LLM_PROVIDER/ANTHROPIC_API_KEY/INTELLIGENCE_LLM_MODEL/RISK_LIVE_CHECKS_ENABLED/INTELLIGENCE_CACHE_TTL_MS/RISK_CACHE_TTL_MS 变量块。若未配置任何内容,这些能力仍都会返回结构完全符合 schema、内容真实的 dataMode: "not_configured" 结果 — 仅返回空值/null 结构,绝不伪造数据。这也使它们能够安全地通过通用的逐项能力测试循环进行测试(tests/http.test.ts/tests/mcp.test.ts/tests/x402.test.ts;这些测试会调用 capability.execute(capability.example),且无法注入模拟网络依赖)。

  • research_company(POST /api/v1/intelligence/research-company,$0.15/次调用)— 根据公开网络搜索结果(src/intelligence/webSearch/)生成结构化研究简报(概况、产品/服务、领导层、融资、竞争对手、技术信号、近期动态、风险标记),还可选择通过 LLM 综合步骤(src/intelligence/synthesis/)提取结构化字段 — 返回前会对每个综合字段进行架构验证,证据不支持的事实将返回 null/空值,而不是猜测。每个响应都包含 sources、confidence、dataFreshness 和 limitations;缓存依据标准化后的公司/网站/国家/深度/关注领域(src/intelligence/companyResearch/provider.ts)。
  • find_companies(POST /api/v1/intelligence/find-companies,$0.05/次调用)— 根据符合行业/地点/规模/关键词条件的公开网络搜索结果,发现候选公司。绝不会为了填满请求的数量限制而捏造公司:如果未配置 LLM 综合提供方,则不会提取任何结构化公司记录(仅返回带引用的原始来源),以免冒险编造。无论请求的数量限制是多少(最高 100),结果在内部都限制为每次请求最多 20 条;响应中的 requestedLimit 与 appliedLimit 会明确说明这一差异(src/intelligence/companyDiscovery/provider.ts)。
  • analyze_company_risk(POST /api/v1/intelligence/analyze-company-risk,$0.35/次调用)— 收集涵盖最多 8 项检查(corporate_identity、domain、website、sanctions、adverse_news、security_signals、reputation、legal_signals)的分级风险信号,且绝不会给出安全/不安全的判定 — 只提供证据,每项都明确标记为 confirmed_evidence / public_allegation / automated_indicator / missing_information,由调用该工具的 Agent 或人工进行权衡。corporate_identity 直接复用 search_oman_company 自身的服务函数(不另行实现查询),也是默认执行的唯一检查;domain/website/sanctions 是免费的公开来源检查(RDAP、公开的 OFAC/Consolidated Screening List),由于会发起真实的外部调用,因此受 RISK_LIVE_CHECKS_ENABLED 控制;adverse_news/reputation/legal_signals 基于网络搜索,需要网络搜索提供方。website 检查原样复用 domain/oman/feedSecurity.ts 和 safeFeedFetch.ts 的 SSRF 防护(阻止私有/环回 IP、抵御 DNS 重绑定的解析、验证重定向)— Production Feed Runner 依赖这些防护来处理合作伙伴提供的 URL(src/intelligence/risk/provider.ts、src/intelligence/risk/checks/)。

成本/利润率可见性(仅限内部): src/intelligence/costEstimator.ts 保存了 Rafid 针对每项功能记录的、人工估算的上游成本假设(绝非提供方实际的机密定价),以及每个进程中实际发起的提供方调用次数累计值;GET /api/v1/internal/revenue/unit-economics(使用与其余 revenueRoutes.ts 相同的 REVENUE_INTERNAL_API_KEY 限制 — 不需要新密钥,也不新增仪表板)会按定价功能公开 revenuePerCallUSD/estimatedProviderCostPerCallUSD/estimatedLlmCostPerCallUSD/estimatedGrossMarginPerCallUSD。这是毛利率,而非净利率 — 不包括托管、数据库和 facilitator/网络费用,且绝不会通过任何公开端点公开。

提供方架构: WebSearchProvider(src/intelligence/webSearch/provider.ts)和 IntelligenceSynthesizer(src/intelligence/synthesis/synthesizer.ts)都是精简接口,各自带有一个 NotConfigured* 默认实现和一个实际实现(分别使用 Tavily 进行搜索、使用 Anthropic 进行综合)— 这与 business-data/sources/provider.ts 自身的提供方接口模式相呼应 — 因此,日后更换任一供应商只需编写一个新类,无需触及功能的架构、路由、MCP 注册或定价。

采购:阿曼供应商筛查(oman_supplier_check,$0.50/次调用)

POST /api/v1/procurement/oman-supplier-check(API key)· POST /api/v1/x402/procurement/oman-supplier-check(x402,$0.50)· MCP 工具 oman_supplier_check。适用于需要在请求报价前筛查阿曼供应商的采购 Agent。这是基于公开来源的采购筛查,不是 KYC/AML、法律核验或供应商审批;因此,结果会给出 identityConfirmed、procurementSuitability(appears_suitable / review_recommended / insufficient_information / potential_risk)和制裁 potential_match,绝不会称其为“已核实”“已批准”或“已制裁”。

输入:必填的 companyName,以及可选的 crNumber、website、email、phone、address、requiredProductOrService。输入会经过确定性规范化处理(src/supplier-check/normalize.ts):使用注册机构自身的公司名称规范化器、小型阿拉伯语→英语公司词汇表(ش.م.م → LLC、للتجارة → TRADING 等)、将阿曼电话号码转换为 E.164、处理 URL/域名(包括 co.om 风格的可注册域名)、检测免费邮箱并清理 CR。

各项检查彼此独立地进行评估(src/supplier-check/analysis/):公司身份(强/中/弱/未确认,根据名称匹配 + CR + 联系方式佐证 × 来源可信度评分——演示数据永远无法达到“已确认”);业务活动相关性(确定性的英/阿关键词分类体系;通过/部分通过/失败/未知);网站(以防 SSRF 的方式抓取提供的网站或注册机构网站:网站上的名称、域名与名称/注册信息是否相符、企业邮箱、电话、地址、重定向);联系方式一致性(邮箱/网站/电话与注册信息及网站是否相符;检测仿冒域名;单独出现 Gmail/Outlook 地址时只会标记为低级提示);地址一致性(与注册信息中的省/州、威拉亚特、区域对照:通过/部分通过/冲突/未知);制裁(基于数据提供方:联合国安理会综合名单和美国综合筛查名单,包括 OFAC SDN,使用保守的中央模糊匹配器——相似度 ≥0.88 且显著词元重合度 ≥60%;即便名称完全匹配,也只标记为 potential_match);以及公共风险信号(网络搜索结果必须在风险词旁提及该供应商;从仿冒域名/CR 矛盾指标推导;采用中性的“检测到潜在公共风险信号”措辞)。

风险模型(src/supplier-check/scoring.ts,在代码中有完整文档说明,每次调用都会在 riskModel.components 中返回):不利证据会增加分数(例如,制裁名称完全匹配 +60、CR 注册给另一家公司 +35、制裁模糊匹配 +35、仿冒域名 +25、业务活动不匹配 +15、邮箱域名不匹配 +10);缺失信息不会增加风险分,而是降低 confidence(按权重计算证据覆盖率)。分数 ≥45 → high,≥20 → medium,否则 → low;证据覆盖率不足时,结果为 insufficient_data,除非分数已达到高风险。

**数据源与复用:**身份信息通过现有的阿曼公司规范注册库查询(getOmanCompanyDataProvider()——使用与 search_oman_company 相同的 OMAN_BUSINESS_DATA_MODE 提供方、rankCompanies、mergeCompanyRows 和溯源信息)。不会创建公司记录。网站和制裁检查复用 RISK_LIVE_CHECKS_ENABLED;公共网络信号复用 WEB_SEARCH_PROVIDER。如果未启用任何相关功能,这些检查会如实报告为 not_checked。

**缓存与幂等性:**每个提供方的证据分别缓存在 rafid_supplier_evidence 中(设置 SUPPLIER_EVIDENCE_DATABASE_URL/DATABASE_URL 时使用 PostgreSQL——执行一次 CREATE TABLE IF NOT EXISTS,并按 (provider_id, evidence_key) 执行 upsert;否则使用内存缓存)。默认 TTL:身份 7 天、网站 3 天、制裁 12 小时、公共网络 1 天(可通过 SUPPLIER_*_CACHE_TTL_MS 覆盖)。只缓存成功的证据(服务中断时会重试),且最终风险结果绝不缓存——每次调用都会根据证据重新计算。这是证据缓存,不是第二套公司/来源证据模型:缓存内容绝不会合并到 oman_companies 中。

风险情报:全球公司信誉核查(company_reputation_check,$0.40/次调用)

POST /api/v1/risk/company-reputation-check(API key)· POST /api/v1/x402/risk/company-reputation-check(x402,$0.40)· POST /api/v1/l402/risk/company-reputation-check(L402,启用时)· MCP 工具 company_reputation_check。类别为 risk_intelligence,支持幂等。适用于需要在签订合同、引入供应商、汇款、建立合作关系、投资或批准 marketplace 卖家之前,调查任何国家公司的 Agent。该工具以证据为先:每个信号都引用证据 ID,每项证据都包含来源、权威等级、检索时间和相关性。它绝不会将公司标记为“安全”“合法”“欺诈”或“受制裁”。它不能作为法律、KYC/AML、信用或合规判定,也不保证公司的合法性。

输入:companyName(必填);强烈建议提供 country(ISO-2/ISO-3/英文名称);可选字段:website、domain、registrationNumber、lei(经过校验和验证)、legalName、city、industry。严格的架构校验:未知字段、未知国家、无效的 LEI,以及不匹配的 website/domain 字段对,都会被拒绝并返回 400 INVALID_INPUT——这也意味着不会进行 x402 结算,也不会消耗 L402 令牌。

curl -X POST http://localhost:8787/api/v1/risk/company-reputation-check \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"companyName":"Example Technologies Ltd","country":"United Kingdom","website":"https://example.com"}'

输出(节选):company、resolution(状态 resolved|probable|ambiguous|unresolved|registry_not_checked、置信度、候选项)、reputationScore(0-100)、confidenceScore(0-100)、trustLevel(insufficient_evidence / significant_concerns / some_concerns / no_major_concerns_found / favorable_public_signals)、各维度的 scores,以及 scoreBreakdown(权重、原始分数、覆盖率)和 confidenceBreakdown、identity、sanctions(可能匹配与高置信度匹配、已检查/不可用的列表)、adverseMedia(包含 category、legalStage、established、覆盖数量和来源的事件)、customerSentiment、onlinePresence、legalRiskSignals、businessStabilitySignals、cyberDomainSignals、transparencySignals、positiveSignals、redFlags、evidenceSummary、evidence[]、coverage.providers[](各提供方的状态,包括 not_configured / timeout / stale_cache)、warnings[](COMPANY_NOT_RESOLVED、AMBIGUOUS_COMPANY、INSUFFICIENT_EVIDENCE、PROVIDER_UNAVAILABLE、PROVIDER_TIMEOUT、RATE_LIMITED、DATA_STALE、PROVIDER_NOT_CONFIGURED、COUNTRY_NOT_PROVIDED)、limitations、summary、methodology。注册表中记录的示例(/api/v1/capabilities,OpenAPI)是针对明确标注为合成数据的场景(虚构公司、.example 发布方)运行真实流程的结果——可通过 node --import tsx scripts/generateCompanyReputationExample.ts 重新生成。

流程(src/company-reputation/):输入校验 → 规范化(normalization.ts:全球法律形式、ISO 国家代码、域名、URL、注册号、LEI)→ 并发运行提供方,彼此隔离并设定时间限制(providers/runner.ts:每个提供方都设有超时;仅对免费的幂等来源重试一次,不重试付费搜索),并优先查缓存、结合混合查询 → 证据去重和联合发布分组(deduplication.ts)→ 实体解析(companyResolver.ts)→ 第三方条目的相关性筛选 → 分析器(analyzers/*:身份、制裁、不利媒体、客户情绪、在线形象、业务稳定性、网络/域名、透明度)生成信号 → scoring.ts → confidence.ts。提供方只收集规范化证据;分析器不会获取数据;评分仅处理信号。 评分(config.ts,版本 crc-1.0.0):身份权重为 20%,法律/监管为 20%,负面媒体为 15%,客户口碑为 15%,线上影响力为 10%,业务稳定性为 10%,网络安全/域名为 5%,透明度为 5%。每个维度的信号分数 = 严重程度分数(info 3、low 8、medium 20、high 40、critical 80)× 强度(权威性 × 相关性 × 有界佐证),并采用饱和函数(swing × (1 − e^(−points/40))),避免大量弱项线性累加;单个弱项对维度分数的影响只有几个点。随后,每个维度都会根据证据覆盖率向中性先验值 50 收缩:score = 50 + coverage × (raw − 50)。因此,缺失数据会显示为“未知”(50)且置信度较低,绝不会显示为“良好”。已记录的封顶规则:高置信度制裁名单匹配会将分数封顶为 20;官方或主要来源确认的欺诈、腐败、制裁违规或犯罪行为定罪/判决,会将分数封顶为 35。

**置信度(与声誉分开计算):**实体解析置信度占 30%,加权维度覆盖率占 20%,独立来源(按事件组计,而非文章数)占 15%,来源权威性占 10%,时效性(陈旧缓存和较早的报道权重较低)占 10%,证据多样性占 10%,司法辖区注册信息覆盖率占 5%;每次提供方故障扣 3 分;身份存在歧义时最高为 40,身份无法解析时最高为 50。78/91 和 78/34 含义截然不同。

**安全属性(已测试):**名称相似的受制裁对象不会被判定为匹配(使用 src/shared/sanctions/ 中共享的保守匹配器;相似度 ≥0.88 且词元重合率 ≥60%);如果没有国家/注册标识符佐证,仅名称完全匹配也只会标记为 possible;单条匿名投诉不会造成重大扣分;十篇转载内容会被视为同一事件;数据缺失和提供方超时绝不会被视为负面信号;指控仍会作为指控处理(法律阶段仅依据来源中的明确措辞判定);会排除其他司法辖区中同名的公司(文本中的国家、法律形式不同),身份存在歧义时,仅归属有标识符佐证的条目。外部文本会经过清理,并移除类似指令的文本;不会向大模型发送任何内容。

提供方:

提供方类别启用条件费用
GLEIF 全球 LEI 索引(全球,官方)注册信息RISK_LIVE_CHECKS_ENABLED=true免费
UK Companies House注册信息(仅 GB)COMPANIES_HOUSE_API_KEY免费密钥
Rafid Oman 公司注册信息注册信息(仅 OM)OMAN_BUSINESS_DATA_MODE=database/composite(绝不使用演示数据)自有数据库
UN Security Council Consolidated List制裁名单RISK_LIVE_CHECKS_ENABLED=true免费
US Consolidated Screening List(含 OFAC SDN)制裁名单RISK_LIVE_CHECKS_ENABLED=true(可选 SANCTIONS_CSL_API_KEY)免费
EU Financial Sanctions Files制裁名单EU_SANCTIONS_LIST_URL(由 EU 签发的令牌化 URL)免费
OpenSanctions(UN/EU/UK/US 及国家级名单)制裁名单OPENSANCTIONS_API_KEY商业用途需付费许可
新闻/负面媒体(搜索 2 次)新闻WEB_SEARCH_PROVIDER=tavily + TAVILY_API_KEY约 $0.016
评价平台/论坛(搜索 1 次)评价与新闻相同约 $0.008
公司网站(1 个页面,SSRF 安全)网站RISK_LIVE_CHECKS_ENABLED=true免费
RDAP 域名注册信息域名RISK_LIVE_CHECKS_ENABLED=true免费

任何提供方都可以通过 COMPANY_REPUTATION_DISABLED_PROVIDERS(提供方 ID 或类别)关闭。即使没有启用任何提供方,调用仍会返回有效但明确受限的结果(trustLevel: insufficient_evidence)。 **缓存:**提供方证据按 (provider, normalized company identity) 缓存在 rafid_company_evidence_cache 中(通过 COMPANY_REPUTATION_DATABASE_URL 或 DATABASE_URL 使用 PostgreSQL,并执行一次 CREATE TABLE IF NOT EXISTS;否则使用内存缓存)。TTL:注册信息 7d、制裁信息 12h、新闻 1d、评价 3d、网站 3d、域名 7d(COMPANY_REPUTATION_*_TTL_MS)。缓存证据会保留其原始检索时间(fetchedAt/observedAt,fromCache: true)。刷新失败时,最长不超过 COMPANY_REPUTATION_MAX_STALE_MS(默认 14 天)的过期证据仍会返回,并标记为 stale_cache + DATA_STALE,同时降低置信度。服务中断永不缓存,最终评分也永不缓存。

**单位经济效益:**最坏情况下,未命中缓存时约需 3 次网络搜索(按 Tavily 级别的定价约为 $0.024);其他所有内容均来自免费公共数据。按每次 $0.40 估算,扣除托管和 facilitator 费用前的毛利约为 $0.37/次。若启用 OpenSanctions,则会产生其自身的按次许可费用。每个提供方的调用、缓存命中、过期数据返回、服务中断和延迟都会在进程内记录(src/company-reputation/telemetry.ts),并通过内部 GET /api/v1/internal/revenue/unit-economics 路由返回(companyReputationTelemetry)。

高级决策 API:company_due_diligence($1.50/次)

POST /api/v1/risk/company-due-diligence · MCP 工具 company_due_diligence。这是基于现有 business_risk_score 证据管道构建的标准化决策层:它会解析公司,评估注册信息、网站身份、制裁、不利新闻、法律、财务和声誉信号,然后返回确定性的 riskScore/riskLevel、置信度、来源信息、建议以及可供机器执行的 decision.action。

请求字段包括必填的 company,以及可选的 domain、country、registrationNumber、lei、purpose、depth(quick、standard、enhanced,默认 standard)和严格限定的 checks 对象(registry、sanctions、adverseMedia、reputation、businessRisk,均默认为 true)。此项能力收费 $1.50 USD,并使用共享的注册表/支付通道;在启用的情况下,它会自动出现在 REST、MCP、x402/L402/MPP/账户计费、OpenAPI 和发现清单中。

示例:

curl -X POST https://<host>/api/v1/risk/company-due-diligence \
  -H "Content-Type: application/json" -H "X-API-Key: <key>" \
  -d '{"company":"Example Ltd","domain":"example.com","country":"GB","purpose":"supplier_onboarding"}'

免费预览仅报告识别出的输入、来源覆盖情况和可用的结果部分。它绝不会返回付费评分、制裁结果、风险标记、建议或决策。如果实体存在歧义或无法可靠解析,付费请求会返回 AMBIGUOUS_ENTITY 或 ENTITY_NOT_FOUND,而不是猜测;提供方不可用会降低覆盖率/置信度,并在结果中明确说明。

风险情报:全球商业风险评分(business_risk_score,$0.50/次)
business_risk_score
$0.50 / call
Global business due-diligence and risk assessment

POST /api/v1/risk/business-risk-score(API 密钥) · POST /api/v1/x402/risk/business-risk-score(x402,$0.50 = 500000 USDC 原子单位) · POST /api/v1/l402/risk/business-risk-score(启用时使用 L402) · POST /api/v1/mpp/charge/business_risk_score 和 MPP 会话调用(启用时使用) · MCP 工具 business_risk_score(远程 /mcp、stdio,以及启用时的付费 /mcp/mpp)。类别 risk_intelligence,支持幂等。为采购、财务、供应商入驻、B2B 销售、市场平台、保险、贷款、合规和自主采购 Agent 解答一个问题:与这家公司开展业务是否存在风险?——覆盖全球任何地区,依据证据而非模型知识作出判断。

Agent:
"Check this supplier before I pay their invoice."

↓

business_risk_score
$0.50

↓

Returns:
- risk score (0–100, 100 = highest detected risk) and risk level
- confidence (0–1, separate from risk)
- company identity (entity match confidence, registry status)
- corporate risk
- financial risk
- sanctions / compliance risk (with match strength)
- reputation risk
- operational risk
- digital risk
- evidence (source, URL, observed/retrieved dates, reliability, claim)
- positive signals
- risk flags (each traceable to evidence ids)
- recommended due-diligence action

输入(严格校验;未知字段 → 400 INVALID_INPUT,不收费):companyName(必填),以及可选的 country(ISO 3166 alpha-2/alpha-3/英文名称——强烈建议提供)、registrationNumber、lei、website、address、city、industry、knownAliases(≤ 5)、includeNews(默认 true)、includeDigitalSignals(默认 true)。名称按其核心特征部分进行规范化(法律实体形式单独比较);网站规范化为 https:// 源站 + 可注册域名;国家规范化为 ISO-2。

curl -X POST http://localhost:8787/api/v1/risk/business-risk-score \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"companyName":"Example Trading Ltd","country":"GB","website":"https://example.com"}'

输出: status(assessed | insufficient_data)、assessmentId、business(name、legalName、country、registrationNumber、registrationStatus、incorporationDate、registry、website、entityMatchConfidence、resolutionStatus)、riskScore、riskLevel(0–20 低 · 21–40 中等 · 41–60 较高 · 61–80 高 · 81–100 严重)、confidence、components(企业/财务/合规/声誉/运营/数字风险;null = 无数据覆盖)、scoreBreakdown(各类别的分值贡献、有效权重、应用的下限)、riskFlags[] 和 positiveSignals[](code、category、severity、weight、confidence、points、evidenceIds、factStatus observed|reported|alleged、requiresVerification)、sanctionsScreening(已检查/不可用的名单、筛查的名称、匹配项及其 matchStrength possible|high 和名单类型 sanctions vs export-control/debarment)、evidence[](type、evidenceClass — official_record / regulatory_action / court_record / credible_journalism / other_publication / user_generated / allegation / self_published — sourceName、sourceUrl、publishedAt、observedAt、retrievedAt、ageDays、stale、freshness、claim、reliability、relevance、fromCache;lookup_result 条目记录无异常的检查结果)、recommendation(proceed / proceed_with_monitoring / enhanced_due_diligence / manual_review / avoid_automated_transaction + 原因代码)、dataCoverage、confidenceBreakdown、providers[]、warnings[]、assumptions[]、limitations[]、methodology、evaluatedAt。文档中的示例(OpenAPI、/api/v1/capabilities、x402 Bazaar 元数据)使用的是明确标注为合成的场景,运行 node --import tsx scripts/generateBusinessRiskExample.ts 可重新生成(示例偏离时测试会失败)。

结构化错误(x402/L402/MPP 不收费——仅成功调用会结算): AMBIGUOUS_ENTITY 409(details.candidates 含注册编号——使用其中一个重试)、ENTITY_NOT_FOUND 404(该司法管辖区的注册机构没有这家公司,且没有其他信息表明其存在)、PROVIDER_TIMEOUT 504 / PROVIDER_UNAVAILABLE 503 / RATE_LIMITED 429(所有身份信息源均失败——无法执行最低限度的身份检查)、INVALID_INPUT 400、INTERNAL_ERROR 500。INSUFFICIENT_DATA 是 200 结果(status: "insufficient_data",分数为 null),仅当部署完全没有该公司的证据来源时才会返回。

流程(src/business-risk/):规范化 → 阶段 1(注册机构 GLEIF / UK Companies House / Rafid Oman registry、公司网站、RDAP——并发执行、相互隔离、设定时限、优先使用缓存)→ 实体解析(来自 company_reputation_check 的 companyResolver.ts:注册编号/LEI 具有决定性,其他国家的公司绝不匹配,相近候选项会判定为不明确——对于实体不明确/未知的情况,会跳过深度评分和所有付费搜索)→ 使用已解析标识符进行 阶段 2(对制裁及受限方名单进行筛查,覆盖名称、提供的别名和注册机构中的曾用名;新闻;评价;监管机构发布的信息;Companies House 备案状态;Google Safe Browsing)→ 去重 + 相关性筛选(排除其他司法管辖区的同名公司,绝不合并)→ 六个检测器(signals.ts)→ scoring.ts → confidence.ts → recommendation.ts。流程复用了 company_reputation_check 的提供方契约、运行器、证据缓存、规范化工具、实体解析器以及制裁/负面媒体分类器;仅风险模型为新增内容。 评分(config.ts,版本 brs-1.0.0,每个响应的 methodology 都会回显):每个信号的贡献值为 SEVERITY_POINTS[severity] (info 0 · low 8 · medium 20 · high 40 · critical 70) × rule weight × confidence。类别评分 = baseline − baseline·(1−e^(−positive/30)) + (100−baseline)·(1−e^(−negative/45))——正向信号只会侵蚀剩余基线,绝不会抵消风险警示;没有覆盖数据的类别为 null。总体评分 = 有覆盖类别的加权平均值,权重为 corporate 20% · financial 20% · compliance 25% · reputation 15% · operational 10% · digital 10%(按比例重新归一化;可用 BUSINESS_RISK_CATEGORY_WEIGHTS 覆盖);当存在高置信度的严重信号时,评分会上调至已记录的下限(高置信度制裁名单匹配 ≥ 85,公司已解散或被列入恶意软件/网络钓鱼名单 ≥ 81,破产程序或受限方匹配 ≥ 70,任何 critical 信号 ≥ 61,任何 high 信号 ≥ 35),并限制在 0–100 之间。缺失数据只会降低置信度和覆盖度;只有当缺失与请求相矛盾时,才会提高风险(公司不在其司法管辖区的登记册中、未找到提供的注册号、提供的网站无法访问、提供的域名未注册、可访问的网站没有联系信息)——详见 methodology.missingDataRiskRules。评分过程中的任何环节都不会使用大模型。

置信度(独立于风险):实体匹配置信度占 30%,加权类别覆盖度占 25%,来源可靠性占 15%,权威登记记录占 10%,证据新鲜度占 10%(媒体证据会按半衰期衰减;过期缓存 ×0.5),一致性占 10%(每处矛盾 −0.25);若身份未经登记册验证,最高为 0.55。

提供方:

提供方角色启用条件费用
GLEIF Global LEI IndexcorporateRISK_LIVE_CHECKS_ENABLED=true免费
UK Companies House(档案 + 申报状态)corporate + financialCOMPANIES_HOUSE_API_KEY免费密钥
Rafid Oman 公司登记册corporateOMAN_BUSINESS_DATA_MODE=database/composite自有数据库
UN Consolidated List、US CSL(OFAC SDN + BIS/DDTC 出口管制与禁业名单)sanctionsRISK_LIVE_CHECKS_ENABLED=true免费
EU FSF / OpenSanctionssanctionsEU_SANCTIONS_LIST_URL / OPENSANCTIONS_API_KEY免费 / 付费许可
新闻 + 评论平台搜索(3 次搜索)newsWEB_SEARCH_PROVIDER=tavily + TAVILY_API_KEY~$0.024
监管机构/执法机构公告(1 次搜索,仅限监管机构域名)regulatory同上~$0.008
公司网站(SSRF 安全防护)、RDAPdigitalRISK_LIVE_CHECKS_ENABLED=true免费
Google Safe BrowsingdigitalGOOGLE_SAFE_BROWSING_API_KEY(+ RISK_LIVE_CHECKS_ENABLED=true)免费密钥

可通过 BUSINESS_RISK_DISABLED_PROVIDERS 关闭任何提供方或角色(可填写 ID 或角色,例如 news,regulatory)。

**缓存与持久化:**提供方证据使用与 company_reputation_check 相同的证据缓存(rafid_company_evidence_cache;相同的提供方 ID ⇒ 证据只获取一次并共享;TTL:登记信息 7d、制裁名单 12h、新闻 1d、评论 3d、网站 3d、域名 7d;键包含规范化后的身份信息,因此不同国家的同名公司绝不会共享记录;刷新失败时可能会返回标记为过期的证据)。每次评估都会根据证据重新计算(确定性计算),并尽力持久化到 rafid_business_risk_assessments(通过 BUSINESS_RISK_DATABASE_URL 或 DATABASE_URL 连接 PostgreSQL,首次使用时创建;否则使用内存;BUSINESS_RISK_ASSESSMENT_STORE=memory|off),并按(身份信息、评分版本、证据指纹)去重——这就是追溯“为什么这家公司得分为 67?”的审计记录。

**单次调用成本:**未命中缓存时的最坏情况为 4 次网络搜索,约 $0.032;其他部分均免费。按 $0.50 计,预计毛利约为每次调用 $0.47,尚未扣除托管和支付中介费用(内部接口 GET /api/v1/internal/revenue/unit-economics)。 x402 挑战大小: x402 Bazaar 发现声明嵌入在 PAYMENT-REQUIRED 标头中;现在对声明的大小设有限制(MAX_DISCOVERY_DECLARATION_CHARS,必要时先删除输出示例,再删除输出架构),确保每个 402 对基于 Node 的 x402 客户端都清晰可读(标头限制为 16 KB)。完整架构和示例仍可在 /openapi.json 中查看。

文档智能:有证据支持的文档事实(document_facts_extract,每次调用 $0.25)
document_facts_extract
$0.25 / call
Structured, verifiable facts from business documents (any country)

POST /api/v1/documents/facts-extract(API key)· POST /api/v1/x402/documents/facts-extract(x402,$0.25 = 250000 USDC 原子单位)· POST /api/v1/l402/documents/facts-extract(启用时可用)· POST /api/v1/mpp/charge/document_facts_extract 和 MPP 会话调用(启用时可用)· MCP 工具 document_facts_extract。类别为 document_intelligence,支持幂等操作。将合同、发票、采购订单、报价单、招标文件/RFP、租约、政策、财务报告、法律文件、简历和公司简介转换为其他 Agent 可据以采取行动的事实——而非摘要:每条事实都附带 0–1 的提取置信度和来源证据。

输入(严格校验;documentUrl / text 必须且只能提供其中一项):documentUrl(公开的 https;带文本层的 PDF、DOCX、HTML、文本、Markdown、CSV)或 text(≤ 200,000 个字符;使用 \f 分隔页面,以提供页面级证据),可选的 documentType(默认为 auto,或 contract | invoice | purchase_order | quotation | tender | policy | financial_report | legal_document | lease | resume | company_profile | other)、requestedFacts(≤ 20 个自然语言问题)、mode(auto | requested_only)、language(ISO 639)、includeSourceEvidence(默认为 true)。限制:25 页——超出限制的文档将被拒绝(DOCUMENT_TOO_LARGE),绝不会截断。

curl -X POST http://localhost:8787/api/v1/documents/facts-extract \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"documentUrl":"https://example.com/supplier-contract.pdf","requestedFacts":["contract expiry date","payment terms","termination notice period"]}'

输出: documentType / documentSubtype / documentTypeConfidence、language、title、facts[](key、label、value、normalizedValue、confidence、method = labeled_field | definition | pattern | clause | table | llm_verified、sourceEvidence)、requestedFacts[](每个请求对应一项,状态为 found / not_found——会报告未找到的内容,绝不会根据模型知识补全)、entities[]、dates[]、amounts[]、percentages[]、obligations[](当事方、义务、must/must_not、截止日期、条件)、requirements[](类别、是否必需)、deadlines[]、riskFlags[]、metadata(页数、字符数、格式、来源、页面来源信息、提取模式/方法、LLM 辅助状态)、warnings[]、overallConfidence、limitations[]。sourceEvidence = { page?, section?, text, startOffset, endOffset }——简短的逐字摘录及其在提取文本中的偏移量;仅当来源具有实际页面时才会出现 page(PDF 页面、由 \f 分隔的文本、DOCX 渲染的分页符)。

标准化(仅在含义明确时执行;始终返回原始值):日期 → ISO 8601(支持英文/法文/西班牙文/德文/葡萄牙文/意大利文/阿拉伯文月份名称;只有文档本身能确定日期顺序时,才解析数字格式的 dd/mm 与 mm/dd);金额 → {amount, currency, frequency}(ISO 代码、含义明确的符号、货币名称,包括阿拉伯语;$、¥、未注明币种的 "dollars" 不会设置 currency,而会通过 currencySymbol 返回;支持 OMR/KWD/BHD 等三位小数货币);百分比 → 比率;时长 → {value, unit, iso8601}。不会执行计算、换算或年化。 **按文档类型识别的事实:**合同(当事方、生效/到期日期、期限、金额、付款条款、续约、终止、通知期限、违约金、SLA、适用法律、责任、保证)·发票(供应商、客户、编号、日期、小计、税额、总额、货币、付款详情、按数量 × 单价核验的行项目)·采购订单·报价单·招标文件(发布方、编号、提交截止日期、资格/技术/财务要求、投标/履约保函、必备文件、评审标准)·租赁协议(房东、租户、房产、单元、日期、租金 + 频率、押金、续约、通知)·政策·财务报告·法律文件·简历·公司简介·通用。

风险标记是可观察到的情况,其严重程度由规则定义(src/document-facts/extract/risk.ts),绝非法律结论,例如 automatic_renewal、short_termination_notice、missing_expiry_date、missing_currency、contradictory_amounts、totals_do_not_reconcile、penalty_clause_detected、unlimited_liability_language、missing_signature、ambiguous_payment_terms、embedded_instructions_detected、macros_present、hidden_html_content_removed。

**安全性——文档是不可信数据:**仅下载一次且防范 SSRF(仅限 https;拒绝访问私有/环回/链路本地/元数据目标,以及重定向到这些目标的请求——使用共享的 safeFeedFetch),并限制大小和耗时;绝不获取文档中的任何链接;使用禁用 eval 的 pdf.js 以纯文本方式解析 PDF;检测 DOCX 宏,但绝不执行;移除 HTML 脚本和用户不可见元素;剥除零宽/双向覆盖字符。系统会检测直接向 AI 系统发出的段落(“忽略之前的指令……”),将其标记为 embedded_instructions_detected,并从事实、义务和要求中排除。可选的 LLM 辅助功能(DOCUMENT_FACTS_LLM=auto + intelligence synthesizer)仅用于确定性提取器无法确认的用户请求/优先事实;它会收到一条声明文档仅为数据的系统提示词,不会看到检测出的注入段落,没有任何工具可用,并且所有回答都必须逐字引用文档——引用位置由我们的代码定位(同时也会推导页码);若回答缺少引用、引用位于注入段落中、引用无法佐证相应值,或包含环境变量中的机密值,则会被丢弃。

结构化错误(x402/L402/MPP 不收费):MISSING_DOCUMENT / CONFLICTING_DOCUMENT_SOURCES / EMPTY_DOCUMENT / INVALID_URL 返回 400,DOCUMENT_TOO_LARGE 返回 413,UNSUPPORTED_FORMAT 返回 415,UNREADABLE_DOCUMENT 返回 422(文档损坏、受密码保护,或扫描件缺少文本层),DOCUMENT_FETCH_FAILED 返回 502,DOCUMENT_TIMEOUT 返回 504,EXTRACTION_FAILED 返回 500,此外还有共享错误 INVALID_INPUT / INVALID_JSON。

OpenAPI / /api/v1/capabilities 中的示例输出是基于合成协议的真实处理流水线结果——使用 node --import tsx scripts/generateDocumentFactsExample.ts 重新生成(若内容偏离,测试会失败)。**单位经济性:**确定性提取没有按调用计费的服务商成本;LLM 辅助功能(启用且有需要时)≈ ≤ $0.02。

金融:发票异常检查(invoice_anomaly_check,$0.25/次)
invoice_anomaly_check
$0.25 / successful call
Pre-payment invoice anomaly and risk-indicator detection (any country, any currency)

POST /api/v1/finance/invoice-anomaly-check(API 密钥)· POST /api/v1/x402/finance/invoice-anomaly-check(x402,$0.25 = 250000 USDC atomic units)· POST /api/v1/l402/finance/invoice-anomaly-check(启用时使用 L402)· POST /api/v1/mpp/charge/invoice_anomaly_check 和 MPP sessions(启用时使用)· MCP 工具 invoice_anomaly_check。类别 finance_risk,OpenAPI 标签 "Finance",幂等,无副作用。在批准、付款、入账、对账或审计发票前使用,尤其适用于 Agent 需要判断发票是否需要人工审核的情况。 该引擎(src/invoice-anomaly/)具有确定性,且在进程内运行:不使用大模型、外部 API 或凭据,也不会存储或记录任何内容。金额会从十进制文本解析为缩放后的 BigInt(绝不会作为二进制浮点数进行比较),并且每次舍入运算的算术比较都有明确的舍入容差,即一个货币最小单位(0.01 USD、0.001 OMR、1 JPY;可通过 options.roundingTolerance 覆盖)。

两种模式。 独立模式——仅使用 invoice(唯一必填字段为 invoice.total):算术、日期/条款、重复行项目、发票编号合理性、货币代码有效性、缺失的业务字段。上下文感知模式——可添加以下任意信息:historicalInvoices(≤ 1,000 张,可来自任何供应商)、supplierProfile(ID、别名、含 verified/addedDate 的 bankAccounts、货币、条款、状态、createdDate)、purchaseOrder(供应商、货币、totalAmount、invoicedToDate / remainingAmount、amountsIncludeTax、issueDate、状态、行项目)、contract(供应商、货币、maxAmount、maxInvoiceAmount、invoicedToDate、期限、条款)、approvalContext(approvalThreshold、货币、splitWindowDays)和 paymentHistory(≤ 1,000 条)。options.asOfDate 可固定参考日期(默认为今天,UTC),以确保输出完全可复现。

curl -X POST http://localhost:8787/api/v1/finance/invoice-anomaly-check \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"invoice":{"invoiceNumber":"INV-2026-1043","supplierName":"ABC Trading LLC","supplierId":"SUP-291","invoiceDate":"2026-09-20","dueDate":"2026-10-20","currency":"USD","subtotal":9200,"tax":460,"total":9660,"bankAccount":"US123456789","paymentTermsDays":30,"poNumber":"PO-2026-818","lineItems":[{"description":"Consulting services","quantity":10,"unitPrice":920,"taxRate":5,"total":9200}]},
       "historicalInvoices":[{"invoiceNumber":"INV-2026-1021","supplierId":"SUP-291","invoiceDate":"2026-08-20","currency":"USD","total":8694,"bankAccount":"US987654321"}],
       "options":{"asOfDate":"2026-09-23"}}'

响应(节选):

{
  "riskScore": 44, "riskLevel": "high", "decision": "review", "anomalyCount": 1,
  "anomalies": [{
    "code": "BANK_ACCOUNT_CHANGED", "severity": "high", "confidence": 0.8, "field": "bankAccount",
    "explanation": "The payment account on the invoice differs from the 1 account(s) previously recorded for this supplier; payment details should be verified with the supplier through a known contact before payment.",
    "evidence": { "reason": "account_differs_from_supplier_records", "invoiceAccountMasked": "****6789", "knownAccountsMasked": ["****4321"], "lastUsedDate": "2026-08-20", "accountSeenForOtherSuppliers": false }
  }],
  "financialChecks": { "subtotalValid": true, "taxValid": true, "totalValid": true, "lineTotalsValid": true, "computed": { "lineItemsSum": 9200, "expectedTax": 460, "expectedTotal": 9660, "tolerance": 0.01 } },
  "recommendedAction": "Verify the payment details directly with the supplier using contact details already on file (not those on the invoice) before paying.",
  "summary": "1 anomaly detected (BANK_ACCOUNT_CHANGED); risk high (44/100). Advisory decision: review — …",
  "mode": "context_aware", "contextUsed": { "historicalInvoices": 1, "supplierHistoryMatched": 1, "…": "…" },
  "invoiceSummary": { "invoiceNumber": "INV-2026-1043", "total": 9660, "bankAccountMasked": "****6789", "…": "…" },
  "dataCompleteness": { "missingRequiredFields": [], "missingRecommendedFields": [] },
  "scoring": { "model": "iac-1.0.0", "components": [{ "code": "BANK_ACCOUNT_CHANGED", "severity": "high", "confidence": 0.8, "basePoints": 55, "points": 44 }], "escalationBonus": 0, "…": "…" },
  "limitations": ["The capability identifies invoice anomalies and risk indicators. It does not independently establish fraud or replace accounting, audit, compliance, or payment-authorization controls.", "…"],
  "asOfDate": "2026-09-23", "checkedAt": "2026-09-23T00:00:00.000Z"
}

(完整生成示例——一张近乎重复的发票,使用了不同的银行账户,并针对另一家供应商的 PO 开票,结果为 critical / hold——位于 OpenAPI 和 /api/v1/capabilities 中;可通过 node --import tsx scripts/generateInvoiceAnomalyExample.ts 重新生成,若示例发生偏差,测试会失败。)

异常代码(每个代码对应一条记录;子发现项位于 evidence.issues 中):

类别代码触发条件
算术LINE_TOTAL_MISMATCH、SUBTOTAL_MISMATCH、TAX_MISMATCH、TOTAL_MISMATCH数量 × 单价 − 折扣 ≠ 行项目总额;行项目总额之和 ≠ 小计;所列税额 ≠ 行项目/发票税率 × 计税基数;小计 − 折扣 + 税额 + 运费 ≠ 总额(超出容差范围)。≤ 总额的 0.5% → 低,否则为中
重复项DUPLICATE_INVOICE、POSSIBLE_DUPLICATE仅当供应商相同,且发票编号相同(或数字相同)、金额相同,或已有记录的付款时,才确认为重复;以下情况标记为可能重复:编号变体(后缀/拼写错误);金额相同且在 duplicateWindowDays(14)内,并有佐证信号(编号相似、行项目相同、PO 相同);3 天内金额相同;编号重复但金额不同;重新提交已取消/拒绝的发票;或另一条供应商记录下存在相同编号和金额。编号连续以及周期性出现相同金额的账单不视为佐证。返回匹配记录
供应商行为(≥ 3 张历史发票)UNUSUAL_AMOUNT、UNUSUAL_CURRENCY、UNUSUAL_PAYMENT_TERMS、INVOICE_NUMBER_ANOMALY、SUPPLIER_PATTERN_DEVIATION金额高于历史最大值、且 ≥ 中位数的 2 倍、稳健 z 分数(中位数/MAD)≥ 3.5(≥ 5 倍时 → 高);供应商首次使用该币种/币种不在档案范围内/与合同不符/不符合 ISO 4217;付款期限 ≤ 合同、档案或历史记录中的期限的 50%,且至少短 10 天;占位编号、格式偏离供应商 ≥ 80% 的主流格式、编号不连续;供应商被列入黑名单/暂停/停用/未经验证,供应商 ID/名称与档案不符,供应商记录创建时间距发票日期 ≤ 30 天,30 天内发票数量 ≥ 3 且达到此前月均速率的 ≥ 3 倍
付款详情BANK_ACCOUNT_CHANGED、UNKNOWN_BANK_ACCOUNT账户不在供应商档案/历史记录/付款账户中(高),或账户已登记在档案中,但添加时间距今 ≤ 30 天且从未用于付款(中);档案中没有该账户,或仅有该账户的未验证记录(高);如果该账户也出现在另一家供应商的记录中,则为严重风险。始终对账户信息进行遮蔽
PO / 合同 / 审批PO_MISMATCH、PO_AMOUNT_EXCEEDED、CONTRACT_LIMIT_EXCEEDED、APPROVAL_THRESHOLD_EXCEEDED(信息)PO 引用、供应商、币种、已关闭/已取消状态、数量超过订购量、单价高于 PO、行项目不匹配;金额超过 PO 剩余额度(超过 PO 金额的 10% → 高);合同期限内累计开票金额(由调用方提供或根据历史记录汇总)超过合同上限,或超过单张发票最高限额;超过审批阈值(仅用于路由的信息,0 分)
拆分发票SPLIT_INVOICE_PATTERNsplitWindowDays(7)内同一供应商的多张发票各自不超过审批阈值,但合计超过阈值(开票日期相同、行项目相似/PO 相同、金额均 ≥ 限额的 85% 时,置信度更高);或 90 天内有 ≥ 3 张发票,金额达到阈值的 90–100%。这是风险指标,绝非指控
日期DATE_ANOMALY、DUE_DATE_ANOMALY发票日期晚于当前日期 > 1 天、距今 > 365 天、早于 PO 签发日期或不在合同期限内;到期日在发票日期之前、到期日与所列付款期限相差 > 3 天、付款期限 > 1 年。不标记到期日与发票日期相同、已逾期或发票日期为次日的情况
行项目DUPLICATE_LINE_ITEM相同描述/SKU、相同价格和数量的行项目出现两次(中);或描述近乎相同且价格相同(低)
数据质量MISSING_REQUIRED_FIELD缺少业务必填字段(发票编号、供应商、发票日期、币种):低;缺少 ≥ 3 项时为中。建议填写的字段(到期日/付款期限、行项目、小计、税额、银行账户)仅列在 dataCompleteness 中
**风险评分(模型 iac-1.0.0,src/invoice-anomaly/scoring.ts + config.ts):**每项异常按基础分 × 置信度计分 — info 0、low 5、medium 20、high 35、critical 50,覆盖规则为 DUPLICATE_INVOICE 50/60、POSSIBLE_DUPLICATE 20/40、BANK_ACCOUNT_CHANGED 25/55/70、UNKNOWN_BANK_ACCOUNT 20/40/60、SPLIT_INVOICE_PATTERN 20/40、MISSING_REQUIRED_FIELD 3/8。低级和 info 级贡献总分上限为 10,中级贡献上限为 45。关联加分:每增加一个包含 ≥ medium 级异常的风险类别,加 8 分(最多 +24);当 ≥ high 级付款详情异常与重复、PO/合同或拆分异常同时出现时,加 10 分。严重性上限:仅有 low 级发现时,得分 ≤ 14;最高为 medium 时,得分 ≤ 64 — 因此,轻微警告绝不会导致 critical 结果。**等级 / 建议决策:**0–14 low → continue;15–39 medium → review;40–64 high → review;65–100 critical → hold。scoring 对象会返回每个组成部分、上限、加分项和原始分数。

错误(x402 / L402 / MPP 不收费):INVALID_INPUT(架构 / 未知字段)和 INVALID_JSON 为 400,INVALID_MONETARY_VALUE 为 400,INVALID_DATE 为 400,UNSUPPORTED_CURRENCY_FORMAT 为 400(每项均包含 details.path,绝不包含具体值),ANALYSIS_FAILED 为 500(已清理)。**单位经济性:**没有上游服务提供商或大模型成本。

**限制。**此功能用于识别发票异常和风险指标。它不能独立确认欺诈,也不能取代会计、审计、合规或付款授权控制。decision 仅供参考,并非付款批准或拒绝。发现结果仅限于所提供的数据(不会查询 ERP、银行、登记机构或第三方欺诈来源;连接器将在后续阶段推出)。供应商基线至少需要该供应商此前的 3 张发票;不同货币的金额绝不会进行换算或比较;checkedAt 精确到日期(截至日期),因此相同输入会得到相同输出。

汽车:车辆价值估算(vehicle_value_estimate,每次调用 $0.25)
vehicle_value_estimate
$0.25 / successful call
Fair-market valuation of a passenger vehicle from comparable-market evidence (global, multi-provider)

POST /api/v1/automotive/vehicle-value-estimate(API key) · POST /api/v1/x402/automotive/vehicle-value-estimate(x402,$0.25 = 250000 USDC atomic units) · POST /api/v1/l402/automotive/vehicle-value-estimate(启用时可用 L402) · POST /api/v1/mpp/charge/vehicle_value_estimate 和 MPP 会话(启用时可用) · MCP 工具 vehicle_value_estimate · 免费预览 POST /api/v1/preview/vehicle_value_estimate。类别 automotive,OpenAPI 标签 "Automotive",支持幂等,无副作用。所有这些信息都来自 src/domain/capabilities.ts 中的同一个注册表条目。当 AI Agent 需要估算乘用车当前市场价值、判断标价是否合理、估算私人出售或经销商价格、评估折旧,或使用本地或区域市场可比车辆评估车辆时,请使用此功能。

它会返回公允价值低位 / 中位 / 高位区间、私人出售、经销商收购(以旧换新)和经销商零售估值、标价与中位数的精确差额及其市场位置、折旧、每项估值调整及其金额和原因、所用可比车辆(含相似度评分)、市场统计数据、确定性的置信度评分、机器可读的风险标记、假设和数据新鲜度。估值计算采用确定性应用代码:不涉及大模型,对于相同市场依据下相同的规范化输入,始终会得到相同输出。 输入。 必填:make、model、year(1950 年至下一日历年)、country(ISO alpha-2/alpha-3 或国家名称,任何国家均可)。强烈建议提供以下信息(每缺少一项都会降低置信度并产生标记):mileageKm(0–2,000,000)、trim、city、condition(excellent、very_good、good、fair、poor、unknown)。可选:askingPrice(> 0)、currency(ISO 4217;默认值 = 市场使用的货币)、fuelType、transmission、bodyType、engine、drivetrain、accidentHistory(false/"none"、"minor_cosmetic"、"repaired"、"structural"、true/"reported" = 未说明严重程度、"unknown")、serviceHistory(full、partial、none、unknown)、owners(1–20)、color、options(≤ 40)、valuationDate(YYYY-MM-DD,不得晚于当前日期;忽略之后观察到的证据——用于保险/历史估值)。vin(可选,17 个字符——见下文“VIN”)。常见同义词会被规范化(“gasoline” → petrol、“Auto” → automatic、“4x4” → 4wd、“VW” → Volkswagen、“UAE” → AE);未知值和未知字段会被拒绝。

curl -X POST https://api.rafidsystem.com/api/v1/automotive/vehicle-value-estimate \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"make":"Toyota","model":"Land Cruiser","year":2022,"trim":"GXR","mileageKm":68000,"condition":"good",
       "country":"Oman","city":"Muscat","currency":"OMR","fuelType":"petrol","transmission":"automatic","bodyType":"suv",
       "drivetrain":"4WD","accidentHistory":false,"serviceHistory":"full","owners":1,
       "options":["sunroof","leather seats","360 camera"],"askingPrice":22500}'

响应(节选;由真实引擎根据合成样例列表生成——见下文):

{
  "status": "estimated",
  "estimatedValue": { "low": 22100, "mid": 23000, "high": 23900, "currency": "OMR" },
  "estimatedPrivateSalePrice": 23400, "estimatedDealerBuyPrice": 20600, "estimatedDealerRetailPrice": 25700,
  "askingPriceAnalysis": { "askingPrice": 22500, "differenceFromMid": -500, "differencePercent": -2.17, "marketPosition": "near_market", "withinEstimatedRange": true, "thresholdsPercent": { "wellBelow": -15, "below": -5, "above": 5, "wellAbove": 15 } },
  "depreciation": { "estimatedOriginalPrice": null, "totalDepreciationAmount": null, "marketImpliedAnnualDepreciationPercent": null, "…": "…" },
  "adjustments": [
    { "factor": "mileage", "impactAmount": -20, "impactPercent": -0.1, "basis": "market_derived", "reason": "Mileage (68,000 km) is above the comparable-market median (66,000 km); …" },
    { "factor": "listing_to_transaction", "impactAmount": -680, "impactPercent": -3, "basis": "heuristic", "reason": "Comparable prices are listing asking prices; a 3.0% typical negotiation margin for Oman …" },
    { "factor": "full_service_history", "impactAmount": 330, "impactPercent": 1.5, "basis": "heuristic", "reason": "Documented full service history supports stronger resale value." }
  ],
  "marketComparables": [{ "year": 2022, "trim": "GXR", "mileageKm": 71000, "askingPrice": 23300, "currency": "OMR", "city": "Muscat", "sourceName": "synthetic-dealer-feed", "observedAt": "2026-09-14T12:00:00.000Z", "similarityScore": 0.99, "…": "…" }],
  "marketStats": { "comparableCount": 10, "comparablesConsidered": 23, "outliersExcluded": 2, "medianPrice": 22875, "medianMileageKm": 64000, "priceDispersionPercent": 1.6, "…": "…" },
  "methodology": { "fallbackLevel": "same_trim_same_year", "mileageEffect": { "basis": "market_derived", "percentPer10000Km": -0.53 }, "rangeHalfWidthPercent": 4, "…": "…" },
  "confidence": { "score": 0.94, "level": "high", "reasons": ["10 comparable listing(s) used …", "…"], "factors": { "comparableVolume": 0.83, "…": "…" } },
  "riskFlags": ["HEURISTIC_ADJUSTMENTS_USED", "OUTLIERS_REMOVED"],
  "assumptions": ["Vehicle has clear legal ownership/title status …", "No undisclosed accident or structural damage; …", "…"],
  "dataFreshness": { "latestComparableAt": "2026-09-20T12:00:00.000Z", "oldestComparableAt": "2026-08-19T12:00:00.000Z", "medianComparableAgeDays": 17, "recentListingCount": 8, "recentWindowDays": 30 },
  "marketCoverage": { "countryCode": "OM", "valuationParametersConfigured": true, "marketDataProviders": [{ "id": "…", "status": "ok", "comparablesReturned": 23 }], "liveMarketDataAvailable": true, "…": "…" }
}

(完整示例见 OpenAPI 和 /api/v1/capabilities;运行 node --import tsx scripts/generateVehicleValueExample.ts 可重新生成——如果示例发生偏差,测试会失败。其第一条假设明确标注为合成数据。)

Agent 使用示例。 “估算这辆 2022 年款 Toyota Land Cruiser GXR 在马斯喀特的当前市场价值,它已行驶 68,000 km,并告诉我 OMR 22,500 是否是合理的要价。” → Agent 使用上述字段和 askingPrice: 22500 调用 vehicle_value_estimate,然后根据 estimatedValue 和 askingPriceAnalysis 作答(“OMR 22,500 比 OMR 23,000 的中位价低 2.2 %——接近市场价;合理区间为 OMR 22,100–23,900;置信度高”)。如果 status 为 insufficient_market_data,则如实说明,而不是猜测。

架构(src/vehicle-value/)。 输入验证(schemas/vehicleValueInputs.ts)→ 规范化(normalization.ts、subject.ts)→ 市场检测(markets.ts)→ 市场数据路由器(providers/router.ts:选择所有支持该国家或其区域回退国家的提供方,并发运行这些提供方,每个调用均受 VEHICLE_MARKET_PROVIDER_TIMEOUT_MS 限制,对结果进行清理和去重,缓存提供方搜索结果)→ 同类车辆规范化 + 货币换算 + 资格筛选(comparables.ts)→ 渐进式回退选择 → 加权相似度(similarity.ts)→ 异常值移除 → 基础估值和调整(valuation.ts)→ 置信度(confidence.ts)→ 结构化结果(service.ts)。核心逻辑不会记录同类车辆来自哪个交易平台。

市场数据提供方。 任何数据源都可实现 VehicleMarketProvider(types.ts:id、supports(market)、searchComparables(query, signal)、可选的 getNewVehiclePrice / newPriceRank),并在 buildVehicleRuntime()(service.ts)中添加——估值核心无需更改。每个提供方在完成配置前都处于关闭状态;在默认部署中,每次调用都会如实返回 insufficient_market_data 和 NO_MARKET_DATA_PROVIDER,并且绝不会声称某个市场拥有实时数据,除非有已配置的提供方覆盖该市场。随附的提供方:

提供方(id)市场启用方式说明
vehicle_market_records在 VEHICLE_MARKET_COUNTRIES 中声明(例如 OM,AE 或 *)VEHICLE_MARKET_DATA_MODE=databaseRafid 自有的已导入许可数据表,包含经销商/合作伙伴导出数据、拍卖结果等证据——参见“数据层”。也提供经过验证的新车价格。
marketcheck美国、加拿大MARKETCHECK_API_KEY(可选 MARKETCHECK_PAGES,默认值为 2)MarketCheck 库存搜索,一个获得许可的付费车辆信息 API:GET https://api.marketcheck.com/v2/search/car/active?api_key=…&make&model&year_range&car_type=used&country=us|ca&rows=50&start=…。英里会换算为公里,使用美元/加元;车身类型、燃料、变速箱和驱动形式会映射为枚举值;经销商联系方式和 VIN 会被移除(VIN 会转换为哈希值以便排除重复项)。当至少有 3 条相同年份(和配置)的信息一致时,列表中经销商提供的 msrp 可用作新车价格参考。密钥仅保留在服务端,不会出现在响应、日志或错误中。
feed:<id>按各数据源声明VEHICLE_MARKET_FEEDS_JSON面向没有公开车辆信息 API 的市场的合作伙伴/授权 HTTPS 数据源(例如阿曼或阿联酋的经销商集团、分类信息或拍卖合作伙伴):[{"id":"om-dealer","url":"https://…/rafid.json","countries":["OM"],"format":"json","authHeaderEnv":"OM_DEALER_FEED_AUTH"}]。合作伙伴以 JSON([...] 或 {"records":[...]})或 CSV 格式发布 vehicle_market_records 行格式的数据;Rafid 通过 Production Feed Runner 的 SSRF 防护抓取器获取数据(仅支持 https,拒绝私有/环回地址和未经验证的重定向,限制为 8 秒/10 MB),验证每一行,删除个人数据列,并将数据源缓存一小时。authHeaderEnv 指定一个保存 Authorization 标头值的环境变量(密钥不会写入 JSON)。不进行抓取——只使用合作伙伴为 Rafid 发布的数据源。

英国/欧洲车辆信息 API(例如 MarketCheck UK)和 GCC 分类信息目前尚无经过验证并集成的公开协议;这些市场通过合作伙伴数据源或导入记录提供服务。估值参数(默认货币、预期年行驶公里数、经销商/私人交易价差、议价幅度、保守回退值)已针对阿曼、阿联酋、沙特阿拉伯、卡塔尔、巴林、科威特、美国、加拿大、英国、澳大利亚和 14 个欧洲市场进行配置;其他国家在有数据源覆盖时使用通用默认值(并且必须通过 currency)。每个响应中的 marketCoverage 会分别报告估值参数支持情况和数据源/数据支持情况。 数据层。 vehicle_market_records(store/postgres.ts,使用 CREATE TABLE IF NOT EXISTS 创建):包含品牌/车型/配置及其标准化键、年份、里程、车况、车身/燃料/变速箱/驱动方式、国家/城市/地区、价格 + 货币 + price_type(挂牌/成交)、来源类型/名称/记录 ID/URL、observed_at、ingested_at、元数据。索引覆盖标准化品牌 + 车型 + 年份、配置、国家 + 城市、里程和 observed_at;(source_name, source_record_id) 上的唯一索引可避免重复导入。仅包含车辆和市场证据——没有卖家姓名、电话、电子邮件、VIN 或车牌号字段。使用 npm run vehicle:import -- listings.csv [--dry-run] [--source-type=licensed_feed] 导入(CSV 或 JSON,≤ 20 MB):每一行都会经过验证和标准化,无效行会被报告,疑似包含个人数据的列会被丢弃并列出,绝不会存储(对于数据源记录,vin 列会被转换为哈希,仅保留在内存中;不会写入表中)。新车价格: vehicle_new_prices(品牌/车型/年份/配置/国家、价格 + 货币、来源名称/URL、生效日期;按品牌/车型/年份/配置/国家/来源唯一)保存已核实的原始价格——例如经销商的官方车型年份价目表、制造商公布的 MSRP。使用 npm run vehicle:import -- new-prices.csv --new-prices [--dry-run] 导入(列为 make,model,year,trim,country,price,currency,sourceName,sourceUrl,effectiveDate)。仅当配置完全匹配时才会使用价格(若未提供配置,则仅在该车型年份只有一个明确无歧义的价格时使用);其优先级高于 MarketCheck 报告的经销商 MSRP。仅导入你获准或有权使用的数据。

可比车辆筛选。 品牌 + 车型必须匹配;证据的观测日期不得晚于估值日,且不得超过 VEHICLE_MARKET_MAX_LISTING_AGE_DAYS(365)。逐步放宽筛选条件,并记录在 methodology.fallbackLevel 中:same_trim_same_year → same_trim_year_plus_minus_1 → same_model_year_plus_minus_1 → same_model_year_plus_minus_2 → regional_same_model_year_plus_minus_2(GCC ↔ GCC、US ↔ CA、AU ↔ NZ、欧元区 ↔ 欧元区;标记为 REGIONAL_FALLBACK_USED)。优先采用可比车辆数量 ≥ 5 的最窄筛选级别;否则采用可比车辆数量最多的级别,但异常值移除后至少须剩余 3 辆——低于此数量时返回 insufficient_market_data。相似度(0–1):配置 25 %、车型年份 20 %、里程 20 %、地点 15 %(同一城市 1 · 同一国家 0.8 · 同一地区 0.35)、车况 5 %、燃料 + 变速箱 5 %、车身 + 驱动方式 5 %、时效性 5 %。中位数会按相似度加权;不会盲目对挂牌价取平均值。 估值方法。(1)当有 ≥ 6 条可比数据,且涵盖 ≥ 2 个车型年份/里程跨度 ≥ 10,000 km 时,根据可比数据拟合车型年份和里程的影响(在对数价格上进行相似度加权最小二乘);结果限制在合理范围内,并按 n/(n + 8) 向市场默认值收缩;否则采用市场的保守默认值(basis: "heuristic",置信度降低)——不存在单一的全局每公里折旧值。(2)在车型级数据池中,使用池内测得的价格差,将其他配置与目标车辆的配置对齐(目标车辆配置至少需要 2 条挂牌数据;否则不推定配置溢价,并将估值标记为车型级)。(3)异常值:n ≥ 5 时,对对数归一化价格使用 Tukey 1.5×IQR 围栏;n = 4 时,使用基于 MAD 的修正 z 分数,阈值为 > 3.5——绝不使用硬编码的价格阈值(marketStats.outliersExcluded)。(4)锚点 = 按相似度加权的中位数,基于经车型年份/配置对齐后的价格,再根据可比数据的加权里程中位数与目标车辆里程之间的差异,按里程影响调整。(5)仅当两组各有 ≥ 2 条可比数据时,才采用市场推导的本地市场(区域数据池)以及变速箱/驱动形式/燃料类型调整。(6)挂牌价 → 按市场议价幅度调整为公允价值(已记录的成交价不做此调整)。(7)车辆特定调整——车况(相对于可比车辆的典型车况)、事故历史(轻微 −3 %、已修复/已报告 −10 %、结构性损伤 −25 %)、保养记录(完整 +1.5 %、无记录 −4 %)、车主数量、高级选装配置(每项 +0.5 %,≤ 2 %)——均采取保守值,标记为 heuristic,且总调整限制在 −35 %/+6 %,因此绝不会盖过市场证据。范围:±(½ × 稳健离散度 + 15 % ×(1 − 置信度)+ 2 %),限制在 4–40 %;证据较弱时范围会扩大。**经销商/私人交易:**每个市场均有 dealerBuyDiscountRange、privateSaleAdjustmentRange、dealerRetailMarkupRange;引擎会根据置信度选取确定值(证据越弱 ⇒ 经销商折扣越大),并确保经销商收购价 ≤ 私人交易价 ≤ 经销商零售价。**舍入:**按数值大小舍入到 10/50/100/500/1,000(低置信度时翻倍),确保不会以虚假精度呈现薄弱证据。挂牌价:differenceFromMid 和 differencePercent(2 位小数)以舍入后的中位值为基准;≤ −15 % 为 well_below_market,≤ −5 % 为 below_market,< +5 % 为 near_market,< +15 % 为 above_market,否则为 well_above_market。**折旧:**仅当存在经核实的新车价格参考时,才计算原价(estimatedOriginalPrice,以及 originalPriceSource)、总折旧和年化折旧(1 − (mid / original)^(1/age))——参考价格来源为进口的官方价目表,否则需有 ≥ 3 个一致的 MarketCheck 经销商报告 MSRPs(否则为 null——绝不推定);当车型年份影响由市场数据推导时,提供 marketImpliedAnnualDepreciationPercent。

置信度方法(confidence.ts,确定性):对可比数据量(22 %,12 条时满分)、平均相似度(18 %)、完全匹配配置占比(10 %)、里程证据(10 %)、地域性(10 %)、新鲜度(12 %,中位数据时长 ≤ 30 天 → 1,≥ 180 天 → 0)、数据提供方多样性(6 %)和价格一致性(12 %,基于稳健离散度与 35 % 的比较)加权求和;随后,陈旧证据(中位数据时长 > 90 天)乘以 0.85,车况未知乘以 0.95,启发式里程影响乘以 0.93,启发式车型年份影响乘以 0.95,区域回退乘以 0.9;少于 5 条可比数据时上限为 0.59,证据不足时上限为 0.39。等级:≥ 0.80 为 high,≥ 0.60 为 medium,≥ 0.40 为 low,否则为 very_low。confidence.factors 和 confidence.reasons 会公开每项输入。 **货币。**结果货币 = currency 或市场默认货币。汇率绝不会硬编码或猜测:只有通过已配置且带日期的来源,才会将另一种货币的证据(或新车价格参考)换算为结果货币;每笔实际使用的换算都会记录在 currencyConversion.conversions 中(from、to、rate、source、rateDate)。如果没有可用来源,该证据会被排除,并标记为 CURRENCY_CONVERSION_UNAVAILABLE。VEHICLE_FX_SOURCES 按货币对列出依次尝试的来源:

  • ecb — 欧洲中央银行欧元参考汇率(eurofxref-daily.xml):免费,无需密钥,支持约 30 种货币(USD、GBP、CAD、AUD、CHF、SEK、DKK、PLN 等)——不包括海湾合作委员会(GCC)货币。该数据仅供参考发布,适合用于可比项的标准化。
  • exchangerate_api — ExchangeRate-API:支持 OMR、AED、SAR、QAR、BHD、KWD 等约 160 种货币,每日更新。设置 EXCHANGERATE_API_KEY 时使用带密钥的端点(v6.exchangerate-api.com/v6/{EXCHANGERATE_API_KEY}/latest/USD),否则使用受速率限制的开放访问端点(该端点要求注明来源)。其条款禁止再分发汇率,并声明不保证准确性;Rafid 仅将其用于可比项,但在商业使用前请审查相关条款。 汇率按来源缓存(VEHICLE_FX_CACHE_TTL_MS,6 小时),并发加载,超时时间为 3 秒;失败的来源会被跳过(最多可复用之前加载的汇率表 3 天)。建议设置:VEHICLE_FX_SOURCES=ecb,exchangerate_api。未配置默认货币的国家必须提供 currency(CURRENCY_REQUIRED,400)。

VIN。vin 为可选项。系统会对其进行规范化和验证(17 个字符,不含 I/O/Q);北美 VIN 还必须通过 ISO 3779 校验位验证(否则标记为 VIN_CHECK_DIGIT_INVALID)。离线时,系统会将制造商前缀(部分 WMI 表)和车型年份代码与请求进行比对。启用 VEHICLE_VIN_DECODER=nhtsa 后,还会通过 NHTSA vPIC 解码(DecodeVinValues/{VIN}?format=json,免费,无需密钥——此功能需显式启用,因为会将 VIN 发送给第三方;VEHICLE_VIN_DECODER_TIMEOUT_MS,3 秒),并与市场搜索并发执行。若解码结果与提供的品牌和年份一致,则只会填充调用方未提供的字段(配置、车身、燃料、驱动形式、变速箱、发动机——列于 vinCheck.enrichedFields);若 VIN 不匹配,则标记为 VIN_MISMATCH,并按请求中描述的信息评估车辆价值。解码器故障会标记为 VIN_DECODE_UNAVAILABLE,不会导致致命错误。来自 MarketCheck、数据源或导入内容的同一车辆刊登信息(通过 VIN 的 SHA-256 哈希匹配)会从该车辆自身的可比项中排除(SUBJECT_LISTING_EXCLUDED)。VIN 绝不会被存储、记录日志、缓存、写入遥测/分析数据或预览指纹;响应中只包含 vinCheck.vinMasked(隐藏 6 位序列号)。

风险标记。INSUFFICIENT_COMPARABLES、NO_MARKET_DATA_PROVIDER、STALE_MARKET_DATA、HIGH_PRICE_DISPERSION、MILEAGE_UNKNOWN、TRIM_UNKNOWN、CONDITION_UNKNOWN、ACCIDENT_HISTORY_UNKNOWN、ACCIDENT_SEVERITY_UNVERIFIED、STRUCTURAL_DAMAGE_REPORTED、HIGH_MILEAGE_FOR_AGE、ASKING_PRICE_SIGNIFICANTLY_ABOVE_MARKET、ASKING_PRICE_SIGNIFICANTLY_BELOW_MARKET、CURRENCY_CONVERSION_UNAVAILABLE、REGIONAL_FALLBACK_USED、BROADENED_COMPARABLE_SEARCH、HEURISTIC_ADJUSTMENTS_USED、PROVIDER_PARTIAL_FAILURE、OUTLIERS_REMOVED、VIN_MISMATCH、VIN_CHECK_DIGIT_INVALID、VIN_DECODE_UNAVAILABLE、SUBJECT_LISTING_EXCLUDED。 性能、缓存、可观测性。 各提供方并发运行,并为每个提供方设置超时;超时或错误会报告在 marketCoverage.marketDataProviders 中(PROVIDER_PARTIAL_FAILURE),且不会导致请求失败。提供方搜索(不包括估值)会在进程内缓存 VEHICLE_MARKET_CACHE_TTL_MS(6 h),缓存键包括提供方、市场国家/地区、品牌、车型、车型年份范围和证据截止日期。分析层会记录每次调用(能力、渠道、成功状态、延迟、dataSource live_provider / not_configured);估值遥测数据(国家/地区、品牌、车型、车型年份、可比车辆数量、提供方数量/失败数、置信度级别、回退级别、延迟——不含 VIN、价格或个人数据)可通过内部单位经济效益路由获取。

计费和预览。 通过共享支付基础设施(API credits/subscriptions、x402、L402、MPP),每次成功调用收费 $0.25——没有针对特定能力的支付代码。架构无效的请求(400)和 VALUATION_FAILED(500)绝不收费。结构化的 insufficient_market_data 结果属于成功的 200 响应,和其他能力如实返回的“无数据”结果一样收费;Agent 应先调用免费预览,该预览会在共享预览速率限制下报告(不查询任何提供方,也不对任何内容定价)已配置的提供方是否覆盖该市场(status: "available" 与 "limited")。

限制。 依据的是市场证据,而非实物检查、车辆历史记录核查或正式估价;不构成财务、贷款或保险建议。覆盖范围完全取决于部署中配置的提供方。可比车辆主要采用挂牌价(按有据可查的议价幅度进行调整)。市场证据不会用于定价选装配置、颜色和发动机(选装配置仅采用小幅封顶的启发式估算)。假设车辆产权清晰、无留置权且没有未披露的损坏(在 assumptions 中说明);VIN 解码可确认车辆身份/规格,但无法确认历史记录。跨币种证据需要配置汇率来源。MarketCheck 仅覆盖美国/加拿大经销商库存;其他市场需要导入记录或合作方数据源。

阿曼生产环境市场数据(数据库模式、导入、缓存)

默认情况下(OMAN_PROPERTY_DATA_MODE=manual 或未设置),analyze_oman_property 仅使用附带的精选样本数据——运行或评估此能力无需数据库。生产环境部署则应填充由真实 PostgreSQL 支撑的数据层并切换模式:

  • OMAN_PROPERTY_DATA_MODE=manual(默认)——仅使用精选的 ManualDatasetProvider 样本数据。不会尝试连接数据库。
  • OMAN_PROPERTY_DATA_MODE=database——仅使用生产记录,通过 DatabaseOmanPropertyDataProvider(domain/oman/dataProviders.ts)从 PostgresPropertyMarketRepository(db/marketStore.ts)读取。必须设置 OMAN_MARKET_DATABASE_URL(或 DATABASE_URL);此模式下,手动样本数据不会对结果产生任何影响。
  • OMAN_PROPERTY_DATA_MODE=composite——优先使用数据库记录,仅在数据库没有可用数据时回退到手动样本数据。适用于仍在为部分区域接入真实数据源、其他区域尚未接入的情况。 架构和迁移: db/marketSchema.ts 定义了 property_market_records(governorate、wilayat、area、normalizedArea、propertyType、bedrooms、bathrooms、sizeSqm、transactionType、priceOMR、rentPeriod、furnished、sourceType、sourceName、sourceRecordId、sourceUrl、observedAt、ingestedAt、metadata JSONB),并在 normalizedArea/propertyType/transactionType/bedrooms/sizeSqm/observedAt 上建立索引,同时为包含来源记录 ID 的记录在 (sourceName, sourceRecordId) 上建立部分唯一索引。PostgresPropertyMarketRepository.migrate() 使用自己的迁移记录表(rafid_market_migrations,advisory lock 74382002)幂等地应用此架构——与客户/计费存储的迁移完全独立,因此市场数据架构变更既不会与客户数据迁移冲突,也不依赖于它们。有 sourceRecordId 时,记录会通过 upsert(INSERT ... ON CONFLICT (source_name, source_record_id) DO UPDATE)写入,因此重新导入同一数据源是安全且幂等的;没有 sourceRecordId 的记录则始终作为新记录插入,因为无法安全地去重。

导入数据集:

npm run market:import -- ./data/example-oman-market-import.csv

此操作会运行 PostgresPropertyMarketRepository.migrate()(可安全地重复运行),然后导入文件,并输出 { totalRows, imported, updated, skipped, errors } 的 JSON 摘要。CSV 和 JSON 均受支持({ "records": [...] } 或纯数组);预期列请参阅 data/example-oman-market-import.csv。每一行都会根据严格的架构进行验证(任何未知列都会被直接拒绝),地点会通过 analyze_oman_property 本身使用的相同英 Arabic area 解析进行标准化,租期、家具状态和交易类型别名也会标准化,文本字段则会经过清理——格式错误的行会被单独跳过并报告,不会导致整个导入中止。导入规模有限制(文件大小 10 MB,最多 20,000 行),且绝不会执行文件中的任何内容;没有 Web UI,只有 CLI 和底层的 importMarketRecords() 服务可供程序化使用。

面积验证因房产类型而异,而非使用统一范围:domain/oman/importPipeline.ts 中的 SIZE_BOUNDS_BY_PROPERTY_TYPE——公寓为 20-1500 平方米,联排别墅为 40-2000 平方米,别墅为 50-10,000 平方米——集中定义在一处,而不是采用针对典型公寓调整的单一范围(此前该范围会拒绝面积远超 3,000 平方米的真实超豪华别墅,同时对公寓又过于宽松)。每种类型的上下限仍然都是有限值:扩大别墅范围绝不意味着“什么都接受”。

缓存: 可选的 ComparableCache(domain/oman/cache.ts)可在数据库前端缓存重复的可比房源查询——缓存键由地区、房产类型、卧室数量、分桶后的房产面积、家具状态和交易类型组成——并支持可配置的 TTL(OMAN_MARKET_CACHE_TTL_MS,默认禁用)。随附的 MemoryComparableCache 仅在进程内有效;该接口刻意不与 Redis 耦合,因此以后无需改动 DatabaseOmanPropertyDataProvider 或任何分析逻辑,即可添加分布式缓存(例如未来的 RedisComparableCache)。

无论模式如何,以下内容都保持不变: 可比房源筛选、异常值剔除、置信度评分和来源标注(domain/oman/comparables.ts/confidence.ts)均不受这些变更影响——数据库提供程序只会检索更广泛的候选集合(匹配地区、房产类型、交易类型和时效性);所有下游关于实际采用哪些候选项的决策,无论记录来自何处,都在同一位置进行。

合作伙伴数据源(来自房地产公司、经纪人、物业管理方的授权房产级数据)

阿曼房地产情报引擎(可比房源筛选、置信度评分、NCSI 官方背景信息)本身已完整实现;这一层的唯一目的是让阿曼的房地产公司、经纪公司或物业管理公司能够轻松且安全地向 Rafid 提供获授权的市场数据,而不必让每条生产记录都只能来自人工整理的样例数据,或由操作人员手动加载文件。这是基础设施,不是 Agent 能力:其中任何内容都没有注册到 domain/capabilities.ts,因此不会出现在 /agent.json、/api/v1/capabilities、MCP 工具目录或 x402 付费路由系列中 — 调用 analyze_oman_property 的 Agent 无法访问甚至发现这些路由,而且这些路由不收费。analyze_oman_property 自身的请求/响应格式完全不受此项改动影响。

合作伙伴模型。 PropertyDataPartner(domain/oman/partners.ts)有意设计得十分精简 — partnerId(短标识,例如 gulf-realty-om)、partnerName、feedType(csv | json | http_feed)、sourceType(几乎总是 partner_feed)、enabled、dataLicenseReference、contactReference(自由文本指引 — 邮件别名或合同编号,绝不会是某个人的姓名或电话号码)以及 createdAt。其中有意不存储合作伙伴方任何个人的个人数据。PartnerRepository 是存储抽象,与 PropertyMarketRepository 完全相同 — 测试使用 MemoryPartnerRepository,生产环境使用 PostgresPartnerRepository(db/partnerStore.ts),并与 PostgresPropertyMarketRepository 共享同一个 Pool/数据库,而不是另开一个连接池。

身份验证模型 — 三种不同的凭据,各自限定用于对应场景:

  • X-API-Key(现有)— 四项计费能力的常规客户凭据。合作伙伴数据导入路由绝不接受此凭据。
  • X-Partner-Token — 每个合作伙伴各自的 bearer token(rafid_partner_<64 hex chars>),在 partner:create 时生成一次,并且只向操作人员返回一次;仅存储其 SHA-256 摘要(data_partners.token_digest),与 rafid_keys.digest 保护客户 API 密钥的方式相同。用于验证下方的数据导入端点,且只能作为一个特定合作伙伴进行操作 — 令牌绝不能用于操作或查看其他合作伙伴的数据。
  • X-Internal-Api-Key — 单个共享密钥(MARKET_DATA_INTERNAL_API_KEY),用于限制下方两个只读内部端点的访问。它有意与所有合作伙伴令牌及客户 API 密钥区分开来(合作伙伴绝不能查看跨合作伙伴的汇总数据)。若未设置,该路由始终返回 503,绝不会在没有保护的情况下开放。

导入模式。 CSV 文件上传、JSON 文件/数组上传,以及经过身份验证的 HTTP 数据源 — 三种方式都通过完全相同的 POST /api/v1/market-data/import 端点和完全相同的验证流程(domain/oman/importPipeline.ts);操作人员运行的 market:import CLI 已使用该流程。不存在单独的写入路径,也不会进行任何形式的抓取。

POST /api/v1/market-data/import — 绝不公开。必须提供 X-Partner-Token。请求正文可以是以下任一种:

  • Content-Type: text/csv(或 text/plain),正文为 CSV 文本,列格式必须与下方完全一致;或
  • Content-Type: application/json,正文为纯 JSON 记录数组,或 { "records": [...] }。 响应:{ success: true, data: { partnerId, totalRows, imported, updated, skipped, rejected, averageDataQualityScore, rejections: [{ row, code, message }] } }。格式错误的行不会中止整个批次,而是单独拒绝,并附上从 1 开始的行号、稳定的错误 code(见下文 拒绝代码)和安全的 message;绝不会回传你的原始行数据。同一批次中,两个 sourceRecordId 相同但内容不同的行会被视为合理的批次内更新(与之前完全相同,后者优先);内容完全相同的行则会被视为真正的重复项:较早出现的行会以代码 DUPLICATE_RECORD 被拒绝,只有较晚出现的行会被写入。

CSV 格式(列与 data/example-oman-market-import.csv 相同;完整的虚构示例见 data/example-partner-feed.csv):

governorate,wilayat,area,propertyType,bedrooms,bathrooms,sizeSqm,transactionType,priceOMR,rentPeriod,furnished,sourceType,sourceName,sourceRecordId,sourceUrl,observedAt,metadata
Muscat,Muscat,Al Mouj,apartment,2,2,135,rental,820,monthly,furnished,partner_feed,Gulf Realty Oman,GRO-2001,https://example-partner.test/units/2001,2026-08-01,"{""floor"": 6}"

JSON 格式(字段名与 CSV 标题行相同;参见 data/example-partner-feed.json):

{ "records": [ {
  "governorate": "Muscat", "wilayat": "Muscat", "area": "Al Mouj", "propertyType": "apartment",
  "bedrooms": 2, "bathrooms": 2, "sizeSqm": 135, "transactionType": "rental", "priceOMR": 820,
  "rentPeriod": "monthly", "furnished": "furnished", "sourceType": "partner_feed",
  "sourceName": "Gulf Realty Oman", "sourceRecordId": "GRO-2001",
  "sourceUrl": "https://example-partner.test/units/2001", "observedAt": "2026-08-01",
  "metadata": { "floor": 6 }
} ] }

每行必填:governorate、area(必须匹配受支持的马斯喀特区域,可使用英文或阿拉伯文名称)、propertyType(apartment/villa/townhouse)、sizeSqm、transactionType(rental/sale)、priceOMR、rentPeriod(rental 必填,sale 时必须留空)、sourceType、sourceName、observedAt。可选:wilayat、bedrooms、bathrooms、furnished、sourceRecordId、sourceUrl、metadata(小型 JSON 对象)。切勿包含 partnerId 列——行模式严格,遇到无法识别的列会直接拒绝,因此文件甚至无法尝试声明合作伙伴身份;归属信息仅来自经过身份验证的 X-Partner-Token(或者通过 CLI 执行批量导入时,由操作员明确提供的合作伙伴 ID),并会静默覆盖行中声称的任何 sourceType/sourceName。这是有意为之:合作伙伴自己的文件绝不能冒充其他合作伙伴,也不能声称尚未获得的来源资质(例如 official_statistics)。为通过模式验证,sourceType/sourceName 仍是必填列——请填写你自己的值(通常为 sourceType=partner_feed、sourceName=<你的公司名称>),即使服务端会覆盖这些值,也应如此填写,以便单独检查文件时内容保持一致。

验证——“拒绝不可能或可疑的值”,而不只是格式错误的值。 除类型/枚举检查外,还会执行应用级范围限制(比数据库自身较宽松的 CHECK 约束更严格;后者仍作为最后一道安全保障):sizeSqm 必须在 10 到 3,000 之间;出售房产的 priceOMR 必须在 3,000 到 20,000,000 OMR 之间;出租房产的月等值价格(年租金先除以 12)必须在 30 到 15,000 OMR 之间;observedAt 不得晚于未来一天,也不得早于 2000 年。任何不符合这些条件的行都会被单独拒绝,并附上通俗易懂的原因,与格式错误或区域无法识别的行处理方式完全相同。

去重策略。 基于 (sourceName, sourceRecordId) 执行幂等 upsert——沿用基础导入流程已有的同一个部分唯一索引和 INSERT ... ON CONFLICT ... DO UPDATE。由于 sourceName 始终会被强制设为经过身份验证的合作伙伴自己的名称,实际效果等同于 (partnerId, sourceRecordId):再次发送相同的 sourceRecordId(无论是在之后的请求中,还是在同一批次中发送两次),都会更新现有记录,而不会创建重复记录。缺少 sourceRecordId 的行无法与之前的导入安全匹配,因此始终会作为新记录插入——这与基础流程中的说明完全一致。 数据质量评分 — 确定性计算,绝不使用大模型。 每条已接受的记录都会获得一个 [0, 1] 范围内的 dataQualityScore(domain/oman/dataQualityScore.ts),该分数是固定的加权总和,每次都以相同方式计算:可选字段的完整度 — 卧室数/浴室数/是否带家具/来源 URL/元数据(30%);面积合理性(15%);价格合理性(20%);数据新鲜度 — 线性衰减,在两年前的记录处降至 0(20%);以及来源身份 — 经过身份验证的合作伙伴或官方统计数据为 1.0,人工基准数据为 0.7,未注明来源的房源为 0.5(15%)。导入响应中的 averageDataQualityScore 是批次平均值;CLI 和 HTTP 路径采用相同方式计算并存储该值。

合作伙伴数据源统计信息。 data_partners 会按合作伙伴跟踪 recordsReceived/recordsAccepted/recordsRejected/recordsUpdated/latestObservationDate,并在每次导入后更新(HTTP 或 CLI)——有关基于此构建的完整监控/健康状态/审计功能,请参阅下文的 合作伙伴运营层。

GET /api/v1/internal/market-data/status(X-Internal-Api-Key)返回的内容严格如下:

{ "records": 1250, "rentalRecords": 740, "saleRecords": 510, "areas": 8, "partners": 3, "latestDataDate": "2026-08-05T00:00:00.000Z", "sourceTypes": ["partner_feed", "listing_asking_price", "official_statistics"] }

此处仅提供跨合作伙伴汇总信息 — 绝不会泄露任何单个合作伙伴的令牌、摘要、统计信息或身份(这些详细信息位于 .../partners 之后,并由同一个密钥控制访问)。

来源溯源。 analyze_oman_property 现有的 provenance/dataQuality.sourceTypes 分组(代码未更改 — services/omanProperty.ts)已通过 (sourceType, sourceName) 区分 partner_feed、official_statistics、manual_benchmark 和 listing_asking_price,因为每个 MarketRecord 原本就包含 sourceType/sourceName;此层只需确保合作伙伴导入的记录在写入时如实标注来源,上文所述的强制服务端归因即可保证这一点。

接入首个阿曼房地产数据合作伙伴 — 快速指南(完整且可执行的检查清单见 docs/FIRST_PARTNER_ONBOARDING.md):

  1. 通过线下方式与合作伙伴商定数据许可/联系人引用信息(已签署的数据共享协议、合同编号)——这些信息将成为 dataLicenseReference/contactReference,绝不能填写个人姓名或电话号码。
  2. 在运营人员自己的机器上签发合作伙伴凭证(除最初交付外,绝不要通过聊天/电子邮件以明文发送):
    npm run admin -- partner:create gulf-realty-om "Gulf Realty Oman" csv partner_feed "Data License #2026-014" "[email protected]"
    
    这会显示新的合作伙伴记录,以及只显示一次的持有者令牌——请立即将其存入合作伙伴的密钥管理器;之后无法再次取回(如果令牌泄露,请轮换令牌 — 见下文的 令牌轮换)。
  3. 生成合作伙伴入驻资料包并交付(见下文的 入驻资料包)——其中会说明端点、标头、格式和规则,但绝不会包含真实令牌。
  4. 合作伙伴可以直接从自己的系统调用端点(feedType 为 http_feed),也可以将文件交给 Rafid,由运营人员执行批量导入并将数据归因于该合作伙伴:
    npm run market:import -- ./gulf-realty-export.csv gulf-realty-om
    
  5. 使用 GET /api/v1/internal/market-data/status(汇总)、GET /api/v1/internal/market-data/partners(按合作伙伴查看健康状态)和 GET /api/v1/internal/market-data/partners/:partnerId/quality(数据质量摘要)监控入驻情况;这些接口均由 MARKET_DATA_INTERNAL_API_KEY 保护。
  6. 如需暂停合作伙伴但保留其历史记录,请运行 npm run admin -- partner:disable gulf-realty-om(其令牌会立即停止通过身份验证);运行 partner:enable 即可恢复。 测试。 tests/partner-feed.test.ts 覆盖合作伙伴身份验证(有效/未知/已禁用/已轮换的令牌)、CSV 导入、JSON 导入(数组和 {records:[...]} 形式)、幂等 upsert(跨请求、同一批次内,以及真正的批次内重复项)、无效/被拒绝的行及其代码、确定性数据质量评分和各合作伙伴的质量摘要、合作伙伴来源信息传递至 analyze_oman_property、过期合作伙伴检测(包括某个过期合作伙伴不会导致其他合作伙伴或市场被标记为过期)、拒绝未经授权的导入、令牌轮换、入驻包生成、导入审计日志,以及 /api/v1/internal/market-data/status/.../partners 响应的确切结构——所有测试均使用内存仓库;此外还提供可选的 PostgreSQL 集成测试(通过 TEST_DATABASE_URL 启用,遵循 tests/market-data.test.ts 的模式),用于针对实时数据库测试迁移架构、PostgresPartnerRepository/PostgresPartnerIngestionAuditRepository,以及 admin/market:import CLI 命令。npm run test:partner-e2e(scripts/partner-e2e.ts)是独立、自包含的端到端演练,覆盖完整的首个合作伙伴流程——创建、签发令牌、导入、重新导入(幂等性)、检查运行状况、运行 analyze_oman_property、禁用并验证之后的导入会被拒绝——仅使用内存仓库,因此无需数据库或生产环境密钥,也可在 CI 中运行。
合作伙伴运营层(入驻包、令牌轮换、导入审计、运行状况监控、过期策略)

该层直接构建于上文的合作伙伴数据源层之上,旨在让真实合作伙伴的入驻和运营安全且实用,同时不增加任何新的公开能力、仪表板或抓取功能。此处的所有内容要么是现有的、受内部密钥保护的 JSON 路由,要么是由运营人员运行的 CLI 命令——不会在 domain/capabilities.ts 中注册任何内容。

入驻包。 npm run admin -- partner:package <partnerId> [baseUrl](domain/oman/partnerPackage.ts)会写入 partner-packages/<partnerId>/{README.md, sample.csv, sample.json, schema.json, curl-example.txt}(已加入 git 忽略)——其中包含完整且独立的说明,介绍端点、必需标头、CSV/JSON 格式、字段含义、允许的房产/交易类型、rentPeriod/observedAt 规则、批次大小上限、拒绝代码,以及重试/幂等行为。绝不包含合作伙伴的真实令牌——所有示例均使用 <PARTNER_TOKEN> 占位符;真实令牌应通过适合传递凭据的渠道单独发送。

令牌轮换。 npm run admin -- partner:rotate-token <partnerId> 会立即使当前令牌失效(通过单条 UPDATE ... SET token_digest 完成,不存在新旧令牌同时有效的窗口期),恰好签发一个新令牌,将其显示一次,并在 data_partner_audit_log 中记录 token_rotated 事件(created/enabled/disabled 事件也会记录)。

导入审计日志。 partner_ingestion_audit(db/partnerOpsSchema.ts,迁移 v3;domain/oman/partnerAudit.ts 中的 PartnerIngestionAuditRepository)会为每次到达已通过身份验证的合作伙伴处的导入尝试准确记录一行,无论成功或失败:id, partnerId, requestId, receivedAt, recordsReceived, recordsAccepted, recordsRejected, recordsUpdated, httpStatus, durationMs, errorCode。它绝不存储合作伙伴的令牌、原始请求正文或任何单条记录的字段——该行结构根本没有空间存放这些内容。下文运行状况端点中的时间窗口统计数据正是由此提供,无需重新读取任何敏感信息。

合作伙伴运行状况。 GET /api/v1/internal/market-data/partners(X-Internal-Api-Key)现在会为每个合作伙伴返回:

{ "partnerId": "gulf-realty-om", "partnerName": "Gulf Realty Oman", "enabled": true, "feedType": "csv",
  "latestIngestionAt": "2026-09-20T08:12:03.000Z", "latestObservationDate": "2026-09-18T00:00:00.000Z",
  "recordsAcceptedLast24h": 42, "recordsAcceptedLast7d": 310, "rejectionRateLast7d": 1.3,
  "stale": false, "staleDays": 7 }

latestIngestionAt(审计日志中最近一次摄取尝试的时间)与 latestObservationDate(PartnerFeedStats 中最新已接受记录的 observedAt,与之前相同)刻意区分——合作伙伴的 latestIngestionAt 可能非常近,但每次尝试都被拒绝;这正是运营人员需要了解的情况。绝不暴露令牌、摘要、联系详情或许可证引用。

过期策略。 PARTNER_FEED_STALE_DAYS(默认值为 7;替代上一阶段的 MARKET_PARTNER_STALE_DAYS,其默认值为 30)——当阈值内没有收到已接受的新鲜观测数据时,合作伙伴即视为过期。过期状态始终按合作伙伴单独计算——一个合作伙伴过期绝不会导致其他合作伙伴或整个市场被标记为过期;也绝不会限制数据摄取或 analyze_oman_property。

拒绝代码。 每条被拒绝记录的 code 都是以下之一:INVALID_FORMAT、INVALID_AREA、INVALID_PROPERTY_TYPE、INVALID_TRANSACTION_TYPE、INVALID_SIZE、INVALID_PRICE、INVALID_BEDROOMS、INVALID_BATHROOMS、INVALID_RENT_PERIOD、INVALID_FURNISHED、INVALID_SOURCE_TYPE、INVALID_SOURCE_NAME、INVALID_URL、INVALID_DATE、INVALID_METADATA、DUPLICATE_RECORD。随附的 message 是安全、易读的摘要——绝不会回显被拒绝记录的原始值。

数据质量摘要。 GET /api/v1/internal/market-data/partners/:partnerId/quality(X-Internal-Api-Key)返回 { partnerId, recordCount, averageDataQualityScore, percentageAbove80, percentageBelow50, missingBedroomRate, missingBathroomRate, missingFurnishedRate },通过数据库端聚合计算(PostgresPropertyMarketRepository.getPartnerQualitySummary() 中的单条 SELECT ... count(*) FILTER (WHERE ...) 查询),而不是将所有记录读入内存。

安全性。 内部 API 密钥使用 timingSafeEqual 进行比较(与上一阶段相同);合作伙伴令牌身份验证通过 SHA-256 摘要查找,而非比较原始令牌,因此可能受到时序攻击的值已经是加密摘要,而不是秘密本身。已停用的合作伙伴会立即无法通过 X-Partner-Token 身份验证(已有测试);已轮换掉的令牌也会立即失效(新增测试)。内部 API 密钥、任何合作伙伴令牌以及任何原始摄取载荷都绝不会被记录——请求日志记录器只记录匹配的路由模板、状态和耗时——而摄取审计表的行结构从设计上就排除了这三者。

首个合作伙伴清单。 请参阅 docs/FIRST_PARTNER_ONBOARDING.md,了解完整的分步运营清单:确认数据权利、创建合作伙伴、生成入驻包、安全地交付令牌、发送测试批次、核验已接受/已拒绝记录及其来源、确认 analyze_oman_property 显示 partner_feed(且仅由合作伙伴数据提供信息时不显示演示数据标记)、监控首个 24 小时,然后启用合作伙伴的生产计划。

生产数据源运行器(定时 HTTPS JSON/CSV 摄取——不进行抓取,不新增公开功能)

直接构建在上述合作伙伴运营层之上,使首个真实的阿曼合作伙伴数据源可以按计划运行,而不必由人工手动推送文件。与合作伙伴数据源/运营层中的其他所有内容一样,这属于基础设施——domain/oman/partnerFeedRunner.ts、其 CLI 入口及其数据库列/表绝不会在 domain/capabilities.ts 中注册,也不会出现在 /agent.json、工具目录、远程 MCP 或 x402 路由系列中。它绝不会触及 analyze_oman_property 本身、x402 定价或任何其他公开功能。 Feed 配置模型。 data_partners 上新增 6 个全部可选的列(迁移 v4,db/partnerFeedSchema.ts):feedUrl、feedFormat("json" | "csv")、scheduleEnabled、scheduleIntervalMinutes、lastSuccessfulRunAt、lastAttemptAt,以及 consecutiveFailures。在此迁移之前创建的合作伙伴都没有配置计划——无需回填。使用 npm run admin -- partner:set-feed <partnerId> <feedUrl> <json|csv> <true|false> [intervalMinutes] 设置——每次都完整替换计划配置(与 setEnabled() 的“单个显式值”简洁性一致),绝不进行部分更新。

安全模型(凭据)。 Feed 身份验证绝不存储在 PropertyDataPartner 本身。单独的表 partner_feed_credentials(同样属于迁移 v4;每个合作伙伴一行:authType — bearer | api_key_header | none — 以及 secretRef 和 headerName)仅存储凭据的形式。secretRef 是一个引用——环境变量的名称——绝不是密钥值;实际值仅在抓取时通过可注入的 SecretProvider 抽象(domain/oman/partnerFeedCredentials.ts)解析到内存中,并且只在单次出站请求期间存在。随附的 EnvSecretProvider 会读取 process.env[secretRef];未来的部署可以换用由真正的密钥管理器支持的 Provider,而无需改动 Feed 运行器或凭据架构。使用 npm run admin -- partner:set-feed-credential <partnerId> <bearer|api_key_header|none> [secretEnvVarName] [headerName] 设置——此命令的参数中绝不会包含密钥的实际值,只包含保存该值的环境变量名称。

SSRF 控制。 每个 Feed URL——以及每个重定向目标,在跟随前都要单独重新验证,绝不盲目信任——都必须通过 domain/oman/feedSecurity.ts 的 validateFeedUrl():默认仅允许 HTTPS(仅供测试/开发使用的 allowInsecureHttp 例外不受合作伙伴影响);拒绝 localhost/*.localhost/0.0.0.0;拒绝私有、环回、链路本地和保留的 IPv4 与 IPv6 地址范围(既检查字面地址,也通过可注入的 HostResolver 对主机名进行 DNS 解析,因此无需真实 DNS 即可进行完整的单元测试);并支持可选的精确主机名允许列表。domain/oman/safeFeedFetch.ts 在此基础上实现有界 HTTP GET:设置超时(AbortController)、限制流式响应大小(超出限制时中止下载,而不是先缓冲超大响应体),并手动处理重定向(redirect: "manual"),这样绝不会静默跟随到不同不安全主机的重定向。

重试策略。 domain/oman/feedRetry.ts 仅在以下情况重试:网络/连接错误、请求超时,或 HTTP 502/503/504——采用有界指数退避(200ms、400ms、800ms,最高 4s),最多总计尝试 3 次。400、401、403、404 或任何验证/解析失败都不会重试——重复已经错误的请求只会得到相同的失败结果。

调度模型。 computeNextDueAt()/isFeedDue()/computeFeedHealth()(domain/oman/partners.ts)是纯粹的无状态函数,供调度过程和健康端点共用,因此“哪些合作伙伴已到期”和“健康端点报告什么”绝不会出现不一致。尚未尝试过的合作伙伴会立即到期,而不是从“现在”起等待完整的间隔时间。 CLI 命令。

  • npm run partner:feeds — 执行一次调度轮次:识别所有已启用且到期的合作伙伴,安全地逐一运行,打印安全的 JSON 摘要({"attempted": N, "successful": N, "failed": N, "results": [...] }),然后退出。绝不会作为持续运行的进程——此命令供外部调度器使用(Vercel Cron、GitHub Actions 的 schedule: 触发器、Windows Task Scheduler、普通 cron)。
  • npm run admin -- partner:run-feed <partnerId> — 按需单次运行,用于在启用合作伙伴的调度前进行测试。只打印 {partner, httpStatus, recordsReceived, recordsAccepted, recordsRejected, recordsUpdated, durationMs, auditId, ok, errorCode} ——绝不会输出解析后的凭据标头或原始记录。
  • npm run admin -- partner:set-feed / partner:set-feed-credential — 见上文。

故障隔离(第 7 节)。 PartnerFeedRunner.runDueFeeds() 会将每个合作伙伴的运行分别置于独立的 try/catch 中——无论某个合作伙伴以何种方式失败,都绝不会中止循环或影响任何其他合作伙伴的运行。返回 {attempted, successful, failed, results}。

数据安全。 Feed runner 调用的 importMarketRecords() 与其他所有数据导入路径(手动 HTTP 端点、market:import)使用的完全相同——绝不存在重复或并行的验证/upsert 实现。现有的幂等 upsert 规则、拒绝代码以及范围有限的 DUPLICATE_RECORD 语义均完全不变。被拒绝的批次绝不会删除现有记录;绝不会记录原始合作伙伴数据;凭据在所有位置均通过结构设计得到脱敏(从结构上不存在存储或记录机密值的代码路径)。

Feed 健康状态字段。 GET /api/v1/internal/market-data/partners 现在还会为每个合作伙伴返回:lastFeedAttemptAt、lastFeedSuccessAt、consecutiveFailures、nextDueAt 和 feedHealth("healthy" | "degraded" | "stale"——先检查 consecutiveFailures >= 3,符合时返回 "degraded";否则若该合作伙伴现有的 stale 标记为真,则返回 "stale";否则返回 "healthy")。此端点上的所有现有 Partner Operations 字段均保持不变。某个合作伙伴的 Feed 健康状态绝不会影响任何其他合作伙伴,也不会全局影响 analyze_oman_property 的可用性。

可观测性。 每次运行完成(无论成功还是失败)都会输出一行结构化 JSON 日志:{partnerId, requestId, attempt, status, httpStatus, durationMs, recordsReceived, recordsAccepted, recordsRejected} ——不包含原始记录、令牌或身份验证标头;每个字段都通过结构设计确保安全,无需再脱敏。

测试。 tests/partner-feed-runner.test.ts 完全通过模拟 HTTP 进行测试(注入 fetchImpl 和 HostResolver——不进行真实网络请求或 DNS 查询):JSON Feed 成功、CSV Feed 成功、HTTP 超时、HTTP 503 重试后成功及重试至耗尽(调用次数恰好为 maxAttempts,绝不更多)、HTTP 401 不重试(恰好调用一次)、响应超出大小限制、意外的内容类型、格式错误的正文、拒绝 localhost(SSRF 防护)、拒绝解析到私有 IP 的主机名(SSRF 防护)、绝不跟随指向受阻主机的重定向、runDueFeeds() 中的合作伙伴隔离、幂等重新获取(无重复记录)、每次尝试写入一条包含安全字段集的审计记录、consecutiveFailures/feedHealth 状态转换(包括下次成功时重置为 0)、机密信息脱敏(向模拟 Feed 端点发送假机密,但其绝不会出现在运行结果、日志或审计记录中)、凭据配置错误,以及三种操作员配置错误保护情形(找不到合作伙伴/已禁用/未配置),这些情况会抛出异常,而不会悄然记录审计失败。 首个真实合作伙伴运行手册。 完整的分步清单请参见 docs/FIRST_LIVE_PARTNER_RUNBOOK.md:创建合作伙伴、生成令牌/软件包、与合作伙伴商定数据源 URL/身份验证方式、配置密钥、使用 partner:run-feed 进行测试、检查审计日志和数据质量、验证来源、运行 analyze_oman_property、确认没有演示标记、启用计划任务,并监控前 24 小时。

官方市场背景(NCSI)

analyze_oman_property 的响应包含一个 officialMarketContext 对象,数据来自阿曼国家统计与信息中心(NCSI)开放数据门户 API——这是此功能实现的首个、迄今唯一一个实时外部网络集成。它有意不作为另一种可比数据源:NCSI 发布的是省级汇总统计数据(价格指数、交易额、合同数量),从不发布单个房源或交易记录,因此此集成绝不会将 NCSI 数据输入 comparablesSummary、market、pricePosition 或基于可比数据计算的 confidence 分数——如上文所述,这些内容始终保持不变,无论是否配置 NCSI。officialMarketContext 有自己独立的 confidence(unavailable/low/medium/high),以免 Agent 将“存在官方统计数据”误认为“可比数据估算更可信”。

Rafid 不会抓取 NCSI 或任何其他房产/统计网站。 此集成调用的是 NCSI 自己有文档说明的 REST API(services/ncsi/ncsiClient.ts)——由 map.ncsi.gov.om/ODPAPI 提供的“Explore Api”,除此之外不会调用其他内容。NCSI 的数据依据《阿曼苏丹国开放政府许可》发布,该许可允许通过 API/Web 服务复用数据;此集成仅读取 NCSI 自己发布的汇总统计数据,并始终将数据来源标注为 NCSI(officialMarketContext.provenance.source/license)——Rafid 不主张拥有 NCSI 数据的所有权。

实际经过实时验证的内容,以及未验证的内容(配置前请阅读): 编写 NcsiClient 之前,我们直接检查了实时 API,而不是靠猜测。我们成功获取了其 OpenAPI/Swagger 文档(https://map.ncsi.gov.om/ODPAPI/swagger/v1/swagger.json),确认“Explore Api”v1 确实可用、无需身份验证,且提供的端点与此客户端实现的完全一致:GET /catalog/datasets(列表)、GET /catalog/datasets/{id}(元数据)、GET /catalog/datasets/{id}/records(查询,支持 Select/Where/order_by/offset/limit/language 参数),以及 GET /catalog/datasets/{id}/download。但是,开发期间实际调用目录列表或记录端点的每次尝试都返回了 HTTP 500——包括使用一个故意不存在的数据集 ID 发出的请求;正常运行的 API 应返回 404,而不是 500。这表明当时实时后端暂时故障,而不是缺少某个参数或数据集 ID。api.ncsi.gov.om(此 API 常被提及的第二个 URL)仅提供了一个空白的 Swagger UI 外壳,在任何标准路径下都找不到可用的规范或数据路由。因此,无法通过实时数据验证任何真实的数据集 ID 或记录字段名称,所以此代码库中没有任何地方硬编码这些信息。

因此,在运维人员完成验证之前,配置会有意保持不生效:

  1. 运行 npm run ncsi:discover(重试实时目录,并筛选包含房地产关键词的标题/描述,支持英语和阿拉伯语)。如果运行时 NCSI 的目录后端运行正常,该命令会输出候选数据集 ID/标题;如果像开发期间那样返回 500 错误,它会明确说明,而不会假装找到了数据集。
  2. 确认数据集 ID 后,检查 GET /catalog/datasets/{id}/records 的实际响应(通过发现命令的输出、curl 或联系 NCSI 支持),以查找省份、时期、价格指数、交易金额以及买卖/抵押合同数量对应的实际字段名称。
  3. 将 NCSI_REAL_ESTATE_DATASET_ID 设置为该 ID,并将 NCSI_FIELD_MAP_JSON 设置为 JSON 对象,把此功能涉及的概念映射到对应的确切字段名称,例如:
    NCSI_REAL_ESTATE_DATASET_ID=real-estate-price-index
    NCSI_FIELD_MAP_JSON={"governorate":"GOVERNORATE_EN","period":"PERIOD","priceIndexValue":"PRICE_INDEX","tradedValueOMR":"TRADED_VALUE","saleContracts":"SALE_CONTRACTS","mortgageContracts":"MORTGAGE_CONTRACTS","publishedAt":"LAST_UPDATED"}
    
    只有 governorate 是必需的;映射中省略的任何概念都会在响应中报告为 null,而不会进行猜测——“仅填充检索到的数据实际支持的字段”是通过结构强制执行的,而非约定俗成。

在这两项都设置之前,officialMarketContext.available 始终为 false,且 reason: "ncsi_not_configured",不会发起任何网络调用——这也是 /openapi.json 和 MCP 工具目录当前展示的示例输出。

**失败行为:**超时、5xx、格式错误的响应,或没有与所请求省份匹配的记录,都会使 officialMarketContext.available: false,并返回明确的 reason(ncsi_timeout / official_source_temporarily_unavailable / ncsi_malformed_response / no_data_for_governorate)。绝不会编造备用数据,也绝不会导致整个 analyze_oman_property 调用失败;基于可比数据的分析始终会独立完成(参见 tests/ncsi.test.ts 中的“an NCSI outage does not prevent the comparable analysis from completing”)。

**缓存:**成功的查询会按省份缓存(domain/oman/officialContextCache.ts 中的 MemoryOfficialMarketContextCache),缓存时间为 NCSI_CACHE_TTL_MS(默认 6 小时,因为官方统计数据按季度/年度发布,而非持续更新)。失败的查询绝不会缓存,因此下一次调用会重试 NCSI,而不是在整个 TTL 期间固定返回“不可用”。

测试:tests/ncsi.test.ts 使用模拟 HTTP 层(可注入 fetchImpl,绝不会访问网络)测试目录/记录解析、由字段映射驱动的解析(缺失指标、过期记录)、所有失败模式(超时/5xx/4xx/格式错误的响应)、缓存命中/过期/失败时不缓存,以及完整流水线集成(NCSI 不可用与运行正常、数据集记录数为零、不支持区域的提前返回路径)。这些测试都不依赖 NCSI 实时服务是否可用。另有一个独立的可选测试 npm run test:ncsi-live(需在 .env 中设置 NCSI_LIVE_TEST=1 才会运行,与 test:db 需连接实时数据库才能运行完全相同),可在需要重新验证时对真实 API 进行冒烟测试。

**已知限制:**由于开发期间无法成功查询实时目录,此集成尚未使用任何真实 NCSI 记录进行验证——以上内容均依据已记录的接口约定和模拟响应验证,而非实时数据负载。待 NCSI 后端恢复正常(或你直接从 NCSI 获取数据集 ID/字段列表)后,重新运行 npm run ncsi:discover 和 npm run test:ncsi-live,即可完成此集成针对真实数据的端到端验证。

统一计费:API 额度、订阅和所有支付方式

付费功能可通过任何已启用的支付方式购买,而功能本身无需知道由哪种方式付款:x402(USDC,无需账户)、Rafid API 密钥上的预付 API 额度和订阅额度(Authorization: Bearer raf_live_…——无需钱包),以及启用时可用的 L402 和 MPP。所有支付方式都按相同的规范 capability.price 计费。规范路由 POST /api/v1/<tool-path> 会以确定性方式选择支付方式(X-Rafid-Payment-Method: auto | credits | subscription | x402 | l402 | mpp),并将 x402/L402/MPP 凭证重新分发到对应的协议专用支付关卡。GET /api/v1/payment-methods 会列出已启用的支付方式;没有可用凭证的调用方会收到包含支付方式发现信息的 402 payment_required,而不是仅提供 x402 的答复。API 额度调用是原子操作,且可安全并发(PostgreSQL 行锁 + 条件更新,BIGINT 微美元),支持幂等(Idempotency-Key),完整记录到账本中,并在功能失败时退款。账户、密钥、额度和套餐可通过 npm run billing -- … 或受 BILLING_ADMIN_SECRET 保护的内部 API 管理;客户可读取 GET /api/v1/account/{balance,usage,transactions}。MCP 客户端可通过 POST /mcp/credits 使用 API 额度付款,也可在设置 RAFID_API_KEY 后通过本地 stdio 运行。只有设置 API_CREDITS_ENABLED / SUBSCRIPTIONS_ENABLED 后,这些功能才会启用。

完整参考:docs/billing.md。客户端示例:examples/api-credits-client。

通过 x402 按次付费(无需 API 密钥)

GET /api/v1/x402 始终可用,不受 X402_ENABLED 影响,并会返回协议、方案、当前是否接受付款、网络/收款地址(仅在启用时提供——绝非秘密,只是公开地址)、使用中的协调器,以及每个工具的价格/端点。Agent 可以在决定是否付款前读取这些信息,不会产生任何副作用。

GET /api/v1/x402/status 是一个更精简、始终可用的配套端点,其唯一用途是明确说明运行时事实,因此无需从本 README 或落地页文案中推断:{ enabled, mode, network, asset, facilitator, walletConfigured, paymentEnforcement }。mode 为 "disabled"、"testnet"(Base Sepolia——由公共协调器完成结算,但不会转移真实资产),或 "production"(任何其他已配置的网络,在该网络上,已验证的付款会产生真实的链上转账)。enabled 和 paymentEnforcement 按设计始终相同:只有在 X402_ENABLED=true 时,此应用才会挂载真正的 @x402/express 支付关卡;如果关卡所需的配置缺失或无效,loadConfig() 会在启动时直接失败——应用运行时不存在 X402_ENABLED=true 但关卡实际上未执行付款强制校验的状态。绝不包含任何秘密。

当 X402_ENABLED=true 时,相同的四项功能也可通过 /api/v1/x402/... 无需身份验证地使用:

方法端点价格
POST/api/v1/x402/property/analyze$0.01
POST/api/v1/x402/property/compare$0.03
POST/api/v1/x402/maintenance/estimate$0.02
POST/api/v1/x402/oman/property/analyze$0.25
这些路由接受与上文对应的身份验证路由相同的 JSON 请求/响应正文,但完全跳过 X-API-Key 标头。授权方式改为有效的链上付款:请求未携带 X-PAYMENT 标头(或付款无效/金额不足)时,会收到 HTTP 402 响应。根据 x402 v2 协议,可接受的付款选项(accepts、价格、网络)包含在经过 base64 编码的 JSON PAYMENT-REQUIRED 响应标头中,而非 JSON 正文中(正文有意设为 {});解码该标头(JSON.parse(Buffer.from(header, "base64").toString()))即可读取这些信息。支持 x402 的 HTTP 客户端会自动处理此过程:为标价金额和网络附加有效的 X-PAYMENT 标头,随后请求便会正常进行。标示价格始终来自 BillingService.getToolPrice(),也就是目录 /api/v1/pricing 所读取的同一价格,因此 x402 网关不会与公布的价格产生偏差。

付款以 USDC 在 X402_NETWORK 指定的网络上结算(默认值:Base Sepolia 测试网,eip155:84532)。默认情况下,验证/结算通过公共 facilitator X402_FACILITATOR_URL(https://x402.org/facilitator,无需账户)完成——但这个免费的 facilitator 仅为 EVM「exact」付款提供 Base Sepolia 结算。若要使用其他网络(包括 Base 主网,eip155:8453),请设置 CDP_API_KEY_ID 和 CDP_API_KEY_SECRET(可从免费的 Coinbase Developer Platform 账户获取,位于 API Keys 下;需要 Ed25519 Secret API Key,其下载的 JSON 文件包含 id/privateKey 字段,分别对应 CDP_API_KEY_ID/CDP_API_KEY_SECRET):两者均已设置时,服务会通过官方 @coinbase/x402 软件包改为向 CDP 的 facilitator(https://api.cdp.coinbase.com/platform/v2/x402)进行身份验证,并忽略 X402_FACILITATOR_URL。如果 X402_NETWORK 不是 eip155:84532,且未同时设置 CDP 变量,启动会立即失败并显示明确错误,而不会接受无法实际结算的付款请求。X402_WALLET_ADDRESS 是收款钱包;在生产环境启用此功能前,必须将其设为你控制的地址。默认测试网适合开发,不会转移真实资金。这些路由与 API 密钥/PostgreSQL 客户系统完全分离:x402 调用不会创建或查询客户账户、配额或用量记录(不过,无论调用哪类路由,每次调用仍会通过 UsageRepository 记录,详见下文)。停用 X402_ENABLED 后,这些路由将被完全移除:访问时会返回 404,且不会出现在 /openapi.json 和 / 发现响应中;无论此标志如何设置,常规 API 密钥调用都完全不受影响。 **当前生产状态——准确说明,而非笼统地称为“已上线”:**支付门控是真实运行的,通过 Coinbase CDP facilitator 在 Base 主网上(eip155:8453)提供服务——生产环境已返回真实的 HTTP 402,并附带正确的条款(payTo 与配置的 X402_WALLET_ADDRESS 一致,网络为 eip155:8453,价格与目录一致)。只有在真正挂载并配置了 @x402/express 中间件,且该中间件确实与真实的 facilitator 通信时,才可能出现这种情况。目前尚未在生产环境中验证的是已完成的主网支付是否能一路结算并触发工具执行——这需要发送真实的 USDC。Base Sepolia 测试网不会发生真实价值转移,因此已完成全流程验证(一个实际注资的测试钱包通过公开的 facilitator 完成了一笔真实支付)。在真正完成并观察到一笔主网支付之前,落地页会有意使用“x402 ready”而非“live”,原因就在于此;GET /api/v1/x402/status 才是检查当前强制执行状态的权威来源,不要轻信任何文字描述(包括本 README)。无论哪种实时支付流程,都没有自动化的 CI 覆盖(实时测试需选择启用:RUN_X402_LIVE_TESTS=true npm test,且需要能够访问 facilitator 的真实互联网连接)。

通过 L402 按次付费(Lightning,无需 API 密钥)

除 x402 外的另一条独立支付通道,适用于通过 Lightning Network 使用比特币支付的 Agent,实现了 Lightning Labs 的 L402 protocol(前身为 LSAT)。除非设置 L402_ENABLED=true,否则该功能关闭;关闭时,/api/v1/l402/<tool> 会返回 404,且不会列入 /openapi.json、/、/agent.json 和 /llms.txt。GET /api/v1/l402(各工具的条款和美元价格)和 GET /api/v1/l402/status({ enabled, mode, network, asset, backend, lightningBackendConfigured, rootKeyConfigured, paymentEnforcement },绝不包含密钥)始终可用。

每项功能的流程(如 POST /api/v1/l402/property/analyze、/api/v1/l402/intelligence/research-company 等):

  1. 不带 Authorization 标头发起调用 → 返回 402,并附带 WWW-Authenticate: L402 macaroon="<base64>", invoice="<bolt11>",JSON 响应正文中也包含相同的详细信息(l402.amountSats、priceUsd、btcUsdRate、tokenValidUntil)。
  2. 使用任意 Lightning 钱包支付发票,并保留 32 字节的 preimage。
  3. 使用 Authorization: L402 <macaroon>:<preimage-hex> 重试 → 返回 200 和正常的工具响应。
  • **价格:**工具的 USD 目录价格(BillingService.getToolPrice(),与 x402 和 /api/v1/pricing 使用相同的数据源),在发出质询时按实时 BTC/USD 汇率换算为 sats,并向上取整。汇率来源依次为:Coinbase 现货价、Kraken、最多 15 分钟前的缓存汇率,最后是(若已设置)L402_BTC_USD_FALLBACK。如果均不可用,该路由会返回 503 L402_PRICING_UNAVAILABLE;绝不会猜测价格。
  • **验证:**纯粹通过密码学手段完成,不会在付费路径上调用节点。系统会根据 L402_ROOT_KEY 检查 macaroon 的 HMAC 链。其 caveat(service、capability、expires、amount_sats、price_usd、btc_usd)必须全部存在且有效,并且 sha256(preimage) 必须等于签入 macaroon 标识符的支付哈希。Macaroon 使用标准 V2 二进制格式,并与 go-macaroon/js-macaroon 字节兼容,因此标准 L402 客户端无需修改即可使用。
  • **每笔付款调用一次:**工具运行前会以原子方式认领令牌(rafid_l402_redemptions,支付哈希为 PRIMARY KEY)。调用成功后令牌即失效。调用失败(4xx/5xx)时会释放令牌,让付款方可以重试已付费的调用。重复使用、已过期、遭篡改或与工具不匹配的令牌会收到新的 402 质询,以及说明原因的 X-L402-Error 标头;L402 客户端已能通过再次付款处理这种情况。
  • **Lightning 后端:**使用 LND REST(POST /v1/invoices),并通过权限最低的 invoice macaroon 进行身份验证;该 macaroon 无法花费资金。可与可自托管节点配合使用(为其自签名证书设置 LND_TLS_CERT),也可与 Voltage 等托管节点配合使用。如果无法访问节点,该路由会返回 503 L402_LIGHTNING_UNAVAILABLE;错误信息绝不会泄露节点主机名。
  • **Voltage Payments 后端(L402_BACKEND=voltage):**不使用 LND 节点,而是从托管的 Voltage Payments 钱包获取发票。Rafid 会发送 POST /organizations/{org}/environments/{env}/payments(payment_kind: "bolt11",金额以 msats 为单位,使用 x-api-key 标头),然后轮询 GET …/payments/{id},直到 BOLT11 发票就绪(最长约 12 秒)。系统使用内置且经过校验和验证的 BOLT11 解码器从发票本身读取支付哈希,并且如果发票编码的金额与请求金额不符,就会拒绝该发票。此后端需要 VOLTAGE_API_KEY、VOLTAGE_ORGANIZATION_ID、VOLTAGE_ENVIRONMENT_ID 和 VOLTAGE_WALLET_ID。测试时,请使用 staging(Mutinynet)环境,并设置 L402_NETWORK=signet。账本行会记录 facilitator: "voltage"。
  • **记账:**每个已兑换的令牌都会在与 x402 相同的收入账本中写入一条 settlement_succeeded 记录。记录包含 network: "lightning:<L402_NETWORK>"、currency/asset: "BTC"、以 sats 为单位的 amountAtomic、以 BTC 为单位的 amountDecimal、作为 transactionHash 的支付哈希(绝不会记录 preimage),以及 facilitator: "lnd"。系统会根据支付哈希去重。收入总额会将 BTC 和 USDC 分开统计,内部仪表板会将 BTC 显示到小数点后 8 位。对账会统计两种支付渠道的已付费执行次数(分析渠道 l402),并跳过 BTC 记录的 USD 价格检查。启动时会自动将 amount_decimal 列扩展为 numeric(24,10),不会损失现有记录的精度。
  • **生产环境要求:**配置 LND 设置(LND_REST_URL(https)、LND_INVOICE_MACAROON(十六进制))或设置 L402_BACKEND=voltage 并提供四项 VOLTAGE_* 设置;还需配置 L402_ROOT_KEY(openssl rand -hex 32),以及用于跨实例单次使用校验的 DATABASE_URL 或 L402_DATABASE_URL。如果缺少其中任一项,loadConfig() 都会拒绝启动。请参阅 .env.example。
MPP:机器支付协议(收费 + 按量计费会话,无需 API 密钥)

MPP 是第三种支付通道,与 x402 和 L402 并行运行,不会取代它们。MPP 是由 Tempo 和 Stripe 联合撰写的 Machine Payments Protocol。在传输层,它使用 IETF 的“Payment”HTTP 身份验证方案(WWW-Authenticate: Payment … / Authorization: Payment … / Payment-Receipt)。Rafid 使用官方 TypeScript SDK mppx(^0.11.0)实现该协议,不会重新实现协议本身。

除非设置 MPP_ENABLED=true,否则 MPP 默认关闭。关闭时,所有 /api/v1/mpp/* 支付路由都会返回 404 MPP_DISABLED,且不会列入 OpenAPI 和发现结果。以下两个路由始终可用:GET /api/v1/mpp(条款和各工具的价格)以及 GET /api/v1/mpp/status(不含密钥的运行时状态)。

通道说明支付单位
x402按请求付费,使用 Base 上的 USDC每次调用进行一笔链上支付
L402Lightning 按请求付费每次调用一张 BOLT11 发票
MPP charge一次性机器支付(Tempo 上的 tempo/charge,可选的 evm/charge = 通过现有 CDP facilitator 支付 Base USDC)每次调用一个 MPP 凭证
MPP session可重复使用的授权 + 计量式机器使用(tempo/session:TIP-1034 支付通道)每个会话存入一笔款项,每次调用使用一个签名凭证,最终结算一次
Agent
  |
  +-- x402 ------------ POST /api/v1/x402/<path>
  |
  +-- L402 ------------ POST /api/v1/l402/<path>
  |
  +-- MPP Charge ------ POST /api/v1/mpp/charge/{tool}
  |
  +-- MPP Session ----- POST /api/v1/mpp/sessions            (authorize: open channel, deposit = budget)
        |
        +-- Tool Call - POST /api/v1/mpp/sessions/{id}/tools/{tool}   (voucher → run → meter)
        +-- Tool Call
        +-- Tool Call
        |
        +-- Meter ----- GET  /api/v1/mpp/sessions/{id}            (spent / remaining / per-tool usage)
        |
        +-- Settlement  POST /api/v1/mpp/sessions/{id}/close      (settle exactly the metered spend)

架构。 代码位于 src/billing/mpp/,与领域服务分开。各项能力与协议无关:MPP 与 REST、x402、L402 和 MCP 一样,都运行 capability.execute()。

文件作用
config.ts解析 MPP_*;启动时以失败关闭方式处理
provider.tsMppProvider 接口(createCharge、verifyCharge、settleCharge、createSession、verifySession、sessionCallChallenge、verifySessionCall、recordUsage、settleSession、closeSession)
mppx.ts唯一与 SDK 通信的文件
service.ts编排
sessions.ts持久化
store.tsSDK 在 Postgres 上使用的 AtomicStore
metering.ts计量
settlement.ts收入账本记录
routes.ts、middleware.tsHTTP
mcp.ts可选的 MCP 绑定
audit.ts审计日志
openapi.tsOpenAPI 路径

以后要添加其他 MPP 支付方式(Stripe SPT、Solana 或其他 EVM 链),只需编写新的 provider 适配器。API 无需更改。

定价。 价格始终来自能力注册表(BillingService.getToolPrice()),因此 x402、L402 和 /api/v1/pricing 显示的数值一致。内部金额均为整数微美元,相当于 MPP 结算所用的 6 位小数 USD 稳定币的一个原始单位。服务器为权威来源:SDK 会自行推导各路由的支付请求,并拒绝金额、收款方、货币、realm、scope(rafid:charge:<tool>)或有效期不符的任何凭证。服务器不会读取客户端提供的价格、spent 或 remaining。

Charge 流程(POST /api/v1/mpp/charge/{tool}):

  1. 首先验证工具的 JSON 输入。输入无效时返回 400,且不会发送质询或进行支付。

  2. 如果没有凭证,响应为 402,并针对每种已配置的支付方式提供一个 WWW-Authenticate: Payment 质询。响应正文包含 { protocol: "mpp", mode: "charge", tool, amount, currency: "USD", paymentRequired: true, challenges, problem }。

  3. 如果存在凭证:

    • 验证凭证,但不消耗它(validateCredential)。
    • 将其声明为一次性凭证(mpp_charge_redemptions)。
    • 运行工具。
    • 只有工具成功后才结算支付(broadcastCredential)。响应包含常规工具输出、一个 payment 块和 Payment-Receipt 标头。
  4. 如果工具运行失败,则不结算支付,并释放该凭证的声明。

  5. 如果结算失败,则不返回结果(402 MPP_SETTLEMENT_FAILED)。这与 x402 遵循的规则相同。

  6. 重放的凭证会收到 402 MPP_PAYMENT_REPLAYED。 会话流程。 在实际协议中,会话是一个 Tempo 支付通道。

  7. 开启。 使用 { "maxBudget": 20, "currency": "USD", "allowedTools": [...] } 调用 POST /api/v1/mpp/sessions:

    • 不带凭证时,会创建一个 pending 会话(mpp_<32 hex>),并返回 402,要求付款方开启通道。建议存款额为预算金额。
    • 预算和工具会绑定到挑战的 HMAC'd meta 中。如果修改它们,请求将返回 409 MPP_TERMS_MISMATCH。
    • 使用开启凭证重试相同的请求正文,会在链上开启通道。响应为 201,会话状态为 active,且 maxBudget = min(requested, deposit)。
    • 一个开启凭证只能绑定一个会话。
  8. 调用。 使用 Idempotency-Key 标头调用 POST /api/v1/mpp/sessions/{id}/tools/{tool} 时,会按顺序执行以下步骤:

    1. 会话存在且处于活动状态。
    2. 工具已获准使用。
    3. 从注册表中读取价格。
    4. 验证输入。
    5. 以原子方式预留预算。SELECT … FOR UPDATE 加上数据库 CHECK (spent + reserved <= max_budget),可确保并发调用永远不会超支。若无法负担调用费用,则会在执行前返回 402 MPP_SESSION_BUDGET_EXCEEDED { required, remaining }。
    6. 验证付款方的凭证,但不消耗它。若没有凭证,响应会返回 402,并附上此通道的凭证挑战。
    7. 执行工具。
    8. 仅在成功时,才接受凭证并将价格计入通道(tempo.session.charge())。
    9. 更新 spent、remaining、calls 和各工具的使用情况。

    响应包含常规工具输出,以及 usage: { sessionId, tool, charge, spent, remaining, calls, status }(保留在 data 之外)。工具调用失败时绝不会收费。使用相同的 Idempotency-Key 重试会返回已存储的结果,且不收费。相同密钥搭配不同工具或输入时,会返回 422 MPP_IDEMPOTENCY_CONFLICT。

  9. 计量。 GET /api/v1/mpp/sessions/{id} 会返回状态、spent、remaining、calls、usageByTool 以及通道视图(存款、已接受的凭证、计量金额、链上结算状态)。

  10. 关闭。 POST /api/v1/mpp/sessions/{id}/close 是幂等的。它会停止调用、完成计量并进行结算:

    • 使用付款方的通道 close 凭证时,通道会在链上关闭,并精确扣取计量支出金额,其余存款将退还。响应包含 SDK 的 Payment-Receipt。mppx 的 sessionManager.close() 会将此凭证作为不带正文的 POST 发送到上次使用的 URL(通常是工具路由),因此每个会话路由都会在解析正文前接受 close 凭证,并且绝不会因此运行工具。topUp 凭证会被拒绝(400):请改为开启新会话。
    • 若没有此凭证,服务端会调用 tempo.session.settle(),但仅当已签名凭证等于计量支出时才会调用。该函数会扣取已签名凭证的金额,因此若这会导致超额扣取,就绝不会调用它。否则,settlement.status 为 pending_payer_close。
    • 结算金额只会记入收入账本一次,记为 toolName: "mpp_session"。各工具的对账会排除此类记录,因为它们并非逐次调用结算。
  11. 状态。 会话状态可以是 pending、active、exhausted、closed、expired(MPP_SESSION_TTL_SECONDS)或 failed。会话不处于 active 状态时会拒绝调用:exhausted → 402,closed → 409,expired → 410。

  12. 结算状态。 settlement.status 可以是以下其中一种:

    状态含义
    not_started尚未尝试
    pending某次尝试持有租约(MPP_SETTLEMENT_LEASE_SECONDS),或结果未知(广播后 RPC 失败)。绝不会报告为成功
    pending_payer_close没有安全的服务端结算方式;只有付款方的关闭凭证才能准确扣除消费金额
    settled结算/关闭交易的收据已确认,或链上已显示 settled ≥ spent
    nothing_to_settle没有计量费用
    failed明确失败(settlement_error 保存原因);协调器会重试

    每次尝试都会先通过条件式 UPDATE 认领该行(租约),因此并发关闭和维护运行绝不会同时提交交易。链上结算采用累计方式,因此即使在结果未知后重试,也不会重复扣款;账本只记录链上报告的差额。结算引用(交易哈希)、尝试次数和最近一次错误都会保存在会话中。

待处理会话清理与滥用限制。

  • 待处理会话会在质询 TTL + MPP_PENDING_GRACE_SECONDS(默认值为 300 + 60 秒)后过期。过期后绝不可能激活:激活操作是条件式 UPDATE … WHERE status = 'pending' AND expires_at > now。如果通道在链上开启时会话已过期,会话会被标记为 failed,并记录孤立通道 ID;付款方会收到包含 channelId 的 410 MPP_SESSION_EXPIRED,且不会计量任何费用(关闭通道即可取回押金)。

  • POST /api/v1/mpp/sessions 按客户端地址进行限流(每分钟 MPP_SESSION_CREATE_RATE_LIMIT_MAX 次,使用现有的固定窗口限流器,并且仅在 RATE_LIMIT_ENABLED=true 时启用)。每个客户端最多也只能持有 MPP_MAX_PENDING_SESSIONS_PER_CLIENT 个未付款的待处理会话(429 MPP_TOO_MANY_PENDING_SESSIONS)。客户端密钥为 HMAC(MPP_SECRET_KEY, IP):不会存储原始 IP;由于通道开启前无法证明钱包地址,因此不会使用钱包地址。

  • 维护(GET 或 POST /api/v1/mpp/internal/maintenance,Authorization: Bearer <MPP_MAINTENANCE_SECRET or CRON_SECRET>,不在 OpenAPI 中)会执行四项操作。每项操作均可幂等执行,并且在多个实例上同时运行也是安全的(FOR UPDATE SKIP LOCKED、租约):

    1. 使逾期待处理会话过期
    2. 使 TTL 已过期且没有进行中操作的活跃会话过期(不触碰通道)
    3. 协调结算(先读取链上状态,再仅在不会超额扣款时进行结算)
    4. 删除超过 MPP_PENDING_RETENTION_DAYS 且已过期、从未开启的会话(已开启会话的审计记录会保留)

    未提供密钥时会返回 503。进程内没有调度器。要在 Vercel 上安排此操作,请设置 CRON_SECRET 并将 cron 添加到 vercel.json,例如 {"crons":[{"path":"/api/v1/mpp/internal/maintenance","schedule":"*/10 * * * *"}]}。Hobby 方案仅允许每日执行 cron,因此本仓库不会附带 cron;任何发送 bearer 标头的外部调度器也可以使用。两次运行之间,每个请求仍会进行惰性过期处理。

  • 启用 MPP 时,GET /api/v1/mpp/status 也会报告:

    • configured
    • provider.reachable(缓存的 eth_chainId 探测结果;不会产生支付)
    • database.ready,以及会话和结算数量
    • charge、session、network(chainId、observedChainId、testnet)

**持久化。**与本仓库中的其他存储一样,数据表会在一个 DDL 事务中、通过 advisory lock 自动创建:

表存储内容键和约束
mpp_sessions会话external_session_id 唯一(一个通道对应一个会话);命名的 CHECK 约束:spent + reserved ≤ max、spent ≤ max、remaining ≥ 0、max ≤ requested、计数器非负、结算状态有效;会话一旦处于活动、耗尽或关闭状态,就必须有通道
mpp_usage_events使用事件(session_id, request_id) 唯一 = 幂等
mpp_charge_redemptions一次性收费凭证挑战 ID 为主键;状态为 in_flight / redeemed / settlement_unknown(只有过期的 in_flight 声明可以重新声明)
mpp_kvSDK 自身的通道状态和重放标记在每键 advisory 锁下执行线性一致的读取-修改-写入

如果设置了 MPP_DATABASE_URL,MPP 就会使用它;否则回退到 DATABASE_URL。生产环境如果没有数据库,MPP 将拒绝启动。

安全性。

  • 凭证经过 HMAC 绑定、限定路由范围并设有过期时间。SDK 会验证挑战、签名、金额、收款方和范围。

  • 收费凭证只能使用一次。

  • 会话的打开凭证不能重放到第二个会话中,其条款也不能被篡改。

  • 凭证必须来自会话自身的通道。

  • 预算强制执行是原子的。

  • 结算绝不会扣取未计量的授权金额。

  • 系统会发出结构化审计事件(每行一个 JSON):

    • mpp.charge.challenge|verified|settled|replay_rejected
    • mpp.session.pending|pending_rejected|activated|call|usage_recorded|exhausted|budget_rejected|expired|closed|settlement_pending|settled|settlement_failed
    • mpp.payment.failed 和 mpp.maintenance.run

    事件只携带允许公开的字段(ID、金额、交易哈希、原因)。绝不会记录凭证、签名凭证、回执、原始支付标头、密钥和机密信息;任何看起来像这些内容的信息都会被丢弃。

  • 如果收费的结算结果未知(广播时发生网络故障),系统会继续将其凭证标记为已声明(settlement_unknown),不返回结果,并响应 502 MPP_SETTLEMENT_UNCONFIRMED。系统绝不会将其报告为成功或“未收费”。

MCP。 标准 MCP 仍位于 /mcp,免费且保持不变:MCP 负责工具发现和调用,MPP 负责支付。启用 MPP_MCP_ENABLED=true 后,/mcp/mpp 将提供 MPP MCP 传输绑定:

  • 未提供支付信息的收费 tools/call 会返回 JSON-RPC -32042 错误,并附带 data.challenges。
  • 客户端使用 params._meta["org.paymentauth/credential"] 重试。
  • 结果会包含 _meta["org.paymentauth/receipt"]。

官方的 mppx/mcp/client McpClient.wrap() 会自动处理这些步骤,tests/mpp.test.ts 则会端到端地测试此流程。要在 MCP 上使用会话模式,请通过 HTTP 打开会话,并在凭证中传入通道凭证以及 _meta["com.rafidsystem/mpp-session"] = { sessionId, idempotencyKey }。initialize 和 tools/list 会直接传递给标准 MCP 服务。更多详情请见 docs/mpp.md。

配置(参见 .env.example):

变量默认值用途
MPP_ENABLEDfalse总开关
MPP_PROVIDERmppx提供方适配器
MPP_NETWORKtempotempo(链 4217,USDC)或 tempo-testnet(Moderato 42431,pathUSD)
MPP_MODEScharge,session启用的意图
MPP_CHARGE_METHODStempotempo 和/或 evm
MPP_CURRENCYUSD定价货币(仅 USD)
MPP_SECRET_KEY—≥32 个随机字节,用于挑战的 HMAC(openssl rand -base64 32)
MPP_REALMapi.rafidsystem.com挑战中绑定的稳定 realm
MPP_TEMPO_PRIVATE_KEY—收款方密钥;会话模式必需(由服务端提交关闭/结算)
MPP_TEMPO_RECIPIENT从密钥派生Tempo 收款方地址
MPP_TEMPO_CURRENCY、MPP_TEMPO_RPC_URL链默认值覆盖项
MPP_EVM_NETWORKeip155:8453evm/charge 网络;主网需要 CDP_API_KEY_*,与 x402 一样
MPP_EVM_RECIPIENTX402_WALLET_ADDRESSevm/charge 收款方
MPP_SESSION_TTL_SECONDS3600会话有效期
MPP_MAX_SESSION_BUDGET_USD / MPP_MIN_SESSION_BUDGET_USD1000 / 0.01预算范围
MPP_REQUIRE_IDEMPOTENCYtrue会话调用必须提供 Idempotency-Key
MPP_CHALLENGE_TTL_SECONDS300挑战有效期
MPP_MCP_ENABLEDfalse/mcp/mpp 绑定
MPP_DATABASE_URLDATABASE_URL持久化

示例。 examples/mpp-client/ 包含 charge.ts、session.ts(创建一个 $10 的会话、使用两个工具、读取剩余预算、关闭会话)以及 bulk-supplier-check.ts(筛选供应商,直到预算耗尽,然后关闭并结算)。

# Unpaid quote (no wallet needed):
curl -si -X POST https://api.rafidsystem.com/api/v1/mpp/charge/analyze_oman_property \
  -H 'Content-Type: application/json' \
  -d '{"governorate":"Muscat","area":"Al Mouj","propertyType":"villa","bedrooms":4,"sizeSqm":420,"askingPriceOMR":450000}'
# → HTTP/1.1 402 … WWW-Authenticate: Payment id="…", method="tempo", intent="charge", request="…"

# Start a session (402 → pay the open challenge with an MPP client → 201):
curl -si -X POST https://api.rafidsystem.com/api/v1/mpp/sessions -H 'Content-Type: application/json' \
  -d '{"maxBudget":20,"currency":"USD","allowedTools":["oman_supplier_check","analyze_oman_property"]}'

MCP 用法

MCP 是 Rafid 的两种主要 Agent 接口之一(另一种是 x402),并非后来附加到 REST API 上的功能。项目保留了已安装的 MCP SDK v2;请参阅官方 SDK 文档。提供两种传输方式,且都由完全相同的 createMcpServer() 工厂(src/mcp/server.ts)构建,并读取完全相同的 capabilities 注册表——无需手动维护第二套工具注册表、模式、描述或 execute() 路径:

  • 本地 stdio — 先构建,然后运行 npm.cmd run mcp,以启动本地进程进行手动操作。
  • 远程 Streamable HTTP — 在已部署的服务上使用 POST /mcp;只要 MCP_REMOTE_ENABLED=true(默认值)就会挂载。采用无状态模式(sessionIdGenerator: undefined):这不仅更简单,而且是必需的,因为无服务器部署(Vercel)可能会将连续请求路由到不同的热实例,且它们之间没有共享内存,因此无法依赖服务端会话状态。GET /api/v1/mcp/status 始终会报告实际启用的传输方式:{ enabled, transport: ["stdio","http"], tools: 4, endpoint: "/mcp" }(禁用远程传输时则为 endpoint: null/transport: ["stdio"])。

对于本地 stdio,请直接启动 Node,以免 npm 横幅内容污染协议标准输出:

{
  "mcpServers": {
    "rafid": {
      "command": "node",
      "args": ["C:/Projects/rafid-agent-api/dist/mcp.js"],
      "env": { "LOG_LEVEL": "info", "X402_ENABLED": "false" }
    }
  }
}

使用你自己的绝对项目路径。对于远程 Streamable HTTP,请将 MCP 客户端指向已部署的 URL,而非本地命令,例如:

{
  "mcpServers": {
    "rafid": { "url": "https://api.rafidsystem.com/mcp" }
  }
}

或者通过 HTTP 直接使用 JSON-RPC 2.0:

curl -s https://api.rafidsystem.com/mcp \
  -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"analyze_property","arguments":{"propertyValue":85000,"annualRent":7200,"serviceCharge":650,"maintenanceCost":400}}}'

两种传输方式均提供以下工具:analyze_property、compare_properties、estimate_maintenance、analyze_oman_property。工具结果包含 JSON 文本和 structuredContent,并采用严格的输入/输出模式,提供验证错误和只读注解;每个工具的 MCP description 都由注册表中的 description 加上 whenToUse 建议句原文组成,不会重复添加这段说明。自定义跨字段约束(别名互斥、名称唯一)会在运行时强制执行;两种传输方式上的所有错误都会经过与 REST 相同的 publicError() 清理器处理,因此内部故障不会通过远程方式泄露内部信息,正如本地方式也不会泄露一样。 MCP 和 REST 并不是两个碰巧行为一致的实现:三种方式(stdio、远程 MCP、REST)都调用完全相同的 capabilities 目录(src/domain/capabilities.ts)和完全相同的 services/property.ts 函数,因此在一处修改公式或价格后,所有方式都会正确生效,无需手动同步。

通过 MCP 使用 API 额度付款(统一计费,参见 docs/billing.md):让远程 MCP 客户端将请求发送到 POST /mcp/credits,并使用 Authorization: Bearer raf_live_…——initialize/tools/list 的行为与 /mcp 完全相同,每次付费的 tools/call 都会计入该密钥对应的账户(先扣订阅额度,再扣预付额度;费用会写入 result._meta["com.rafidsystem/billing"])。也可以在托管模式下运行本地 stdio——RAFID_API_KEY=raf_live_… node dist/mcp.js 会将每次工具调用转发到托管 API(RAFID_API_URL),并在那里计费。MCP 上的 MPP 仍使用 /mcp/mpp;x402 和 L402 仅适用于 HTTP。/mcp 本身保持不变。

Stdio 仅限启动进程的操作系统用户使用;REST API 密钥不是 stdio 的身份验证机制,stdio 调用也仍会有意地不计量——MCP 客户端由自己的操作系统用户直接启动该进程,没有需要保护的共享服务资源。远程 MCP 则不同:它是共享的公共服务资源,因此每次远程工具调用都会通过与 REST/x402 调用相同的 UsageRepository 记录(accessMode: "mcp-remote"、billableAmount: 0——在当前阶段,远程 MCP 不是付费渠道),并受到与其他公共 Agent 端点相同的速率限制保护(见下文“安全性、可观测性与计费边界”)。两种传输方式的日志都写入 stderr;stdout 专用于 MCP stdio 帧传输。远程身份验证(速率限制除外)尚未实现——见下文“接下来的五项生产优先事项”。

安全性、可观测性与计费边界

API 密钥中间件使用恒定时间比较来比对 SHA-256 摘要。密钥在 postgres 模式下从 PostgreSQL 获取,在旧版开发模式下从配置中获取。请求日志仅包含生成的请求 ID、匹配的路由模板/工具名称、状态、时间戳和耗时;不会记录请求正文、标头、查询字符串、原始 API 密钥和未知 URL 路径。内部异常会返回通用消息。每个响应——无论成功还是出错——都带有 Cache-Control: no-store、X-Content-Type-Options: nosniff、X-Frame-Options: DENY 和 Referrer-Policy: no-referrer 标头,以及一项宽松但安全的 CORS 策略(Access-Control-Allow-Origin: *)。此策略在此处是安全的,因为每条路由都会通过请求标头(X-API-Key 或 X-PAYMENT)进行授权,而不是依赖浏览器自动携带的 Cookie 或会话——不存在可被跨域页面利用的凭据状态。请求正文在解析前限制为 32kb(413 PAYLOAD_TOO_LARGE)。 createApp 支持注入 rateLimiter 中间件,用于在身份验证之后、解析请求体之前,限制 PostgreSQL 模式下已验证的客户流量。另有 middleware/rateLimit.ts 中的 createRateLimiter(),用于保护公开的、未经身份验证的 Agent 端点——发现端点(/、/agent.json、.well-known 清单、/llms.txt、/api/v1/agent、/api/v1/pricing、/api/v1/tools、/api/v1/capabilities、/api/v1/mcp/status)、整个 /api/v1/x402/... 路由系列,以及远程 MCP(/mcp)——每个端点都有独立的、按 IP 键控的固定窗口预算(RATE_LIMIT_WINDOW_MS / RATE_LIMIT_MAX,两者均可配置;本地开发时可设为 RATE_LIMIT_ENABLED=false 来禁用)。这是诚实的、按进程运行的内存默认实现,不是分布式限流器:在运行多个并发实例的无服务器平台(Vercel)上,每个实例都会独立执行限额,因此当流量分散到多个实例时,整个部署的实际限额可能超过 RATE_LIMIT_MAX。预期会运行许多并发实例的生产部署应使用共享存储限流器(例如 Upstash Redis)替换该实现,并保持相同的 RequestHandler 形状;应用中其他部分无需改动。未启用代理信任。请部署在 HTTPS 后方;没有这些控制措施时,请勿公开 MVP。

隔离的计费目录(billing/catalog.ts)包含参考 USD 价格:分析 $0.01、比较 $0.03、维护 $0.02、阿曼房产分析 $0.25。它是唯一事实来源:只有 BillingService(billing/service.ts)会读取该目录,其他所有模块——路由处理器、x402 门控,以及 Agent/定价/工具端点——都通过 BillingService 获取价格,而不会直接读取目录或使用字面量。REST 会在验证之后、计算之前调用可注入的 BillingGate.authorize;其默认实现不执行任何操作,不应将其理解为计费。这个接口为未来的额度或用量计费适配器提供支持,无需更改计算服务。PostgreSQL 模式提供持久的客户 UUID;环境变量密钥模式保留配置级本地身份。Stdio 仍是无需计量的本地执行方式。

每次能力调用——无论是 API 密钥、x402 还是远程 MCP——无论结果如何(成功、验证失败、身份验证失败、触发限流),都会通过 UsageRepository(billing/usage.ts)记录:请求 ID、经过脱敏的标识符(现有客户 ID、x402:<network> 标签或 mcp-remote——绝不记录原始 API 密钥、钱包密钥或支付凭证/签名)、工具名称、accessMode("api-key" / "x402" / "mcp-remote")、状态、耗时、计费金额和货币。USAGE_REPOSITORY 用于选择存储:console(默认——每次调用向 stderr 输出一行 JSON,由 server.ts 中的 ConsoleUsageRepository 接入)、memory(仅用于本地检查/测试,重启后丢失),或 postgres(PostgresUsageRepository,将数据持久化到可查询的 rafid_agent_usage 表中——该表独立于 db/store.ts 中的客户存储,并在首次使用时通过 CREATE TABLE IF NOT EXISTS 创建,复用 DATABASE_URL)。更换存储库无需更改任何路由处理器或 mcp/remote.ts。

当 X402_ENABLED=true 时,@x402/express、@x402/core 和 @x402/evm 会对单独的、未经身份验证的 /api/v1/x402/... 路由系列进行访问控制(见上文“通过 x402 按次付费”)。支付验证和结算完全交由已配置的 Facilitator 处理(公共 Facilitator,或在设置 CDP_API_KEY_ID / CDP_API_KEY_SECRET 时使用 CDP 的 Facilitator——见上文);本仓库本身不实现钱包托管、结算或重放保护。切勿提交或记录 CDP_API_KEY_SECRET;请像存储 DATABASE_URL 一样妥善存储它。

后续五项生产优先事项

  1. **客户密钥与滥用控制:**持久化客户身份、哈希密钥、密钥签发/轮换/撤销、每位客户共享的配额,以及认证前的边缘限流。按进程运行、基于 IP 的速率限制器现已保护发现、x402 和远程 MCP(见上文“安全性、可观测性与计费边界”);仍需实现:适用于多实例部署的分布式限流器,以及专门针对 x402/远程 MCP 路由系列的客户配额。
  2. **持久化用量与计费:**x402 按次付费已实现,并已在 Base Sepolia 和 Base 主网上线;UsageRepository 现有基于 PostgreSQL 的持久化实现(USAGE_REPOSITORY=postgres),并保留控制台/内存实现。仍需实现:x402 收据对账,以及另一个计费适配器(例如积分/订阅),供不愿以加密货币按次付费的客户选择;此外还需对远程 MCP 本身收费(目前它是免费的、无需认证的渠道)。
  3. **部署与运维:**HTTPS 部署、密钥管理、CI 检查、依赖项扫描、指标/告警、负载测试、备份和恢复演练。
  4. 经过认证的分布式远程 MCP:/mcp 现已提供 Streamable HTTP 传输(无状态,与 stdio 共享完全相同的注册表/服务层——见上文“MCP 用法”),并具备基本的 IP 速率限制和用量记录。仍需实现:远程调用方的持久化身份/授权、适用于公开部署(而非仅绑定 localhost)的来源/主机验证,以及用于多实例生产流量的分布式限流器。
  5. 可信的房产数据智能:analyze_oman_property 现已具备可用于生产的数据架构:REST/x402/MCP 全链路异步能力执行、PropertyMarketRepository 抽象及其基于 PostgreSQL 的实现和独立的迁移账本、经过验证的 CSV/JSON 导入管道(npm run market:import)、可配置的提供方模式(OMAN_PROPERTY_DATA_MODE=manual|database|composite)、逐响应数据新鲜度/过时状态报告、动态(而非硬编码的)演示数据风险标记、可选的可比房产缓存、与 NCSI 官方统计 API 的真实集成(尽管尚未通过实时验证),用于提供省级市场背景信息(officialMarketContext),以及——截至本阶段——可用于生产的合作伙伴数据源层(经认证的 CSV/JSON/HTTP 数据导入、每位合作伙伴的令牌、幂等 upsert、确定性的非 LLM 数据质量评分、合作伙伴数据源健康/过时状态,以及内部汇总状态报告,完全置于 Agent 能力注册表之外)。该数据源层终于为真实的阿曼房地产公司、经纪人或物业管理方提供了有文档说明且经过授权的市场数据提供途径——见上文“阿曼房产分析(马斯喀特)”、“生产级阿曼市场数据”、“合作伙伴数据源”和“NCSI 官方市场背景信息”。无论数据源如何,可比房产筛选、异常值剔除和置信度评分均保持不变;NCSI 背景信息在结构上与基于可比房产的数据分开。仍需实现:让 NCSI 实时目录端点实际返回响应(开发期间每个请求都返回 HTTP 500——完整发现见“NCSI 官方市场背景信息”),以便验证真实的 NCSI_REAL_ESTATE_DATASET_ID/NCSI_FIELD_MAP_JSON 并使用真实数据测试此集成;通过新的合作伙伴数据源层实际接入首个真实(而非虚构的)阿曼房地产数据合作伙伴(ListingDataProvider 仍是一个真实且已接入、但始终为空的接口,等待合作伙伴接入);扩大马斯喀特/阿曼地区覆盖范围;并在添加更多投资回报率、服务费、租赁或设施工具之前,为 estimate_maintenance 校准维护成本假设,并记录其来源、新鲜度和公式版本。
  6. Rafid Agent Intelligence,第 2/3 阶段: 第 1 阶段(research_company、find_companies、analyze_company_risk——参见上文“Rafid Agent Intelligence”)已实现并通过测试,且默认不启用。仍待完成:同一扩展规范中的第 2 阶段(research_web、deep_research)和第 3 阶段(extract_document、analyze_document,仅进行架构准备);此外,上方 README 的 REST API 表格仍未列出 4 项现有的阿曼商业能力(search_oman_company、get_oman_company_profile、analyze_oman_company、due_diligence_oman_company)——这是先前就存在的文档缺漏,并非本阶段引入,值得在推进第 2 阶段时一并补上。

有关初步发现、确切范围和验证方式,请参阅实现审查。

Postman API Network

访问 Rafid Agent API 公共 Postman 工作区,查看可运行的开发者请求:

  • 发现可用能力:GET /api/v1/capabilities
  • 查看支持的付款方式:GET /api/v1/payment-methods
  • 检查 x402 是否可用:GET /api/v1/x402/status
  • 预览公司尽职调查:POST /api/v1/preview/company_due_diligence

API 基础 URL:https://api.rafidsystem.com。预览和发现端点无需付款;完整的付费能力遵循 API 公布的付款方式。请勿将凭据、付款签名或私钥粘贴到公共工作区。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。