# MotherShip 查询接口调用文档 本文档只说明 **MotherShip / Axel** 查询报价接口如何调用,适合直接发给第三方开发人员使用。 Base URL:`https://if.dev.51track.vip` --- ## 1. 推荐接口 当前使用 **两步宿主接口**(须鉴权): 1. `POST /api/host/quote/candidates` 2. `POST /api/host/quote/submit` 特点: - 第一步返回联想地址候选 - 第二步服务端内部等待并轮询 - 调用方**不需要自己轮询** --- ## 2. 鉴权(必传) 当前服务器已开启鉴权(`HOST_PUBLIC_API_ENABLED=false`),**每次请求**须携带以下请求头: ```http Authorization: Bearer X-Customer-Id: CUST_001 Content-Type: application/json ``` | 字段 | 值 | 说明 | |------|-----|------| | `Authorization` | `Bearer ` | 我方单独发放的 API Token | | `X-Customer-Id` | `CUST_001` | 固定客户 ID,须与 Token 绑定一致 | **示例(联调 Token):** ```http Authorization: Bearer demo-host-token X-Customer-Id: CUST_001 Content-Type: application/json ``` > 生产环境 Token 由我方另行发放,格式同上;请勿将 Token 写入前端页面或公开仓库。 ### 鉴权错误 | HTTP | message | 说明 | |------|---------|------| | 401 | 缺少 Authorization 头 | 未带 `Authorization: Bearer ...` | | 401 | Service Token 无效 | Token 拼写错误或不在白名单 | | 401 | 令牌无效或已过期 | 误用了管理员 JWT,应使用 Service Token | ### Apifox 环境变量建议 | 变量 | 示例值 | |------|--------| | `baseUrl` | `https://if.dev.51track.vip` | | `token` | `demo-host-token`(联调)或我方发放的生产 Token | | `customerId` | `CUST_001` | 在「Auth」或「Headers」中配置: - `Authorization`:`Bearer {{token}}` - `X-Customer-Id`:`{{customerId}}` - `Content-Type`:`application/json` --- ## 3. 第一步:获取联想地址候选 ### 接口 ```http POST /api/host/quote/candidates ``` ### 请求头示例 ```http Authorization: Bearer demo-host-token X-Customer-Id: CUST_001 Content-Type: application/json ``` ### 请求体 ```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" } } ``` ### 字段说明 | 字段 | 说明 | |------|------| | `pickup_address` | 提货草稿地址 | | `delivery_address` | 派送草稿地址 | | `street/city/state` | 必填,`state` 为美国州二字码 | | `zip` | 可选,建议传 | | `cargo.weight.unit` | `kg` 或 `lb` | | `cargo.dimensions.unit` | `cm` 或 `in` | | `cargo.pallet_count` | 托盘数,1–25 | | `cargo.cargo_type` | `general_freight` 等 | ### 成功响应示例 ```json { "code": 0, "message": "ok", "data": { "host_session_id": "2dd352e6-9a1d-4563-8f13-ebff18aabdb9", "pickup_candidates": [ { "option_id": "Eisx...", "display_label": "1234 Warehouse Street, Los Angeles, CA, USA", "formatted_address": "1234 Warehouse Street, Los Angeles, CA, USA", "street": "1234 Warehouse Street", "city": "Los Angeles", "state": "CA", "zip": "90001", "selectable": true } ], "delivery_candidates": [ { "option_id": "EihE...", "display_label": "Distribution Dr, Wilmer, Dallas, TX, USA", "formatted_address": "Distribution Dr, Wilmer, Dallas, TX, USA", "street": "5678 Distribution Dr, Wilmer", "city": "Dallas", "state": "TX", "zip": "75201", "selectable": true } ], "requires_selection": true } } ``` ### 第一步调用后的处理 1. 保存 `data.host_session_id`(约 30 分钟有效) 2. 从 `pickup_candidates` 中选择 1 条 `selectable: true` 3. 从 `delivery_candidates` 中选择 1 条 `selectable: true` 4. 将选中的**完整候选对象**用于第二步请求 不要使用 `selectable: false` 的候选。 --- ## 4. 第二步:提交候选并直接获取最终报价 ### 接口 ```http POST /api/host/quote/submit ``` ### 请求头 与第一步相同(须带 `Authorization` 与 `X-Customer-Id`)。 ### 请求体 ```json { "host_session_id": "2dd352e6-9a1d-4563-8f13-ebff18aabdb9", "pickup_candidate": { "option_id": "Eisx...", "display_label": "1234 Warehouse Street, Los Angeles, CA, USA", "formatted_address": "1234 Warehouse Street, Los Angeles, CA, USA", "street": "1234 Warehouse Street", "city": "Los Angeles", "state": "CA", "zip": "90001", "selectable": true }, "delivery_candidate": { "option_id": "EihE...", "display_label": "Distribution Dr, Wilmer, Dallas, TX, USA", "formatted_address": "Distribution Dr, Wilmer, Dallas, TX, USA", "street": "5678 Distribution Dr, Wilmer", "city": "Dallas", "state": "TX", "zip": "75201", "selectable": true } } ``` ### 注意 - `host_session_id` 必须来自第一步返回值 - `pickup_candidate` / `delivery_candidate` 必须是第一步候选列表中的**完整对象** - 第二步会等待 10–30 秒,最长约 420 秒 - 第二步**不会**返回 `processing` 让你继续轮询;服务端会自己处理轮询 ### 成功响应示例 ```json { "code": 0, "message": "ok", "data": { "quote_id": "QTE_20260630_0003", "status": "done", "currency": "USD", "valid_until": "2026-06-30T10:30:00.000Z", "quotes": [ { "service_level": "standard", "rate_option": "lowest", "carrier": "Frontline Freight", "transit_days": "Est. 5 business days", "final_total": 337.06 } ] } } ``` ### 应展示给用户的字段 | 字段 | 说明 | |------|------| | `quotes[].final_total` | 最终展示价(USD) | | `quotes[].carrier` | 承运商 | | `quotes[].transit_days` | 时效 | | `valid_until` | 报价有效期 | --- ## 5. 调用步骤总结 ### 业务步骤 1. 带鉴权头调 `POST /api/host/quote/candidates` 2. 取得 `host_session_id` 3. 让用户在候选地址中各选一条可用地址 4. 带鉴权头调 `POST /api/host/quote/submit` 5. 直接拿最终报价并展示 ### 第三方系统需要做的事 - 在服务端保存 Token,**不要**暴露在前端 - 每次请求携带 `Authorization` + `X-Customer-Id: CUST_001` - 第一步展示候选地址选择 UI - 第二步显示 Loading(建议超时 ≥ 420 秒) - 第二步返回后展示报价 ### 第三方系统不需要做的事 - 不需要 `GET /api/quotes/{id}` 轮询 - 不需要自己做 kg/cm 到 lb/in 的换算 --- ## 6. 单位说明 接口支持: - 重量:`kg` / `lb` - 尺寸:`cm` / `in` 例如: ```json "weight": { "value": 500, "unit": "kg" }, "dimensions": { "length": 120, "width": 100, "height": 150, "unit": "cm" } ``` 服务端会自动换算为 MotherShip 需要的英制单位。 --- ## 7. Apifox 测试方法 ### 环境变量 | 变量 | 值 | |------|-----| | `baseUrl` |https://if.dev.51track.vip | | `token` | `demo-host-token` | | `customerId` | `CUST_001` | ### 第一步 - 方法:`POST` - URL:`{{baseUrl}}/api/host/quote/candidates` - Headers: - `Authorization`:`Bearer {{token}}` - `X-Customer-Id`:`{{customerId}}` - `Content-Type`:`application/json` - Body:JSON(可参考 `deploy/host-candidates-req.example.json`) ### 第二步 - 方法:`POST` - URL:`{{baseUrl}}/api/host/quote/submit` - Headers:与第一步相同 - Body:填入第一步返回的 `host_session_id` 和选中的完整候选对象 Apifox 请求超时建议:`420000` ms(420 秒) ### cURL 示例(第一步) ```bash curl -X POST "https://if.dev.51track.vip/api/host/quote/candidates" \ -H "Authorization: Bearer demo-host-token" \ -H "X-Customer-Id: CUST_001" \ -H "Content-Type: application/json" \ --data-binary @deploy/host-candidates-req.example.json ``` --- ## 8. 常见错误 | code | HTTP | 说明 | 处理方式 | |------|------|------|----------| | `UNAUTHORIZED` | 401 | 缺少或无效 Token | 检查 `Authorization`、`X-Customer-Id` | | `VALIDATION_FAILED` | 400 | 参数不合法 | 检查 JSON、字段名、单位值 | | `SESSION_EXPIRED` | 404 | 会话过期 | 重新调第一步 | | `ADDRESS_SESSION_MISMATCH` | 400 | 第二步候选与第一步不一致 | 使用第一步返回的完整候选对象 | | `ADDRESS_NOT_SELECTABLE` | 400 | 选了不可用候选 | 改用 `selectable: true` 的候选 | | `QUOTE_TIMEOUT` | 504 | 服务端等待超时 | 稍后重试 | | `RATE_LIMITED` | 429 | 请求过于频繁 | 降低调用频率 | --- ## 9. 联系与支持 - Token 失效或需单独开通生产 Token,请联系我方运维 - `X-Customer-Id` 当前固定为 `CUST_001`,须与发放的 Token 一致