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_verification | 13 / 4 | 相同邮箱数量所需的额度套餐;区分一次性购买和月订阅。 | 公开套餐观察及本次用量推算。 |
sound_effects | 8 / 3 | 不同音效包对用途、人数和许可的匹配程度。 | 商品页与已取得的许可条款观察。 |
font_license | 6 / 4 | 不同字体 / 套餐对媒介、人数、流量口径与期限的匹配程度。 | 公开价格及许可范围观察;动态选择器中未确认的金额留空。 |
pcb | 不计入数字目录 | 相同板子规格、数量、目的地、币种和交期口径的制造估算。 | 向原受保护服务取价,返回 Formquote 客户销售估算。 |
运行时数量以 目录接口的 itemCount 和 supplierCount 为准。目录列出某家供应商,不代表已经取得其转售许可或开通其 API;这些状态分别记录在供应商、许可与能力字段中。
2. 接口与响应
| 方法与路径 | 用途 | 访问方式 |
|---|---|---|
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 / 403 | PCB 认证失败或网页来源不允许。公共数字工具不应请求 PCB。 |
| 405 / 413 / 415 | 分别检查 POST 方法、请求体大小和 JSON Content-Type。 |
| 502 | PCB 服务、配置或返回数据不可用。按 supplierErrors[].retryable 处理;不要用旧报价按新数量计算替代结果。 |
| 500 | 服务暂时无法完成请求;保留相关性信息,稍后重试。 |
客户端应同时识别统一结果和 { "schemaVersion": 1, "error": { "code": "…", "message": "…" } } 形式。响应 requestId 是相关性标识,不是订单号;数字引擎生成自己的标识,不能假设它会原样回显输入的 requestId。
3. 领域模型
统一的是需求、依据与决策结构。各品类仍保留自己的单位、许可和价格规则,这样 AI 才能区分“有一个价格”与“这个价格适用于眼前的需求”。
| 对象 | 字段 / 位置 | 责任 |
|---|---|---|
| Category | id / requestSchema / comparisonMode | 定义品类及合法输入,说明是相同配置比较还是不同商品的需求匹配。 |
| QuoteRequest | categoryId / quantity / specifications / usageContext / purchaseContext | 表达数量、品类规格、使用条件和已有采购资源;不能把未支持的要求静默忽略。 |
| CatalogItem | catalog.items[] | 一项具体公开方案及其原始价格、规格、许可和来源,不等于已经针对本次需求报价。 |
| Supplier | supplierId / supplier | 标识提供方案的商家,并单独记录公开 API、连接状态和转售条款。 |
| Offer | offers[] | 绑定一次需求的候选结果;引用目录项、价格、条件匹配、证据及交付动作。 |
| Pricing | offer.pricing | 分开保存目录参考价、所需配置小计、现金总额、消耗分摊、循环费用及未知项。 |
| PricingEvidence | offer.evidence[] | 给每条价格或条件提供来源 URL、观察日期、计算来源与实时性说明。 |
| RequirementMatch | requirementMatches[] | 用 yes / no / unknown 回答每项已检查条件,并关联证据 ID。 |
| ComparisonGroup | groups[] | 约束可比较范围,提供组内 offerIds 顺序和真实排序字段。 |
| Fulfillment | offer.fulfillment[] | 说明下一步能打开购买页还是确认页;当前不执行交易或交付。 |
同一结构在四个品类中的含义
| 维度 | 邮箱验证 | 音效 | 字体 | PCB |
|---|---|---|---|---|
| 需求量 | 待验证邮箱数 / credits。 | 一份所列素材许可;人数放在 seats。 | 一份所列许可;用户、网站和流量分别表达。 | 实际板子数量 board。 |
| 关键规格 | count | intent / seats / aiUse | licenseUse / 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 和字段错误补齐信息。
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. 扩展一个新品类
- 先定义可比较的需求。确定稳定的
categoryId、数量单位和特殊规格;明确是相同配置比价,还是不同商品对需求的匹配。默认值与可接受别名应在品类 schema 中公开。 - 收集具体商品与依据。逐项记录供应商、所选套餐 / 字重 / 许可、准确价格币种、计费周期、最低购买量和真实来源日期。没有确认的档位保留未知,不能从“低至”单价反推整档价格。
- 分别建模规格与许可。用
specifications表达商品要求,usageContext表达用途,purchaseContext表达已有额度等采购条件。新增字段必须有验证和业务处理,不能接收后忽略。 - 实现品类价格规则。分别计算购买小计、已确认现金总额、消耗分摊和循环费用。维持十进制金额;未知运费、税费、用量档或多年续费保留
null。底层采购成本不得混入客户报价。 - 输出可解释的匹配和分组。每项条件返回
yes / no / unknown与证据 ID;按币种、周期、许可口径或其他真实差异分组。较低价格不应掩盖不匹配的条件。 - 声明实际后续能力。先选择官网购买或询价动作。只有完成供应商权限、认证、余额 / 支付前置条件和可验证交付流程后,才扩展新的执行模式,同时更新能力与响应 schema。
- 注册并验证。把目录数据加入
domain/data.mjs,把品类 schema、规范化、匹配与计算加入domain/engine.mjs,并更新 Worker 的允许品类和能力信息。验证代表性的合法输入、未知价格、冲突别名、授权不匹配及币种分组。 - 重新生成文档合同。运行下方脚本。它从当前品类注册表和 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 报价。