# 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 <Service Token>
X-Customer-Id: CUST_001
Content-Type: application/json
```

| 字段 | 值 | 说明 |
|------|-----|------|
| `Authorization` | `Bearer <Service Token>` | 我方单独发放的 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 一致
