# 美美与共查价系统 · 宿主 OpenAPI 技术对接文档 **版本**:1.2 **更新日期**:2026-07-22 **适用对象**:第三方宿主系统(服务端对接 / 网页嵌入) --- ## 1. 文档说明 本文档供第三方宿主系统对接「美美与共查价中台」使用。 中台对接 **两个官网**,每个官网各有 **免账号 / 登录后** 两种查价方式,合计 **四条互斥路线**: | 路线 ID | 官网 | 账密 | 说明 | |---------|------|------|------| | `MS_GUEST` | MotherShip | 无 | 免登录 Widget / Direct 路径 | | `MS_LOGGED_IN` | MotherShip | 有(该客户已绑定 MotherShip 账密) | 官网 Create shipment 登录态路径 | | `FLOCK_GUEST` | Flock Freight | 无 | 免登录 Direct(失败可回退 DOM) | | `FLOCK_LOGGED_IN` | Flock Freight | 有(该客户已绑定 Flock 账密) | 强制官网登录 DOM,禁止 Direct | **核心规则**:根据「用户/宿主上传的承运商账密」与「本票货物/地址参数是否符合某官网硬限」判定路线后,**整票只走一条路线**,禁止混用上限、混用接口、混用结果字段。 本文档仅列出对调用方有意义且可依赖的字段。响应中如出现未在本文档定义的其他字段,均属于平台保留字段,调用方不应依赖。 **对接形态**: | 形态 | 说明 | 见章节 | |------|------|--------| | OpenAPI 服务端调用 | 宿主后端带 API Key 调两步查价接口 | §2~§6 | | 网页嵌入(iframe) | 宿主页面嵌查价 UI;**须直传类型+Key,跳过登录页** | §2.5 | > **安全要求**:API Key、承运商官网账密仅允许保存在宿主服务端或经我方管理端加密存储;禁止写入宿主公开前端仓库、移动端安装包。OpenAPI 调用禁止把 Key 放进 URL;iframe 嵌入仅允许由**宿主服务端**在生成 `iframe.src` 时短时拼入查询串(见 §2.5)。 --- ## 2. 接入信息 ### 2.1 基础地址 | 环境 | Base URL | |------|----------| | 当前联调 / 演示 | `https://if.dev.51track.vip` | > 公网访问**不要**追加端口(如 `:30325`)。`:30325` 仅用于服务器本机调试。 嵌入页路径:`{Base URL}/embed-demo` ### 2.2 请求格式 - 请求方法:以各接口定义为准 - 请求体:JSON - 字符编码:UTF-8 - Content-Type:`application/json` ### 2.3 鉴权请求头(OpenAPI) | 请求头 | 必填 | 说明 | |--------|------|------| | Authorization | 是 | `Bearer `;Key 通常以 `chj_` 开头,由我方管理端发放 | | X-Customer-Id | 是 | 客户 ID,必须与 API Key 绑定一致 | | Content-Type | 是 | 固定为 `application/json` | 本次联调凭证(示例,正式值由我方单独发放): | 配置项 | 值 | 说明 | |--------|-----|------| | API Key | `<由我方发放>` | 放入 `Authorization: Bearer ...` | | Customer ID | `<由我方发放>` | 放入 `X-Customer-Id` | > **注意(OpenAPI)**:API Key 不得放入 URL、请求体或业务日志明文。 **请求头示例**: ```http Authorization: Bearer chj_xxxxxxxxxxxxxxxxxxxxx X-Customer-Id: CUST_001 Content-Type: application/json ``` 内网联调若使用环境 Token(非正式第三方形态): ```http Authorization: Bearer demo-host-token X-Customer-Id: CUST_001 Content-Type: application/json ``` ### 2.4 通用响应结构 | 字段 | 类型 | 说明 | |------|------|------| | code | Number / String | `0` 表示接口调用成功;非 `0` 表示鉴权、参数或业务处理失败 | | message | String | 提示信息;成功时通常为 `ok` | | data | Object / null | 成功时的业务数据;失败时多为 `null` | ### 2.5 网页嵌入(iframe / CC 等宿主)— 跳过登录页 #### 2.5.1 目标 宿主将查价系统嵌进自有页面时: 1. **用户不应再看到**嵌入页「客户登录」表单。 2. 宿主传入 **登录类型 + 凭证** 后,直接进入 **MotherShip / Flock Freight** 两模块门户。 3. 模块内可再选「免账号 / 登录后」;登录后路线需已绑定承运商官网账密(可在嵌入页内配置)。 #### 2.5.2 URL 查询参数 | 参数 | 必填 | 说明 | |------|------|------| | `login_type` | 建议 | `api_key` 或 `password`;别名:`type`、`lt` | | `api_key` | Key 登录必填 | 客户 API Key;别名:`key`、`token` | | `customer_id` | 密码登录必填 | 客户编号;别名:`customerId` | | `password` | 密码登录必填 | 嵌入页客户密码;别名:`pwd` | | `embed` | 可选 | `1` / `true`:宿主托管壳(隐藏「退出登录」);别名:`hosted`、`from_cc`、`from_host` | 仅带 `api_key`(或 `key`)且未写 `login_type` 时,按 **`api_key` 登录**处理。 #### 2.5.3 推荐写法(CC:API Key) ```text https://if.dev.51track.vip/embed-demo?login_type=api_key&api_key=&embed=1 ``` 短写: ```text https://if.dev.51track.vip/embed-demo?type=api_key&key=&embed=1 ``` **iframe 示例(Key 须由宿主后端渲染进 HTML,禁止写死在公开前端仓库)**: ```html ``` 账号密码直登(较少用): ```text https://if.dev.51track.vip/embed-demo?login_type=password&customer_id=CUST_001&password=<密码>&embed=1 ``` #### 2.5.4 行为与安全 | 步骤 | 行为 | |------|------| | 1 | 打开嵌入页 → 读取查询串 → 调用 `POST /api/embed-demo/login` | | 2 | 成功 → 写入嵌入会话 Cookie → 进入两模块门户(**不展示登录页**) | | 3 | 前端 `history.replaceState` 去掉地址栏中的 Key/密码等敏感参数 | | 4 | 失败 → 展示错误文案,并可回落手动登录表单 | 安全约定: - Key **只允许**宿主服务端拼进 `iframe.src`;用户浏览器地址栏会短暂可见,登录成功后会被剥离。 - 生产环境嵌入 Cookie 使用 `SameSite=None; Secure`,以支持跨站 iframe。 - 勿在公开仓库、前端构建产物、CDN 静态配置中明文提交 API Key。 - OpenAPI 仍须走 `Authorization` 头(§2.3),与嵌入 URL 直传互不替代。 #### 2.5.5 常见错误 | 现象 | 可能原因 | 处理 | |------|----------|------| | 仍停在登录页 | 未传 `api_key` / `login_type`,或 Key 为空 | 检查 iframe `src` | | 提示 API Key 无效 | Key 错误、已轮换、或客户停用 | 管理端核对 Key 与客户状态 | | 提示缺少 Authorization | 宿主在调 OpenAPI 时未带鉴权头 | 与嵌入无关;按 §2.3 补头 | | 登录成功但刷新掉登录 | 跨站 Cookie 被拦 / 非 HTTPS | 确认生产 HTTPS;Cookie 为 None+Secure | **成功响应示例**(OpenAPI 通用): ```json { "code": 0, "message": "ok", "data": { } } ``` **失败响应示例**: ```json { "code": "VALIDATION_FAILED", "message": "请填写 2 位州码", "data": null } ``` > 调用方不能仅根据 HTTP 状态码判断业务成功。仅当 `code = 0` 时,才能判定本次接口调用成功。 > 异步询价业务失败时,轮询接口可能仍返回 HTTP 200 且 `code = 0`,此时须检查 `data.status`:若为 `failed`,按失败处理并展示 `data.error_message`。 --- ## 3. 路线判定(必读) 宿主在发起询价前,按下列顺序 **确定唯一路线**,然后只调用该路线对应接口与字段。 ### 3.1 判定流程 ``` 1) 宿主明确选择官网,或按参数硬限自动匹配: - 符合 MotherShip 硬限且业务选 MotherShip → 进入 MS_* - 符合 Flock 硬限且业务选 Flock → 进入 FLOCK_* - 两网站都不符合 → 拒绝询价,提示用户改货改地址 - 禁止:同一票并行打两家再拼结果(除非宿主产品明确要求「比价」且各自独立展示) 2) 查该客户在我方是否已绑定对应官网账密(管理端「查价网站账密」): - MotherShip 已绑账密 → 必须走 MS_LOGGED_IN(服务端强制登录态,禁止匿名 Direct/缓存价) - MotherShip 未绑账密 → 走 MS_GUEST - Flock 已绑账密 → 必须走 FLOCK_LOGGED_IN(服务端强制 DOM 登录,禁止 Direct) - Flock 未绑账密 → 走 FLOCK_GUEST 3) 账密与参数必须同属一条路线: - 有 Flock 账密但货物只符合 MotherShip → 只能走 MS_*(Flock 账密本票不用) - 有 MotherShip 账密但货物只符合 Flock → 只能走 FLOCK_* - 有账密却按免账号字段凑合提交:服务端仍可能强制登录态,价格口径与免账号不同,宿主须按登录后字段规范传参 ``` ### 3.2 两官网硬限对照(换算后英制) | 项 | MotherShip(MS_*) | Flock(FLOCK_*) | |----|--------------------|------------------| | 地址形态 | 街道 + 城市 + 州码(建议邮编);须经候选确认 | **仅邮编**(5 位);提货邮编 ≠ 派送邮编 | | 托盘 / 件数 | 1~25 | 免账号 **4~20**;登录后 `form_mode=logged_in_quick` 时 1~999 | | 单托重量 | 0.01~9999 lb;登录后 Weight each ≤ **5000 lb** | 按**整票总重**,≤ **45000 lb** | | 尺寸 | L≤999 in,W/H≤99 in | L≤636 in,W≤102 in,H≤108 in | | 其它 | 整票总重 ≤ 249975 lb | 免账号须满足拖车线性英尺(约 53') | 重量 / 尺寸支持 `lb`/`kg`/`t`(公制吨)、`in`/`cm`/`m` 入参,服务端统一换算为 lb/in(小数进一取整)。宿主应在提交前按目标官网硬限拦截。 ### 3.3 接口总览 | 路线 | 推荐调用链 | 等待方式 | 典型耗时 | |------|------------|----------|----------| | `MS_GUEST` | `POST /api/host/quote/candidates` → `POST /api/host/quote/submit` | **同步**(submit 内等待) | 10~420 s | | `MS_LOGGED_IN` | `POST /api/addresses/mothership-suggest`(可选)→ 组地址 → `POST /api/quotes` → `GET /api/quotes/{quote_id}` | **异步轮询** | 轮询总窗口建议 ≥ 420 s | | `FLOCK_GUEST` | `POST /api/flock/quotes` → `GET /api/quotes/{quote_id}` | **异步轮询** | 建议 ≥ 210 s(可配置) | | `FLOCK_LOGGED_IN` | 同上;`flock_input.form_mode = "logged_in_quick"` | **异步轮询** | 建议 ≥ 210 s | > `MS_GUEST` 也可改用异步三步(`POST /api/quotes` + 轮询),但第三方默认同步对接请用宿主两步接口。 > 已绑定 MotherShip 账密时,即便误调宿主两步接口,服务端仍会强制登录态 RPA;宿主两步**不支持**登录后扩展字段(`cargo_lines` / `mothership_details` 等),完整登录后能力请走 `MS_LOGGED_IN` 异步接口。 --- ## 4. 承运商账密(路线开关) 账密由我方管理端按客户绑定,RPA 运行时解密使用,**不进入询价请求体**。 ### 4.1 管理端接口(内部运维,非宿主日常调用) ```http GET /api/admin/customers/{customer_id}/provider-credentials PUT /api/admin/customers/{customer_id}/provider-credentials ``` 需管理员 JWT。PUT 示例: ```json { "provider": "mothership", "email": "user@example.com", "password": "********" } ``` | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | provider | String | 是 | `mothership` 或 `flock` | 对应官网 | | email | String | 是 | 非空 | 官网登录邮箱 | | password | String | 是 | 非空 | 官网登录密码 | **影响**: - 绑定成功后,该客户对该 `provider` **自动切换到对应 `*_LOGGED_IN` 路线**。 - 账密错误时询价可能返回 `PROVIDER_LOGIN_FAILED`(或业务失败 `error_message` 提示确认账密)。 - 更换账密须按管理端策略操作;勿在业务日志打印明文密码。 宿主侧若自行收集用户官网账密,应通过约定安全通道交给我方写入管理端,或由双方约定的 BFF 代写;**询价 OpenAPI 请求体中不要传官网密码**。 --- ## 5. 路线 A:MotherShip 免账号(`MS_GUEST`) 适用:客户**未**绑定 MotherShip 账密;参数符合 MotherShip 硬限。 ### 5.1 调用流程 1. 带鉴权头调用「获取地址候选」。 2. 保存 `host_session_id`(约 30 分钟有效)。 3. 用户从 `pickup_candidates`、`delivery_candidates` 各选 1 条 `selectable = true`。 4. 将**完整候选对象**与 `host_session_id` 提交「提交并获取报价」。 5. 第二步可能持续 **10~420 秒**;客户端与网关超时建议 **≥ 420 秒**。 6. `status = done` 展示 `quotes[]`;`failed` 展示中文 `error_message`。 > 第二步期间展示 Loading,并禁止重复提交。 ### 5.2 第一步:获取地址候选 #### 接口地址 `POST /api/host/quote/candidates` #### 请求参数 | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | pickup_address | Object | 是 | 见下表 | 提货草稿地址 | | delivery_address | Object | 是 | 见下表 | 派送草稿地址 | | cargo | Object | 是 | 见下表 | 货物信息 | **地址对象(pickup_address / delivery_address)** | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | street | String | 是 | 非空 | 街道地址 | | city | String | 是 | 非空 | 城市 | | state | String | 是 | 固定 2 位 | 美国州二字码,如 `CA`、`TX` | | zip | String | 否 | `12345` 或 `12345-6789`;可空 | 邮编;建议传 | **货物对象(cargo)** | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | weight.value | Number | 是 | 正数 | 单托重量数值 | | weight.unit | String | 是 | `lb`、`kg` 或 `t`(公制吨);别名 `lbs`/`pound(s)`/`tonne(s)`;**禁止** `ton`/`tons`(美制短吨歧义) | 重量单位 | | dimensions.length / width / height | Number | 是 | 正数 | 单托尺寸 | | dimensions.unit | String | 是 | `in`、`cm` 或 `m`;别名 `inch`/`meter` 亦可 | 尺寸单位 | | pallet_count | Number | 是 | 整数 1~25 | 托盘数 | | cargo_type | String | 是 | 见 §12.1 | 货物类型枚举 | **请求示例**: ```json { "pickup_address": { "street": "1234 Warehouse Blvd", "city": "Los Angeles", "state": "CA", "zip": "90001" }, "delivery_address": { "street": "5678 Distribution Dr", "city": "Dallas", "state": "TX", "zip": "75201" }, "cargo": { "weight": { "value": 500, "unit": "kg" }, "dimensions": { "length": 120, "width": 100, "height": 150, "unit": "cm" }, "pallet_count": 2, "cargo_type": "general_freight" } } ``` #### 响应数据 | 字段 | 类型 | 说明 | |------|------|------| | data.host_session_id | String (UUID) | 第二步必传;约 30 分钟有效 | | data.pickup_candidates | Array\ | 提货候选 | | data.delivery_candidates | Array\ | 派送候选 | | data.requires_selection | Boolean | 是否需要用户选择 | **候选对象字段** | 字段 | 类型 | 说明 | |------|------|------| | option_id | String | 候选唯一标识 | | display_label | String | 展示文案 | | formatted_address | String | 格式化地址 | | street / city / state / zip | String | 结构化地址 | | selectable | Boolean | 仅可选 `true` | | unavailable_reason | String | 不可选原因(可能出现) | #### 调用后处理规则 1. 必须保存 `host_session_id`。 2. 提货、派送各选 1 条 `selectable = true`。 3. 第二步必须回传第一步返回的**完整候选对象**,禁止只传 `option_id`,禁止自行拼装。 4. 会话过期后须重新调用本接口。 ### 5.3 第二步:提交候选并获取报价 #### 接口地址 `POST /api/host/quote/submit` #### 请求参数 | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | host_session_id | String | 是 | UUID;来自第一步 | 宿主会话 ID | | pickup_candidate | Object | 是 | 第一步完整候选 | 提货地址 | | delivery_candidate | Object | 是 | 第一步完整候选 | 派送地址 | #### 超时与重试 - 客户端 HTTP 超时:`420000` ms。 - 网关 `proxy_read_timeout` ≥ 420 秒。 - `SESSION_EXPIRED`:重新走第一步。 - `QUOTE_TIMEOUT`:可稍后重试;建议重新获取候选。 #### 响应数据 | 字段 | 类型 | 说明 | |------|------|------| | data.quote_id | String | 报价单号 | | data.request_id | String | 请求标识 | | data.status | String | `done` / `failed` | | data.currency | String | 固定 `USD` | | data.valid_until | String | ISO 有效期 | | data.source_type | String | `rpa` / `cache` / `stale` 等 | | data.quotes | Array\ | 档位列表 | | data.error_code / error_message | String | 失败时 | **quotes[] 字段** | 字段 | 类型 | 说明 | |------|------|------| | service_level | String | 如 `standard`、`guaranteed` | | rate_option | String | 如 `lowest`、`fastest`、`bestValue` | | carrier | String | 承运商 | | transit_days | String | 时效描述 | | final_total | Number | **对客户展示价(USD,已含加价)** | > 档位数量不固定,按实际列表展示。 **成功响应示例**: ```json { "code": 0, "message": "ok", "data": { "quote_id": "QTE_20260630_0003", "request_id": "xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx", "status": "done", "currency": "USD", "valid_until": "2026-06-30T10:30:00.000Z", "source_type": "rpa", "quotes": [ { "service_level": "standard", "rate_option": "lowest", "carrier": "Frontline Freight", "transit_days": "Est. 5 business days", "final_total": 337.06 } ] } } ``` --- ## 6. 路线 B:MotherShip 登录后(`MS_LOGGED_IN`) 适用:客户**已**绑定 MotherShip 账密;参数符合 MotherShip 硬限(含登录后 Weight each ≤ 5000 lb)。 服务端检测到账密后会强制登录态查价,禁止命中匿名 Direct / L2 缓存价。 ### 6.1 调用流程 1. (推荐)用 `POST /api/addresses/mothership-suggest` 按关键字联想,用户点选候选。 2. 组装完整询价体(含地址确认字段 + 可选登录后字段)。 3. `POST /api/quotes` → 得到 `quote_id`,`status` 多为 `processing`。 4. 轮询 `GET /api/quotes/{quote_id}` 至 `done` / `failed`(间隔建议 2 s,总窗口 ≥ 420 s)。 5. 成功时读 `data.quotes[]`(结构同 §5.3)。 ### 6.2 地址联想(可选但推荐) #### 接口地址 `POST /api/addresses/mothership-suggest` #### 请求参数 | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | customer_id | String | 是 | 与鉴权客户一致 | 客户 ID | | query | String | 是 | 去空白后 ≥ 3 字符 | 用户键入的地址关键字 | **请求示例**: ```json { "customer_id": "CUST_001", "query": "1234 Warehouse" } ``` #### 响应数据 | 字段 | 类型 | 说明 | |------|------|------| | data.candidates | Array\ | 候选列表(字段同 §5.2 候选对象) | 用户选定后,须把候选映射进 `pickup_address` / `delivery_address`(见下节必填确认字段)。 ### 6.3 提交询价 #### 接口地址 `POST /api/quotes` #### 请求参数(核心) | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | request_id | String | 是 | 非空;建议 UUID | 幂等 / 追踪 | | customer_id | String | 是 | 与鉴权一致 | 客户 ID | | pickup_address | Object | 是 | 见下表 | 提货地址(须已确认) | | delivery_address | Object | 是 | 见下表 | 派送地址(须已确认) | | weight | Object | 是 | value 正数;unit `lb`/`kg`/`t` | 单托重量(兼容字段) | | dimensions | Object | 是 | unit `in`/`cm`/`m` | 单托尺寸(兼容字段) | | pallet_count | Number | 是 | 1~25 | 托盘数;多行时建议为各行 quantity 合计 | | cargo_type | String | 是 | 见 §12.1 | 货物类型 | | service_level | String | 否 | `standard` / `guaranteed` | 服务等级 | | rate_option | String | 否 | `lowest` / `fastest` | 价格偏好 | | ready_date | String | 否 | `YYYY-MM-DD`;周末会拨到下一工作日 | 可提货日 | | ready_time | String | 否 | ≤32 字符 | 可提货时间 | | pickup_accessorials | Array\ | 否 | 每项 ≤64;最多 20 | 提货附加服务 id | | delivery_accessorials | Array\ | 否 | 同上 | 派送附加服务 id | | cargo_lines | Array\ | 否 | 1~20 行 | 登录后多行货物(RPA 按行填);**字段已为英制,不做 kg/cm/m/t 换算** | | mothership_details | Object | 否 | 见下 | 二级 Details(联系人/FBA 等) | **地址对象必填确认字段(与免账号草稿不同)** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | street / city / state | String | 是 | 结构化地址;state 2 位 | | zip | String | 否 | 可空字符串 | | place_id | String | 是 | 通常用候选 `option_id` | | formatted_address | String | 是 | 完整展示地址 | | selected_from_suggestions | Boolean | 是 | 必须为 `true` | | mothership_option_id | String | 是 | 候选 `option_id` | | mothership_display_label | String | 是 | 候选 `display_label` | | selected_from_mothership | Boolean | 是 | 必须为 `true` | **cargo_lines[]** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | cargo_type | String | 是 | 行货物类型文案/枚举 | | quantity | Number | 是 | 正数,≤999 | | weight_lb | Number | 是 | 该行单件重量 **lb**(须已换算;不接受 kg/t)建议 ≤5000 | | length_in / width_in / height_in | Number | 是 | 该行尺寸 **英寸**(须已换算;不接受 cm/m) | **mothership_details(节选)** | 字段 | 类型 | 说明 | |------|------|------| | pickup / delivery | Object | `company_name`、`suite`、联系人姓名邮箱电话、`reference`、`notes`、`opens_at`、`closes_at` | | request_delivery_appointment | Boolean | 是否预约送货 | | fba_number / fba_po_number | String | FBA 相关 | | cargo[] | Array | `description`、`nmfc`、`hazmat`、`alcohol`、`tobacco`、件数类型与数量等 | **请求示例(登录后最小可用)**: ```json { "request_id": "2dd352e6-9a1d-4563-8f13-ebff18aabdb9", "customer_id": "CUST_001", "pickup_address": { "street": "1234 Warehouse Street", "city": "Los Angeles", "state": "CA", "zip": "90001", "place_id": "Eisx...", "formatted_address": "1234 Warehouse Street, Los Angeles, CA, USA", "selected_from_suggestions": true, "mothership_option_id": "Eisx...", "mothership_display_label": "1234 Warehouse Street, Los Angeles, CA, USA", "selected_from_mothership": true }, "delivery_address": { "street": "5678 Distribution Dr, Wilmer", "city": "Dallas", "state": "TX", "zip": "75201", "place_id": "EihE...", "formatted_address": "Distribution Dr, Wilmer, Dallas, TX, USA", "selected_from_suggestions": true, "mothership_option_id": "EihE...", "mothership_display_label": "Distribution Dr, Wilmer, Dallas, TX, USA", "selected_from_mothership": true }, "weight": { "value": 1103, "unit": "lb" }, "dimensions": { "length": 48, "width": 40, "height": 60, "unit": "in" }, "pallet_count": 2, "cargo_type": "general_freight", "ready_date": "2026-07-22", "cargo_lines": [ { "cargo_type": "general_freight", "quantity": 2, "weight_lb": 1103, "length_in": 48, "width_in": 40, "height_in": 60 } ] } ``` #### 提交成功响应 ```json { "code": 0, "message": "ok", "data": { "quote_id": "QTE_20260721_0012", "status": "processing" } } ``` 随后进入 §9 轮询。 --- ## 7. 路线 C:Flock 免账号(`FLOCK_GUEST`) 适用:客户**未**绑定 Flock 账密;参数符合 Flock 免账号硬限(托盘 4~20、总重 ≤45000 lb、线性英尺等)。 服务端优先走 Direct JSON;失败时按环境配置可回退 DOM 填表(可能弱注册)。 ### 7.1 调用流程 1. 校验邮编、取货日、件数、总重、尺寸与线性英尺。 2. `POST /api/flock/quotes` → `quote_id` + `processing`。 3. 轮询 `GET /api/quotes/{quote_id}`(总窗口建议 ≥ 210 s)。 4. 成功时读 `data.flock.lines[]`(**不是** MotherShip 的 `quotes[]` 形态)。 ### 7.2 提交询价 #### 接口地址 `POST /api/flock/quotes` > 若服务端关闭 Flock 能力,可能返回业务不可用(如 `QUOTE_UNAVAILABLE`)。 #### 请求参数 | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | request_id | String | 是 | 非空 | 请求标识 | | customer_id | String | 是 | 与鉴权一致 | 客户 ID | | flock_input | Object | 是 | 见下表 | Flock 表单参数 | **flock_input(免账号)** | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | pickup_date | String | 是 | `MM/DD/YYYY`;不可早于今天;不可周末 | 提货日 | | pickup_zip | String | 是 | 5 位邮编 | 提货邮编 | | delivery_zip | String | 是 | 5 位;≠ pickup_zip | 派送邮编 | | pallet_count | Number | 是 | 整数 **4~20** | 托盘件数 | | total_weight | Object | 是* | value 正数;unit `lb`/`kg`/`t` | 整票总重;也可用 `total_weight_lb` | | dimensions | Object | 否 | 缺省 48×40×48 in;unit `in`/`cm`/`m` | 单托尺寸 | | form_mode | String | 否 | 免账号**不要**传 `logged_in_quick` | 省略即为匿名 | | registration | Object | 否 | 见附录 | DOM 回退时可选自填弱注册信息 | \* `total_weight` 与 `total_weight_lb` 二选一即可。 **硬限(换算后)** | 项 | 限制 | |----|------| | 总重 | ≤ 45000 lb | | 尺寸 | L≤636,W≤102,H≤108(in) | | 线性英尺 | 整票占用 ≤ 约 53'(否则校验失败) | **请求示例**: ```json { "request_id": "a1b2c3d4-e5f6-4789-a012-3456789abcde", "customer_id": "CUST_001", "flock_input": { "pickup_date": "07/22/2026", "pickup_zip": "90001", "delivery_zip": "75201", "pallet_count": 6, "total_weight": { "value": 12000, "unit": "lb" }, "dimensions": { "length": 48, "width": 40, "height": 48, "unit": "in" } } } ``` #### 提交成功响应 ```json { "code": 0, "message": "ok", "data": { "quote_id": "QTE_20260721_0201", "status": "processing" } } ``` ### 7.3 结果字段(轮询成功后) | 字段 | 类型 | 说明 | |------|------|------| | data.flock.reference | String | Flock 参考号(可能为空) | | data.flock.lines[] | Array | 报价行 | | data.flock.lines[].tier | String | `flock_direct` / `standard` | | data.flock.lines[].label | String | 展示名 | | data.flock.lines[].transit_days / transitDays | String | 时效 | | data.flock.lines[].final_total_usd | Number | **对客户展示价(USD,已含加价)** | | data.flock.shipment_meta | Object | 提货日、邮编、重量等摘要 | > 一档即可能成功;勿假设固定两档。展示价优先 `final_total_usd`。 --- ## 8. 路线 D:Flock 登录后(`FLOCK_LOGGED_IN`) 适用:客户**已**绑定 Flock 账密;参数按登录后 Quick 表单填写。 服务端强制官网登录 DOM,**禁止**匿名 Direct。宿主须传 `form_mode: "logged_in_quick"`,并尽量补齐登录后字段,否则 RPA 可能填表失败。 ### 8.1 调用流程 与 §7.1 相同(同一对接口),差异仅在请求体与服务端执行路径。 ### 8.2 提交询价 #### 接口地址 `POST /api/flock/quotes`(同路线 C) #### flock_input 相对免账号的差异 | 字段 | 类型 | 必填 | 约束 | 说明 | |------|------|------|------|------| | form_mode | String | **是(本路线)** | 固定 `logged_in_quick` | 切换登录后校验与 RPA 表单 | | pallet_count | Number | 是 | **1~999** | 登录后件数上限放宽 | | pickup_location_type | String | 建议 | 官网地点类型 | 如商业/住宅等 id/文案 | | delivery_location_type | String | 建议 | 同上 | 派送地点类型 | | packaging_type | String | 建议 | 包装类型 | 登录后 Quick 字段 | | freight_class | String | 建议 | 货运等级 | 登录后 Quick 字段 | | description | String | 建议 | 货物描述 | 登录后 Quick 字段 | | stackable / turnable | Boolean | 否 | | 可堆叠 / 可翻转 | | additional_services | Array\ | 否 | | 附加服务 id | | vehicle_types | Array\ | 否 | | 允许车型 id | | pickup_liftgate / pickup_inside / pickup_pallet_jack | Boolean | 否 | | 提货附属 | | delivery_liftgate / delivery_inside / delivery_pallet_jack | Boolean | 否 | | 派送附属 | | call_before_pickup / call_before_delivery | Boolean | 否 | | 提货/派送前电话 | | pickup_service / delivery_service 及窗口时间字段 | String | 否 | 总重 >5000 lb 时官网才开放高级调度 | 见实现字段名 | > 登录后**不再强制**免账号的 4~20 托盘与线性英尺校验;尺寸/总重上限仍适用 Flock 官网硬限。 **请求示例**: ```json { "request_id": "b2c3d4e5-f6a7-4890-b123-456789abcdef", "customer_id": "CUST_001", "flock_input": { "form_mode": "logged_in_quick", "pickup_date": "07/22/2026", "pickup_zip": "90001", "delivery_zip": "75201", "pallet_count": 2, "total_weight": { "value": 2200, "unit": "lb" }, "dimensions": { "length": 48, "width": 40, "height": 48, "unit": "in" }, "packaging_type": "Pallets", "freight_class": "70", "description": "General freight", "pickup_location_type": "Business with a dock or forklift", "delivery_location_type": "Business with a dock or forklift" } } ``` 结果字段同 §7.3。 --- ## 9. 通用:异步轮询 适用于 `MS_LOGGED_IN`、`FLOCK_GUEST`、`FLOCK_LOGGED_IN`(以及非推荐的 MotherShip 异步三步)。 ### 9.1 接口地址 `GET /api/quotes/{quote_id}` ### 9.2 请求 - 方法:GET - 路径参数:`quote_id`(提交接口返回) - 鉴权头:同 §2.3(须为报价所属客户) ### 9.3 响应数据 | 字段 | 类型 | 说明 | |------|------|------| | data.quote_id | String | 报价单号 | | data.request_id | String | 请求标识 | | data.status | String | `processing` / `done` / `failed` / `expired` | | data.currency | String | `USD` | | data.quotes | Array | MotherShip 档位(MS_* 成功时) | | data.flock | Object | Flock 结果(FLOCK_* 成功时) | | data.error_code / error_message | String | 失败时 | | data.rpa_stage / rpa_stage_label | String | processing 时可能出现的进度 | | data.valid_until | String | 成功时有效期 | ### 9.4 轮询建议 | 项 | 建议 | |----|------| | 间隔 | 2 s;10 s 后可加密至约 0.8 s | | MotherShip 总窗口 | ≥ `420000` ms | | Flock 总窗口 | ≥ `210000` ms(以联调环境配置为准) | | 终止条件 | `done` / `failed` / `expired` / 超时 | | 并发 | 同一用户操作防重复提交 | --- ## 10. 错误码 ### 10.1 接口层错误(`code !== 0`) | code | HTTP | 说明 | 处理建议 | |------|------|------|----------| | UNAUTHORIZED | 401 | 缺少或无效 API Key | 检查 Bearer / 轮换 | | FORBIDDEN | 403 | 客户与 Key 不匹配,或无权访问报价 | 核对 `X-Customer-Id` / `quote_id` | | VALIDATION_FAILED | 400 | 字段不合法 | 按**当前路线**硬限与必填项检查 | | SESSION_EXPIRED | 404 | `host_session_id` 过期 | 仅 MS_GUEST:重走候选 | | ADDRESS_SESSION_MISMATCH | 400 | 候选不属于本次会话 | 使用返回的完整对象 | | ADDRESS_NOT_SELECTABLE | 400 | 提交了不可选候选 | 改选 `selectable: true` | | QUOTE_NOT_FOUND | 404 | 报价不存在 | 检查 quote_id | | QUOTE_TIMEOUT | 504 | 同步等待超时 | 稍后重试 | | RATE_LIMITED | 429 | 限流 | 降频 | | QUOTE_UNAVAILABLE | 503 | 渠道不可用(如 Flock 关闭/占用) | 稍后重试或换路线 | | INTERNAL_ERROR | 5xx | 系统异常 | 稍后重试 | ### 10.2 询价业务失败(`code = 0` 且 `data.status = failed`) | error_code | 说明 | |------------|------| | QUOTE_UNAVAILABLE | 暂时无法获取报价 | | QUOTE_TIMEOUT | 询价超时 | | QUOTE_ENTRY_UNAVAILABLE | 报价入口暂时无法打开 | | ADDRESS_SUGGESTION_NOT_FOUND | 地址无法匹配 | | CARRIER_NO_CAPACITY | 该线路暂无可用运力 | | ADDRESS_NOT_SUPPORTED | 地址不在服务范围 | | RPA_CAPTCHA | 触发验证码 | | SESSION_EXPIRED | 承运商登录会话过期 | | PROVIDER_LOGIN_FAILED | 承运商账密不正确或未即时更新 | | RPA_DATA_INVALID | 未能解析完整报价 | | PAGE_LOAD_TIMEOUT | 承运商页面加载超时 | > 对外展示优先使用中文 `error_message`。 --- ## 11. 对接建议 ### 11.1 第三方需要做 1. 服务端保存 API Key 与 Customer ID,后端代理调用。 2. 实现路线判定(§3),**一票一路**。 3. 账密由管理端绑定;有账密则按登录后字段规范传参。 4. `MS_GUEST`:地址选择 UI + submit Loading(≥420 s)。 5. 异步路线:实现轮询与超时;Flock 读 `flock.lines`,MotherShip 读 `quotes`。 6. 成功后注意 `valid_until`。 ### 11.2 第三方不需要做 1. 不需要在请求体传官网密码。 2. 不需要自行做单位换算(支持 `lb`/`kg`/`t`、`in`/`cm`/`m`,服务端统一进一取整为 lb/in)。 3. 不需要调用管理端客户 CRUD / embed-demo 登录作为正式对接。 4. 不需要把 Priority1 实验接口当作本期正式四路线之一。 ### 11.3 cURL 示例 **MS_GUEST 第一步**: ```bash curl -X POST "https://if.dev.51track.vip/api/host/quote/candidates" \ -H "Authorization: Bearer " \ -H "X-Customer-Id: CUST_001" \ -H "Content-Type: application/json" \ --data-binary @ms-guest-candidates.json ``` **FLOCK_GUEST 提交**: ```bash curl -X POST "https://if.dev.51track.vip/api/flock/quotes" \ -H "Authorization: Bearer " \ -H "X-Customer-Id: CUST_001" \ -H "Content-Type: application/json" \ --data-binary @flock-guest.json ``` **轮询**: ```bash curl -X GET "https://if.dev.51track.vip/api/quotes/QTE_20260721_0201" \ -H "Authorization: Bearer " \ -H "X-Customer-Id: CUST_001" ``` ### 11.4 Apifox 环境变量建议 | 变量 | 示例值 | |------|--------| | baseUrl | `https://if.dev.51track.vip` | | token | `chj_...` | | customerId | 与 Token 绑定的客户 ID | Headers:`Authorization: Bearer {{token}}`,`X-Customer-Id: {{customerId}}`,`Content-Type: application/json`。 --- ## 12. 附录 ### 12.1 货物类型枚举(cargo_type,MotherShip) | 值 | 说明 | |----|------| | general_freight | 普通货物(默认) | | machinery | 机械设备 | | furniture | 家具 | | electronics | 电子产品 | | building_materials | 建材 | | auto_parts | 汽车配件 | | food_nonperishable | 非生鲜食品 | | apparel | 服装 | | other | 其他 | ### 12.2 报价状态(status) | 值 | 说明 | |----|------| | processing | 询价进行中(须继续轮询) | | done | 成功 | | failed | 失败,看 error_message | | expired | 报价已过期 | ### 12.3 Flock registration(可选,免账号 DOM 回退) | 字段 | 类型 | 说明 | |------|------|------| | first_name / last_name | String | 姓名 | | company | String | 公司 | | email | String | 工作邮箱 | | phone | String | 10 位美国电话 | | shipments_per_month | String | `1-25` / `26-50` / `51-100` / `101+` | ### 12.4 非推荐 / 不在本期四路线 | 说明 | 接口 | |------|------| | MotherShip 免账号异步三步(可用,非默认同步方案) | `POST /api/quotes` + 轮询 | | Priority1(实验) | `POST /api/priority1/quotes` | | `/embed-demo` 演示门户 | Cookie 登录,非正式 OpenAPI | | 管理端客户 / 预警等 | 仅运维 | --- ## 13. 联系与支持 | 事项 | 说明 | |------|------| | 开通客户 / API Key / 绑定官网账密 | 联系我方运维或管理端操作 | | Key 失效 | 轮换后立即通知宿主更换 | | 故障排查 | 提供时间、路线 ID、`quote_id` 或 `host_session_id`、请求摘要(勿传完整 Key/密码) | | 文档版本 | 1.1(2026-07-21) |