Formquote.
中文

API / DOMAIN MODEL / INTEGRATION

把不同品类的需求,接入同一套报价结构。

Formquote 接收品类、数量、规格和用途,返回带价格依据、适用条件与下一步操作的方案。三个数字品类可公开查询;PCB 延续原服务的操作员权限。当前支持报价与官方页面跳转,尚未启用统一支付、下单、邮箱验证执行或自动交付。

API 基址:https://pcba-quote-lab.pages.dev
统一接口:/api/v2;请求和结果的 schemaVersion 仍为 1。原 /api/v1/offers 的 PCB 请求、响应和认证合同保持不变。

1. 当前目录范围

2026 年 10 月 8 日的目录包含 27 个数字商品方案、11 家不同供应商。这是经过整理的首批样本,不是全市场覆盖;同一家供应商的多个套餐分别计为方案。27 条记录中有 22 条已确认原始配置价格,另外 5 条保留为待确认记录。是否能用于本次需求,还要检查数量、用途及授权条件。

各品类目录规模和报价方式
categoryId方案 / 供应商如何比较当前数据方式
email_verification13 / 4相同邮箱数量所需的额度套餐;区分一次性购买和月订阅。公开套餐观察及本次用量推算。
sound_effects8 / 3不同音效包对用途、人数和许可的匹配程度。商品页与已取得的许可条款观察。
font_license6 / 4不同字体 / 套餐对媒介、人数、流量口径与期限的匹配程度。公开价格及许可范围观察;动态选择器中未确认的金额留空。
pcb不计入数字目录相同板子规格、数量、目的地、币种和交期口径的制造估算。向原受保护服务取价,返回 Formquote 客户销售估算。

运行时数量以 目录接口的 itemCount 和 supplierCount 为准。目录列出某家供应商,不代表已经取得其转售许可或开通其 API;这些状态分别记录在供应商、许可与能力字段中。

2. 接口与响应

API 端点与访问权限
方法与路径用途访问方式
GET /api/v2/categories品类定义、默认请求、表单字段、请求 JSON Schema 与目录数量。公开;看到 PCB 品类不代表取得其访问权限。
GET /api/v2/catalog完整数字目录。可用 ?categoryId=email_verification 等参数筛选;不接受 pcb。公开。
GET /api/v2/capabilities逐品类的取价方式、认证方式和交易 / 交付能力。公开。
GET /api/v2/health统一服务版本与数字目录状态;不探测外部供应商或 PCB 上游账户。公开。
POST /api/v2/quotes返回统一报价结果。三个数字品类公开;PCB 要求原操作员 Bearer。
POST /api/v1/offers原 PCB 合同,继续接收 scope/spec/quantity/destination。原操作员 Bearer;详见完整 OpenAPI 保留的 v1 定义。

POST 必须发送 Content-Type: application/json,UTF-8 JSON 请求体不超过 32 KiB(32768 字节)。这里只接受数量和规格,不接受邮箱名单、制造文件、字体文件或音频文件。报价 JSON 使用 Cache-Control: no-store。

结果状态与错误

complete 表示当前返回方案的价格与所检查条件完整;partial 表示仍有未知费用、条件或价格;blocked 表示请求无法完成。三个数字品类经常正常返回 HTTP 200 和 partial,客户端不能把它当作无结果。

HTTP 状态处理方式
200读取 offers、eligibility、pricing 和 supplierErrors。PCB 的 no_current_offer 也可能以 200 / blocked 返回。
400修改输入。字段错误通常以 blocked 结果的 missingInputs 与 supplierErrors[].field 指示;JSON 或请求类型错误也可返回 error.code/message。
401 / 403PCB 认证失败或网页来源不允许。公共数字工具不应请求 PCB。
405 / 413 / 415分别检查 POST 方法、请求体大小和 JSON Content-Type。
502PCB 服务、配置或返回数据不可用。按 supplierErrors[].retryable 处理;不要用旧报价按新数量计算替代结果。
500服务暂时无法完成请求;保留相关性信息,稍后重试。

客户端应同时识别统一结果和 { "schemaVersion": 1, "error": { "code": "…", "message": "…" } } 形式。响应 requestId 是相关性标识,不是订单号;数字引擎生成自己的标识,不能假设它会原样回显输入的 requestId。

3. 领域模型

统一的是需求、依据与决策结构。各品类仍保留自己的单位、许可和价格规则,这样 AI 才能区分“有一个价格”与“这个价格适用于眼前的需求”。

领域对象与 JSON 字段映射
对象字段 / 位置责任
Categoryid / requestSchema / comparisonMode定义品类及合法输入,说明是相同配置比较还是不同商品的需求匹配。
QuoteRequestcategoryId / quantity / specifications / usageContext / purchaseContext表达数量、品类规格、使用条件和已有采购资源;不能把未支持的要求静默忽略。
CatalogItemcatalog.items[]一项具体公开方案及其原始价格、规格、许可和来源,不等于已经针对本次需求报价。
SuppliersupplierId / supplier标识提供方案的商家,并单独记录公开 API、连接状态和转售条款。
Offeroffers[]绑定一次需求的候选结果;引用目录项、价格、条件匹配、证据及交付动作。
Pricingoffer.pricing分开保存目录参考价、所需配置小计、现金总额、消耗分摊、循环费用及未知项。
PricingEvidenceoffer.evidence[]给每条价格或条件提供来源 URL、观察日期、计算来源与实时性说明。
RequirementMatchrequirementMatches[]用 yes / no / unknown 回答每项已检查条件,并关联证据 ID。
ComparisonGroupgroups[]约束可比较范围,提供组内 offerIds 顺序和真实排序字段。
Fulfillmentoffer.fulfillment[]说明下一步能打开购买页还是确认页;当前不执行交易或交付。

同一结构在四个品类中的含义

维度邮箱验证音效字体PCB
需求量待验证邮箱数 / credits。一份所列素材许可;人数放在 seats。一份所列许可;用户、网站和流量分别表达。实际板子数量 board。
关键规格countintent / seats / aiUselicenseUse / seats / websites / monthlyPageviews / trafficMetric / termYears宽高与固定 2 层 FR4 制造参数。
上下文各供应商已有额度及到期时间。商业成品、独立素材转售、纯音效时间占比。媒介、项目数、印量和是否需要全部字重。配送国家和城市 / 地区。
价格规则按完整套餐补足缺口,另外计算消耗分摊。按所列内容与许可;多人许可不线性乘人数。按明确字重、用途、用量和期限;不拿入门价替代未知配置。只取原客户制造总额;尺寸或数量变更必须重新取价。
后续动作已列方案可前往商家购买;配置、价格或授权尚待确认时前往商家确认。仅联系 / 确认;不开放下单。

4. 可直接调用的请求示例

推荐按 OpenAPI 的按品类 oneOf schema 生成表单与工具参数。API 也接受已声明的数量别名;当同时发送两种表示时,它们必须一致。未声明的顶层、规格或用途字段会被拒绝。

邮箱验证:只发送数量

curl 'https://pcba-quote-lab.pages.dev/api/v2/quotes' \
  --header 'Content-Type: application/json' \
  --data '{
    "schemaVersion": 1,
    "categoryId": "email_verification",
    "specifications": {"count": 10000},
    "comparisonGoal": "lowest_cash_now"
  }'

count 范围为 1–10,000,000。它也可以写成 "quantity": 10000,或 "quantity": {"value": 10000, "unit": "email"};对象单位还接受 emails / credit / credits。规范请求优先使用整数,Schema 同时记录实现支持的整数字符串形式。

如果用户明确提供已有余额,可加入以下上下文。它是用户提供的预算输入,没有经过上游 API 核实;超过到期日的余额会被视为不可用。使用已有额度后,因为历史采购成本未知,allocatedJobCost 会保持 null。

"purchaseContext": {
  "creditsBySupplier": {
    "reoon": {"credits": 1000, "expiresAt": "2026-12-31T23:59:59Z"}
  }
}

音效:界面用途与单用户许可

curl 'https://pcba-quote-lab.pages.dev/api/v2/quotes' \
  --header 'Content-Type: application/json' \
  --data '{
    "schemaVersion": 1,
    "categoryId": "sound_effects",
    "specifications": {"intent": "ui", "seats": 1, "aiUse": false},
    "usageContext": {"commercialUse": true, "standaloneResale": false},
    "comparisonGoal": "best_requirement_fit"
  }'

intent 支持 any / ui / motion / nature / train / kettle;不传时 API 使用 any。seats 为 1–1000,不传时为 1。aiUse 指把音频素材用于 AI 训练或生成,默认 false;在 AI Chat 中查询商品不属于该用途。

涉及纯音效占比时,用 usageContext.supplierPureSfxTimePercent 表达某供应商的纯音效占成品总时长百分比,范围 0–100;也接受别名 pureSfxSharePercent。这不是音轨音量百分比。该字段用于检查已记录的主要声音产品限制,达到限制时会将当前许可价格标为不适用于该用途。

字体:每站每月 5,000 PV 的网页用途

curl 'https://pcba-quote-lab.pages.dev/api/v2/quotes' \
  --header 'Content-Type: application/json' \
  --data '{
    "schemaVersion": 1,
    "categoryId": "font_license",
    "specifications": {
      "licenseUse": "web", "seats": 1, "websites": 1,
      "monthlyPageviews": 5000, "trafficMetric": "pv", "termYears": 1
    },
    "usageContext": {
      "medium": "website", "commercialProjects": 1, "requiresAllStyles": false
    },
    "comparisonGoal": "best_requirement_fit"
  }'

licenseUse 支持 web / desktop / both。网页或混合用途必须给出流量;纯桌面用途未给流量时按 0 处理。monthlyPageviews 字段的实际单位由 trafficMetric 指定:PV 是页面浏览量,UV 是独立访客;两者不自动换算。该数量按每站输入,要求全部网站合计的许可会再乘以网站数。

桌面印刷可通过 usageContext.printProjects 和 printUnits 提供项目数与每项目印量。medium 还能表达 app / web_app / game / logo / paid_ad 等需求,但当前目录没有确认它们对应的完整配置价格;接受字段不表示所列 Web 许可已覆盖这些用途。音效和字体的顶层 quantity 通常省略,若提供只能为一份所列素材 / 许可;人数请使用 seats。

PCB:仅现有操作员使用

curl 'https://pcba-quote-lab.pages.dev/api/v2/quotes' \
  --max-time 160 \
  --header 'Content-Type: application/json' \
  --header "Authorization: Bearer $FORMQUOTE_PCB_OPERATOR_TOKEN" \
  --data '{
    "schemaVersion": 1,
    "categoryId": "pcb",
    "quantity": {"value": 30, "unit": "board"},
    "specifications": {"width": 5, "height": 5, "dimensionUnit": "cm"},
    "purchaseContext": {"destinationCountry": "JP", "destinationRegion": "Tokyo"}
  }'

这里的环境变量仅是操作者本地已有凭证的占位方式,不是公开访问码。数量限定 3–300;宽高必须换算为 10–200 的整数毫米。除 widthMm / heightMm 外,标量尺寸支持 mm / cm / m / in / inch,不能把真实分数毫米四舍五入成另一种板子。默认制造条件沿用 2 层 FR4、1.6 mm、35 μm 铜、无铅 HASL、绿油白字、单片和 budget 服务。

配送国家仅支持 JP / US / CA / GB / DE / FR / NL / AU / SG / IN;城市 / 地区可选,最多 80 个允许字符。目的地不等于确认可配送或已核定运费。原工作台仍可从 /pcb 打开。

5. 正确解释价格与排序

字段应向用户表达什么
referencePrice所列原始配置的目录价格。需求超出已核实许可时,它仍可能存在,但不能作为本次需求的报价。
quotedSubtotal对本次条件仍可引用或推算的小计;它可能不含税费、运费或其他待确认项。
cashRequiredNow当前所需现金金额。必要费用未确认时为 null;不能直接用 quotedSubtotal 代填。
allocatedJobCost按本次消耗分摊的成本,不等于必须购买完整套餐所需现金。已用余额的历史成本未知时也为 null。
recurringCharge所列月费或年费与计费间隔。它说明循环付费,不是一次性购买总额。
requestedTermCost已确认的请求期限金额。未锁定未来续费价格时,不把首年价乘以年数填成多年总价。
completeness / certainty价格是否完整,以及来自公开价、推算还是供应商报价;不能仅凭一个金额判定。

例如,假设所列 5,000 credits 套餐为 10.00 USD,本次需要 3,000 credits 且没有余额:整包购买小计为 10.00,消耗分摊为 6.00,预计剩余 2,000 credits。税费未知时,现金总额仍是 null。这解释了金额字段的关系,不是一个新的供应商报价。

一次性额度包会按缺口向上取整,并把重复购买同一包的预算标为推算;月订阅只使用所列首期额度,不假设未来月份或多个订阅能立即补足当前需求。未知验证结果、退款、重复项和 goodwill credits 也不会被提前当作保证折扣。

音效和字体必须先看 eligibility 与 requirementMatches。许可或配置不匹配时,quotedSubtotal 可以是 null,同时保留原 referencePrice 供浏览。Pangram Pangram Starter Pack 等套餐列出的 key styles,不等于全部字体的所有商业字重;网页许可也不自动覆盖 App、Logo 或向客户转授权。

排序边界:按 groups[].offerIds 使用组内顺序,并展示 rankingMetric 和 comparableFinalTotal。lowest_cash_now 当前会在税费未知时按 quotedSubtotal 排序;它不能被描述为保证最低到手价。不同币种、计费周期或不一致的 PCB 交期不会合并比较。音效和字体是不同内容的候选,价格排序不是相同 SKU 的优劣证明。

所有金额使用十进制字符串或 null,不要将未知项转成 0。PCB 的兼容字段 totalMinor 只是原客户销售制造估算的分单位金额;统一结果不公开供应商采购成本、服务毛利或定价密钥。

6. 来源日期、推算与时效

evidence.mode含义时间应如何解释
published_observed已保存的官方公开页面观察。observedAt 是核验时点或日期;再次请求本站 API 不会使该来源自动变新。
derived_estimate根据已列价格、用户需求或用户提供余额做预算推算。计算时间可为本次请求时间,但仍通过 derivedFromEvidenceIds 指向旧的原始依据。
supplier_live当前服务返回的客户可见报价数据;本项目用于 PCB 在线估算。表示这次取回的估算,不构成供应商最终报价、工程批准或价格锁定。

checkedAt 保留数据来源精度,可能是日期或完整时间戳;generatedAt 是本次统一结果生成时间。refreshAfter 是本站复核期限:邮箱目录 7 天,音效与字体 30 天。freshness=fresh 只表示仍在该窗口内;stale 需要重新核实,unknown 表示时间不足以判断。它们都不承诺实时库存。

PCB 的内部 freshness 最长为观察时间之后 5 分钟;适配器会拒绝到期、规格不匹配或异常结果,不从取回时间重新续期。所有品类目前的 supplierExpiresAt 均为 null,明确没有被当作供应商有效期承诺。客户端应保留每条 sourceUrl,让用户可以查看价格和许可依据。

7. 接入 AI Chat 的公开数字报价工具

在自己的 AI 应用里注册一个公开数字报价工具,将参数发送到 POST /api/v2/quotes,再把结构化结果交给模型。下面是与平台无关的工具契约描述;实际注册方式由所用 Chat 框架决定。输入 Schema 可直接引用 OpenAPI 中仅含三个数字品类的 DigitalQuoteRequest。

{
  "name": "quote_digital_catalog",
  "description": "按数量、用途和授权条件比较邮箱验证、音效或字体目录。保留未知费用与来源日期,不执行交易。",
  "method": "POST",
  "url": "https://pcba-quote-lab.pages.dev/api/v2/quotes",
  "inputSchema": {
    "$ref": "https://pcba-quote-lab.pages.dev/openapi-v2.json#/components/schemas/DigitalQuoteRequest"
  },
  "authentication": "none"
}

如果框架不支持远程 $ref,在应用构建时读取 OpenAPI,解析该 schema 及其本地引用后注册。不要将整个受保护的 PCB 分支一起开放给公共工具,也不要向工具注入操作员凭证。

async function quoteDigitalCatalog(input) {
  const allowed = ["email_verification", "sound_effects", "font_license"];
  if (!allowed.includes(input.categoryId)) {
    throw new Error("此公开工具仅支持三个数字品类。");
  }
  const response = await fetch(
    "https://pcba-quote-lab.pages.dev/api/v2/quotes",
    {
      method: "POST",
      headers: {"Content-Type": "application/json"},
      body: JSON.stringify(input),
      credentials: "omit",
      cache: "no-store"
    }
  );
  return {httpStatus: response.status, result: await response.json()};
}

向模型说明以下输出规则:先呈现适用性,再解释价格;保留金额币种与 null;用证据 URL 标注公开价来源;区分整包购买和本次分摊;遇到未知授权、未知税费或已过复核期的数据时明确显示待确认。后续操作只能使用实际返回的 fulfillment,不能宣称已经购买、验证、下载或拿到许可证。

用户通常可以说“我有一万条邮箱需要清理”“找一套给应用按钮使用的音效”或“一个每月 5,000 PV 的网站需要字体授权”。应用先把需求映射为本页的品类字段;遗漏数量、流量或特殊用途时,依据 schema 和字段错误补齐信息。

本站提供可接入的 API 与 Schema。发布文档或网站不会自动安装 Chat 工具,也不表示已接入 ChatGPT、Google AI Mode 或其他 AI 产品;客户端安装状态以 capabilities.integration.chatInstallation 为准,当前为 manual。

8. 权限与执行边界

数字目录 GET 和数字报价 POST 公开,不要求 API Key。浏览器跨来源调用可使用 Content-Type;预检没有开放 Authorization。PCB 仍要求原操作员 Bearer,并检查浏览器 Origin;PCB 响应不提供跨来源认证访问。服务端或命令行也必须持有被授权的操作员凭证,不能把这类凭证分发给客户或公共 AI。

由于同一 POST URL 按 categoryId 分支处理,标准 OpenAPI 的 operation-level security 无法完整表达按请求体改变权限。文档通过操作描述和 x-category-security 标出条件;接入方须同时实施品类限制,公共工具使用 DigitalQuoteRequest。

fulfillment.mode=merchant_checkout 表示打开商家页面,由用户完成选择和结账;inquiry 表示打开确认或联系页面,本站不会自动发送询价。state=available 只说明导航动作可用。当前能力接口中的 order、payment、automaticFulfillment 和各品类 executionEnabled 均为 false。

公开 API 文档、可采购、可转售与可自动执行是不同条件。新增验证执行或自动交付时,需要分别落实供应商授权、认证、额度或计费前置条件和交付处理;不能仅根据“该供应商有 API”开启执行状态。当前报价无需上传用户邮箱或素材文件。

9. 扩展一个新品类

  1. 先定义可比较的需求。确定稳定的 categoryId、数量单位和特殊规格;明确是相同配置比价,还是不同商品对需求的匹配。默认值与可接受别名应在品类 schema 中公开。
  2. 收集具体商品与依据。逐项记录供应商、所选套餐 / 字重 / 许可、准确价格币种、计费周期、最低购买量和真实来源日期。没有确认的档位保留未知,不能从“低至”单价反推整档价格。
  3. 分别建模规格与许可。用 specifications 表达商品要求,usageContext 表达用途,purchaseContext 表达已有额度等采购条件。新增字段必须有验证和业务处理,不能接收后忽略。
  4. 实现品类价格规则。分别计算购买小计、已确认现金总额、消耗分摊和循环费用。维持十进制金额;未知运费、税费、用量档或多年续费保留 null。底层采购成本不得混入客户报价。
  5. 输出可解释的匹配和分组。每项条件返回 yes / no / unknown 与证据 ID;按币种、周期、许可口径或其他真实差异分组。较低价格不应掩盖不匹配的条件。
  6. 声明实际后续能力。先选择官网购买或询价动作。只有完成供应商权限、认证、余额 / 支付前置条件和可验证交付流程后,才扩展新的执行模式,同时更新能力与响应 schema。
  7. 注册并验证。把目录数据加入 domain/data.mjs,把品类 schema、规范化、匹配与计算加入 domain/engine.mjs,并更新 Worker 的允许品类和能力信息。验证代表性的合法输入、未知价格、冲突别名、授权不匹配及币种分组。
  8. 重新生成文档合同。运行下方脚本。它从当前品类注册表和 Worker 合成 v2 schema,保留原 PCB v1 合同,并用实际本地结果断言验证基础字段和示例。同步更新本页目录范围与接入说明后,再发布应用。
node scripts/generate-openapi.mjs

生成文件为 public/openapi.json 和 public/openapi-v2.json。脚本使用 Node 内置模块,不安装依赖,不调用供应商、不读取密钥、不创建交易。品类 requestSchema 是请求定义来源,避免在文档里维护另一套失配的参数表。

10. 开发者注意:legacy adapter source

当前 PCB 后端依赖原服务的不可变旧部署。新 Worker 通过服务端固定的 PCB_LEGACY_ORIGIN 保留原受保护路由,再由 pcb-adapter.mjs 将客户报价规范为统一结果。这个来源由服务器控制,客户端不能提供转发 URL;凭证只沿受保护请求传递,不会写入目录、证据或导出的报价。

original/ 保存的是原公开静态文件与 OpenAPI 快照,不代表已经取得或迁移原私有后端源码。原服务的可用性、操作员认证和客户定价配置仍是 PCB 的运行依赖;/api/v2/health 不能替代原 /api/health 或真实受保护报价验证。

后续迁移应先取得原后端源码和经过授权的配置管理方式,在新的自有运行环境恢复客户销售报价接口;保持原 /api/v1/offers 合同、权限和公开字段边界,验证数量 / 规格绑定、异常响应与五分钟 freshness,再切换固定适配目标。迁移前后都不应导出 API Key、操作员凭证、采购成本或毛利。没有可用上游时返回明确错误,不利用历史单价模拟一个新的 PCB 报价。