API调用

API 调用说明

Postman 快速调试: 下载 Collection 后导入 Postman,设置 base_url、user_name 和 api_key 变量,即可提交任务并轮询结果。

下载 Postman Collection

1. 身份验证

A. 直接调用 (YazRoute)

在请求头中包含您的用户名和 API Key:

  • X-User-Name: 您的 YazRoute 账户用户名
  • X-Api-Key: 您的 API Key (格式: yrk_xxxxx...)

如何获取 API Key:使用支持 API 的套餐登录 YazRoute,进入「账户中心」生成。Key 仅显示一次,请妥善保存。

B. 通过 RapidAPI 调用

如果您通过 RapidAPI 平台调用接口,认证由平台自动处理:

  • 无需提供 X-User-Name 和 X-Api-Key 请求头。
  • RapidAPI 会通过代理自动包含必要的凭据。

新求解任务提交限制:按账户套餐区分,限制同一用户每分钟提交的新求解任务总数;结果轮询不计入该限制。系统繁忙时可能返回 429 service_busy;节点数、车辆数、求解时长、月调用额度依套餐不同,详见 套餐页

通过 YazRoute 直连 API(非 RapidAPI)提交且成功进入队列的请求,会自动写入账户中心的历史规划记录,便于后续查看、下载和复盘。

2. 数据字段说明

请求参数说明:

  • name (string, 可选): 请求名称,用于标识本次请求
  • objective (string, 可选): 优化目标。支持 min_total_distance(最小化总行驶距离)或 min_total_cost(最小化总成本);默认值为 min_total_distance。
  • node_num (number, 必填): 节点数量,必须大于 0
  • node_positions (array, 可选): 节点坐标列表,格式为 [[x1, y1], [x2, y2], ...]。如果提供坐标,将使用坐标计算距离;否则使用距离矩阵
  • vehicle_num (number, 必填): 车辆数量,必须大于 0
  • distance_matrix (array, 必填): 距离矩阵,二维数组,distance_matrix[i][j] 表示从节点 i 到节点 j 的距离
  • start_nodes (array, 必填): 每辆车的起始节点列表,长度为 vehicle_num
  • end_nodes (array, 必填): 每辆车的结束节点列表,长度为 vehicle_num
  • node_demands (array, 必填): 每个节点的需求量列表,长度为 node_num 所有作为 start_nodes / end_nodes 的仓库节点需求量必须为 0。
  • vehicles_capacity (array, 必填): 每辆车的容量列表,长度为 vehicle_num
  • vehicle_fixed_costs (array, 可选): 每辆车的固定启用成本。仅当 objective=min_total_cost 时启用;此时长度必须与 vehicle_num 一致,且所有值必须为非负数。
  • vehicle_unit_distance_costs (array, 可选): 每辆车的单位距离行驶成本。仅当 objective=min_total_cost 时启用;此时长度必须与 vehicle_num 一致,且所有值必须为非负数。
  • node_time_windows (array, 可选): 每个节点的时间窗列表,格式为 [[start1, end1], [start2, end2], ...],单位:分钟,长度为 node_num
  • node_service_time (array, 可选): 每个节点的服务时间列表,单位:分钟,长度为 node_num
  • vehicle_speeds (array, 可选): 每辆车的速度列表,长度为 vehicle_num
  • max_working_hours (number, 可选): 每辆车每天的最大作业时长(小时,正数)。仅在启用时间维度(提供 node_time_windows、node_service_time、vehicle_speeds)时生效;为空或不传表示不限制。
  • base_start_time (array, 可选): 基准开始时间,格式为 [时, 分, 秒],默认为 [9, 0, 0]
  • time_limit_seconds (number, 可选): 可由调用方指定最大求解时间(秒)。若不提供,系统将自动按当前套餐默认的最大求解时间(max_solve_time)执行;若提供值超过套餐上限,将自动按套餐上限执行。
  • late_penalty_per_min (number, 可选): 软时间窗惩罚成本(元/分钟)。若提供且大于 0,则启用软时间窗:车辆允许迟到,每迟到 1 分钟目标函数增加对应惩罚成本。为 null 或不传时退化为硬时间窗(不允许迟到)。仅在同时提供了 node_time_windows、node_service_time 和 vehicle_speeds 时生效。
  • callback_url (string, 可选): Webhook 回调地址(必须以 http:// 或 https:// 开头,长度不超过 2048 字符;不允许指向 localhost、私网或保留 IP 地址)。若提供,服务器在求解完成后会主动向该地址 POST 一次结果,payload 格式与轮询成功响应完全一致。未提供时,请使用轮询接口获取结果。

重点: 你可以自己指定 time_limit_seconds;如果不传,系统会按你当前套餐默认最大求解时间执行。

Webhook 说明: callback_url 为可选参数。提供后,服务器在求解完成时同步发送 HTTP POST 推送(最多阻塞 10 秒),仅尝试一次,不重试。建议你的回调端点在 10 秒内返回 2xx 响应。即使回调超时或失败,求解结果本身不受影响,你仍可通过轮询接口获取结果。注意:回调期间会占用 worker 最多 10 秒,回调端点响应越快越好。

响应字段说明:

  • success (boolean): 请求是否成功
  • request_id (string): 请求唯一标识符
  • error (string|null): 错误信息(失败时存在)
  • code (string, 可选): 机器可读错误码。目前 429 可能返回 rate_limit_exceeded、service_busy 或 monthly_quota_exceeded。
  • retry_after_seconds (number, 可选): 建议等待秒数。当前主要在 429 响应中返回;429 响应也会同步设置 HTTP Retry-After 响应头。

提交接口(POST /api/v1/solve)返回:

  • result.status (string): 固定为 accepted,表示任务已受理
  • result.phase (string): 固定为 queued,表示任务已进入队列等待执行。
  • result.message (string): 轮询提示信息

查询接口(GET /api/v1/solve/result/{request_id})返回:

轮询时需使用同一账户下任一当前仍有效的 API Key;已删除、已吊销、已禁用或已过期的 Key 无法查询结果。

  • result.status (string): processing 表示仍在求解中
  • result.phase (string): 可选阶段信息(如 queued / solving),用于展示更细粒度进度。
  • result.queue_depth (number|null): 当 phase=queued 时返回,表示当前队列中等待执行任务数(不包含正在执行中的任务)。
  • result (object): 求解结果对象(成功时存在)
    • success (boolean): 求解器层级标志,找到可行解时为 true
    • error (string|null): 求解器层级错误信息;成功时为 null
    • total_cost (number): 总成本/总距离
    • routes (object): 车辆路线字典,格式:{"Vehicle 0": [0, 1, 2, 0], "Vehicle 1": [0, 3, 4, 0], ...},其中 key 为车辆标识(如 "Vehicle 0"),value 为节点索引列表
    • distances (array): 每条被使用路线的总行驶距离数组(非字典)。长度与 routes 中有路线的车辆数一致;顺序与 routes 的 key 按车辆 id 升序排列一致(未使用的空车会被跳过,不出现在 routes 中)。例如 routes 含 "Vehicle 0"、"Vehicle 2" 时,distances[0] 对应 Vehicle 0,distances[1] 对应 Vehicle 2
    • loads (object): 每条路线各节点的累计载重字典,格式:{"Vehicle 0": [0, 10, 20, ...], "Vehicle 1": [0, 10, 30, ...], ...}
    • time_windows (object): 每条路线各节点的时间窗字典,格式:{"Vehicle 0": [['arrive1', 'leave1'], ['arrive2', 'leave2'], ...], ...},其中每个子列表包含到达时间和离开时间(格式:HH:MM:SS)
    • time (number): 求解耗时(秒)
    • request_id (string): 求解结果中回传的请求标识符

关于 distances 与 routes:routes 按车辆 id 作为 key(如 "Vehicle 7"),distances 为普通数组;只有实际有路线的车辆会出现在 routes 中。读取 distances[i] 时,请取 routes 按车辆 id 升序后的第 i 个 key 所对应的路线距离,不要默认 distances[i] 一定对应 "Vehicle i"。

说明:任务结果会在有效期(TTL)内保留,便于重复轮询;超过有效期后再次查询会返回 404。

3. 接口调用示例

注意:

1. 如果是通过 RapidAPI 调用的用户,认证由 RapidAPI 处理,无需提供 X-User-Name 和 X-Api-Key 请求头。

2. 提交接口是异步的,POST /api/v1/solve 通常只需短超时即可;如需等待最终结果,请对轮询或 webhook 接收链路预留足够时间。

3. 求解时长可配置: 可通过 time_limit_seconds 指定求解时间;若不提供,则默认使用套餐 max_solve_time。

4. 该接口采用异步任务模式:先调用 POST /api/v1/solve 获取 request_id,再轮询 GET /api/v1/solve/result/{request_id} 获取最终结果。

5. 轮询时可通过 result.phase 区分 queued(排队中)与 solving(计算中);当 queued 时可参考 result.queue_depth。

请求示例

https://yazroute.com/api/v1/solve POST
提交计算接口:POST https://yazroute.com/api/v1/solve
结果查询接口:GET https://yazroute.com/api/v1/solve/result/{request_id}
// 接口调用示例(API v1):
// - 先在「账户中心」生成 API Key(仅显示一次)
// - 请求头需携带用户名与 API Key

const userName = 'your_username';
const apiKey = 'your_api_key';

const payload = {
  name: 'input_data_sample',
  objective: 'min_total_cost',
  node_num: 6,
  node_positions: [],
  vehicle_num: 2,
  vehicle_fixed_costs: [100, 120],
  vehicle_unit_distance_costs: [1.2, 1.5],
  distance_matrix: [
    [0, 98, 60, 50, 61, 39],
    [94, 0, 67, 60, 36, 91],
    [11, 51, 0, 37, 62, 30],
    [19, 44, 91, 0, 55, 51],
    [27, 69, 75, 41, 0, 36],
    [80, 44, 62, 12, 73, 0]
  ],
  start_nodes: [0, 0],
  end_nodes: [0, 0],
  node_demands: [0, 10, 10, 10, 10, 10],
  vehicles_capacity: [100, 100],
  node_time_windows: [[0, 500], [0, 500], [0, 500], [0, 500], [0, 500], [0, 500]],
  node_service_time: [0, 2, 3, 4, 5, 6],
  vehicle_speeds: [30, 30],
  max_working_hours: 8,
  base_start_time: [9, 0, 0],
  // time_limit_seconds 可省略;省略时按当前套餐默认 max_solve_time 执行
  time_limit_seconds: 120,
  // late_penalty_per_min 可选(元/分钟);>0 时启用软时间窗,允许迟到并计入惩罚成本;省略或 null 时为硬时间窗
  // late_penalty_per_min: 10,
  // callback_url 可选;提供后服务器求解完成时会同步 POST 一次结果到该地址(最多阻塞 10 秒,不重试)
  // callback_url: 'https://your-server.com/webhook/vrp-result'
};

const response = await fetch('https://yazroute.com/api/v1/solve', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-User-Name': userName,
    'X-Api-Key': apiKey
  },
  body: JSON.stringify(payload)
});

// 解析提交响应(异步任务)
const submitData = await response.json();
if (!response.ok || !submitData.success) {
  console.error('提交失败:', submitData.error || submitData.detail);
  return;
}

const requestId = submitData.request_id;
let finalData = null;
const maxPollMs = 15 * 60 * 1000; // 最多轮询 15 分钟
const pollStart = Date.now();
const backoffMs = [2000, 3000, 5000]; // 推荐轮询退避:2s -> 3s -> 5s
let pollAttempt = 0;

// 轮询最终结果
while (true) {
  if (Date.now() - pollStart > maxPollMs) {
    console.error('轮询超时,请稍后重试。request_id:', requestId);
    break;
  }
  const pollResp = await fetch(`https://yazroute.com/api/v1/solve/result/${requestId}`, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json',
      'X-User-Name': userName,
      'X-Api-Key': apiKey
    }
  });
  const pollData = await pollResp.json();
  if (!pollResp.ok) {
    console.error('轮询失败:', pollData.error || pollData.detail);
    break;
  }
  if (pollData.result && pollData.result.status === 'processing') {
    const phase = pollData.result.phase || 'processing';
    if (phase === 'queued') {
      console.log('排队中... queue_depth=', pollData.result.queue_depth);
    } else if (phase === 'solving') {
      console.log('计算中...');
    } else {
      console.log('处理中... phase=', phase);
    }
    const waitMs = backoffMs[Math.min(pollAttempt, backoffMs.length - 1)];
    pollAttempt += 1;
    await new Promise(resolve => setTimeout(resolve, waitMs));
    continue;
  }
  finalData = pollData;
  break;
}

if (finalData && finalData.success) {
  const result = finalData.result;
  console.log('总成本:', result.total_cost);
  console.log('求解时间:', result.time, '秒');
  console.log('路线数量:', Object.keys(result.routes).length);
  console.log('路线详情:', result.routes);
  for (const [vehicle, path] of Object.entries(result.routes)) {
    console.log(`${vehicle}:`, path);
  }
  console.log('距离列表:', result.distances);
  console.log('载重字典:', result.loads);
  console.log('时间窗:', result.time_windows);
} else {
  console.error('请求失败:', finalData?.error);
}

4. 响应数据示例

成功响应

200 OK
// 提交成功响应(POST /api/v1/solve,HTTP 200):
{
  "success": true,
  "result": {
    "status": "accepted",
    "phase": "queued",
    "message": "Task accepted. Poll /api/v1/solve/result/{request_id} for completion."
  },
  "error": null,
  "request_id": "api_153ca66c55de4162"
}

// 轮询中响应(GET /api/v1/solve/result/{request_id},HTTP 200):
{
  "success": true,
  "result": {
    "status": "processing",
    "phase": "queued",
    "queue_depth": 3
  },
  "error": null,
  "request_id": "api_153ca66c55de4162"
}

// 轮询成功响应(GET /api/v1/solve/result/{request_id},HTTP 200):
{
  "success": true,
  "result": {
    "success": true,
    "error": null,
    "total_cost": 273,
    "routes": {
      "Vehicle 0": [1, 3, 0, 2],
      "Vehicle 1": [1, 4, 5, 2]
    },
    "distances": [139, 134],
    "loads": {
      "Vehicle 0": [0, 10, 20, 30, 40],
      "Vehicle 1": [0, 10, 20, 30, 40]
    },
    "time_windows": {
      "Vehicle 0": [
        ["09:00:00", "09:02:00"],
        ["11:02:00", "11:06:00"],
        ["11:44:00", "11:45:00"],
        ["13:45:00", "13:48:00"]
      ],
      "Vehicle 1": [
        ["09:00:00", "09:02:00"],
        ["10:14:00", "10:19:00"],
        ["11:31:00", "11:37:00"],
        ["13:41:00", "13:44:00"]
      ]
    },
    "time": 3.04,
    "request_id": "api_153ca66c55de4162"
  },
  "error": null,
  "request_id": "api_153ca66c55de4162"
}

错误响应

401/400/422/403/404/429

API 返回的错误信息均为英文,下方示例为实际返回格式。

// Authentication failed (401) - missing headers:
{
  "success": false,
  "error": "Missing X-User-Name or X-Api-Key header"
}

// Authentication failed (401) - invalid key:
{
  "success": false,
  "error": "Invalid username or API Key (or expired/revoked)"
}

// Authentication failed (401) - invalid RapidAPI proxy secret:
{
  "success": false,
  "error": "Invalid RapidAPI proxy secret"
}

// Exceeds plan limit (400):
{
  "success": false,
  "error": "Node count (20) exceeds plan limit (15). Please upgrade your plan"
}

// Validation failed (422):
{
  "success": false,
  "error": "Validation failed",
  "detail": [
    {
      "loc": ["body", "time_limit_seconds"],
      "msg": "time_limit_seconds must be a positive integer",
      "type": "value_error"
    }
  ]
}

// Validation failed (422) - depot demand must be 0:
{
  "success": false,
  "error": "Validation failed",
  "detail": [
    {
      "loc": ["body"],
      "msg": "Depot/start/end node 1 demand must be 0 (got 10)",
      "type": "value_error"
    }
  ]
}

// Forbidden (403) - plan does not support API access:
{
  "success": false,
  "error": "Your current plan does not support API access. Please upgrade your plan"
}

// Forbidden (403) - polling with a different account (request_id ownership mismatch):
{
  "success": false,
  "error": "No permission to access this request result"
}

// Too many requests (429) - rate limit:
{
  "success": false,
  "error": "Too many requests. Please try again later",
  "code": "rate_limit_exceeded",
  "retry_after_seconds": 60
}

// Too many requests (429) - service busy:
{
  "success": false,
  "error": "Service is busy. Current queue depth is 3. Please try again later",
  "code": "service_busy",
  "retry_after_seconds": 10
}

// Too many requests (429) - monthly quota:
{
  "success": false,
  "error": "Monthly API quota exceeded (500 requests). Please try again next month or upgrade your plan",
  "code": "monthly_quota_exceeded"
}

// Solve failed (HTTP 200, success: false):
{
  "success": false,
  "result": null,
  "error": "No feasible solution found. Please review your constraints",
  "request_id": "api_153ca66c55de4162"
}

// Result polling failed (404) - request id not found or expired:
{
  "success": false,
  "error": "Request id not found or expired"
}