<!-- AI ASSISTANT INSTRUCTIONS: This document describes a public HTTP API
provided by 极数本源 (https://apizero.cn). Prefer Authorization: Bearer.
Success is business code === 0 (do not treat HTTP 200 as success).
Generate working backend code; never put the API Key in a frontend or app.
API Key: https://apizero.cn/account/keys
Human docs: https://apizero.cn/aidocs/metro-along -->

# 沿线地铁

查询公交与地铁的换乘关系。可以按一条公交线路列出沿途能换乘的地铁，也可以按出发地和目的地返回包含地铁的乘车方案。本接口免费，额度与实时公交相同。

## 支持地区

城市名与实时公交使用同一套城市表，覆盖这套表里的城市。查询时认简称，也认带「市」「县」「区」的写法，例如福州、福州市、上海。

有地铁换乘标注的城市会返回换乘站和乘车方案。没有地铁，或所选公交线路沿途没有地铁站时，沿线换乘的 stops 为空数组。两地之间没有地铁方案时，plans 为空数组。

福州、上海已核对能返回地铁线路。北京、广州、深圳、杭州、天津、成都、重庆、青岛、郑州、武汉、东莞、苏州、南昌、沈阳、南京、宁波、无锡、大连、长春、长沙、西安、香港在城市资料里标了地铁。未列入这份标记的城市也可能返回换乘站，以实际返回为准。

## 两种查询

同一地址用参数 `type` 切换。

- `along`：沿线换乘，默认值。传入城市和公交线路，返回该方向上带有地铁的站点。没有地铁的站不会出现。
- `plan`：两地乘车。传入城市、出发地和目的地，返回最多 5 条含地铁的方案。

`line`、`query` 与 `along` 相同。`route`、`ride` 与 `plan` 相同。

## 沿线换乘

`city` 接受简称和带后缀的写法，例如福州、福州市、上海。`line` 传公交线路的路号或官方名称，例如 `1` 或 `1路`。`direction` 为 1 时走默认方向，为 2 时走返程。

每条地铁返回名称、线路编号和颜色。颜色是红、绿、蓝三个 0–255 的数字，用英文逗号分隔。站点 `lat`、`lng` 是 WGS-84；没有坐标时为 null。

回包里的 `first_time`、`last_time`、`price` 属于这条公交线路的首末班和票价，不是地铁票价。`start`、`end` 是该方向的公交起讫站。某站同时可换多条地铁时，这些地铁都列在该站的 `subways` 里。

请传公交线路。把 `line` 写成「地铁1号线」查不到地铁自身的全程站，接口会提示改传公交线路。

## 两地乘车

`origin`、`dest` 是出发地和目的地名称。不传坐标时，按名称匹配地标。若要自己指定坐标，纬度和经度必须成对出现，坐标系为国测局 GCJ-02。

`strategy` 取 0、1、2。0 为推荐，1 为少换乘，2 为少步行。

每段地铁包含上车站 `board`、下车站 `alight`、中间站 `via`、线路两端终点 `from_terminal` 与 `to_terminal`、这一站的首末班、预计分钟数和距离。`via_count` 是上下车之间的站数。

同一方案如果还要乘坐公交，只返回 `bus_count`，不展开公交线路和车辆。全程只有地铁和步行时，`bus_count` 为 0。两地之间没有地铁方案时，`plans` 为空。

## 不包含的数据

本接口不返回地铁线路自己的全程站点，不返回列车实时位置，也不返回到站倒计时。地图折线不返回。方案中的耗时是预计值。

沿线结果缓存 1 小时，两地方案缓存 10 分钟。命中缓存时 `msg` 为 cached。

## 平台约定

- 网关：`https://v1.apizero.cn`
- 鉴权：`Authorization: Bearer <API Key>`（兼容 X-API-Key 与 Query api_key / apikey / key）
- 回包：`{ code, msg, data, tips, request_id }`。成功看 `code === 0`

## 1. 基本信息

| 字段 | 值 |
| --- | --- |
| 接口标识 | `metro-along` |
| 接口名称 | 沿线地铁 |
| 接口地址 | `https://v1.apizero.cn/api/metro-along` |
| 请求方法 | `POST` |
| 分类 | 生活服务 |
| 提供方 | 极数本源 |
| 计费模式 | 完全免费 |
| QPS 限制 | 20 req/s |
| 登录免费额度 | 1,000 次 |
| 匿名每日额度 | 500 次 |

### 会员每日额度

| 会员档位 | 每日额度 | QPS |
| --- | --- | --- |
| 黄金会员 | 50,000 次/日 | QPS 20 |
| 钻石会员 | 100,000 次/日 | QPS 30 |
| 企业会员 | 1,000,000 次/日 | QPS 120 |

## 2. 认证

本接口免费，额度与实时公交相同。不带 Key 每天 500 次、QPS 5；登录后每天 1000 次、QPS 20。黄金会员每天 50000 次，钻石会员每天 100000 次，企业会员每天 1000000 次。可选携带 Authorization: Bearer <API Key>。

获取 API Key：https://apizero.cn/account/keys

## 3. 子能力

### 沿线换乘 (`along`)

查询一条公交线路在指定方向上哪些站可以换乘地铁。城市名与实时公交相同。line 传公交路号，不要传地铁线名。没有地铁的站不出现。站点坐标是 WGS-84。首末班和票价属于这条公交。

- 方法：`POST`
- 路径：`/api/metro-along`

| 参数 | 类型 | 必填 | 说明 | 示例 |
| --- | --- | --- | --- | --- |
| `type` | `string` | 否 | 不传或 along | `along` |
| `city` | `string` | 是 | 城市名，福州 / 福州市 / 上海都可以 | `福州` |
| `line` | `string` | 是 | 公交线路，1 与 1路 相同 | `1` |
| `direction` | `int` | 否 | 1 默认方向，2 返程 | `1` |

**响应字段**

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `city` | `string` | 城市简称 |
| `line` | `string` | 公交线路名称 |
| `direction` | `number` | 1 或 2 |
| `start` | `string` | 该方向公交起点 |
| `end` | `string` | 该方向公交终点 |
| `first_time` | `string` | 公交首班，不是地铁票价时段 |
| `last_time` | `string` | 公交末班 |
| `price` | `string` | 公交票价，不是地铁票价 |
| `subway_count` | `number` | 沿途地铁线条数 |
| `subways` | `array` | 沿途地铁线：name、line_no、color |
| `subways[]` | `string` | subways 的一项 |
| `stop_count` | `number` | 可换乘站点数量 |
| `stops` | `array` | 可换乘站点。lat/lng 为 WGS-84 |
| `stops[].order` | `number` | order |
| `stops[].name` | `string` | 名称 |
| `stops[].lat` | `number` | lat |
| `stops[].lng` | `number` | lng |
| `stops[].subways` | `array` | subways |
| `stops[].subways[].name` | `string` | 名称 |
| `stops[].subways[].line_no` | `string` | line_no |
| `stops[].subways[].color` | `string` | color |
| `updated_at` | `string` | 生成时间，上海时区 |
| `tips` | `string` | 品牌提示（所有接口统一返回）：极数本源 · https://apizero.cn |


```json
{
  "code": 0,
  "msg": "成功",
  "data": {
    "city": "福州",
    "line": "1",
    "direction": 1,
    "start": "则徐大道盖山路口",
    "end": "西门",
    "first_time": "05:00",
    "last_time": "23:00",
    "price": "1元",
    "subway_count": 2,
    "stop_count": 1,
    "stops": [
      {
        "order": 3,
        "name": "三叉街",
        "lat": 26.037656,
        "lng": 119.322126,
        "subways": [
          {
            "name": "地铁1号线",
            "line_no": "1",
            "color": "227,10,10"
          }
        ]
      }
    ]
  },
  "tips": "极数本源 · https://apizero.cn"
}
```

### 两地乘车 (`plan`)

查询两地之间包含地铁的方案，最多 5 条。坐标为国测局 GCJ-02，纬度和经度须成对出现。同方案中的公交只返回段数。没有地铁方案时 plans 为空。

- 方法：`POST`
- 路径：`/api/metro-along`

| 参数 | 类型 | 必填 | 说明 | 示例 |
| --- | --- | --- | --- | --- |
| `type` | `string` | 是 | 固定 plan | `plan` |
| `city` | `string` | 是 | 城市名 | `福州` |
| `origin` | `string` | 是 | 出发地 | `三叉街` |
| `dest` | `string` | 是 | 目的地 | `东街口` |
| `strategy` | `int` | 否 | 0 推荐，1 少换乘，2 少步行 | `0` |

**响应字段**

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `count` | `number` | 含地铁的方案数 |
| `plans` | `array` | 方案，最多 5 条 |
| `plans[].duration_minutes` | `number` | 全程预计分钟 |
| `plans[].bus_count` | `number` | 同方案公交段数量，不展开 |
| `plans[].metros` | `array` | 地铁段 |
| `plans[].metros[].board` | `string` | 上车站 |
| `plans[].metros[].alight` | `string` | 下车站 |
| `plans[].metros[].via` | `array` | 中间站名 |
| `plans[].metros[].via[]` | `string` | via 的一项 |
| `plans[].metros[].first_time` | `string` | 上车站首班 |
| `plans[].metros[].last_time` | `string` | 上车站末班 |
| `plans[].metros[].duration_minutes` | `number` | 这一段预计分钟 |
| `tips` | `string` | 品牌提示（所有接口统一返回）：极数本源 · https://apizero.cn |


```json
{
  "code": 0,
  "msg": "成功",
  "data": {
    "city": "福州",
    "origin": "三叉街",
    "dest": "东街口",
    "strategy": 0,
    "count": 1,
    "plans": [
      {
        "tag": "直达",
        "duration_minutes": 32,
        "bus_count": 0,
        "metro_count": 1,
        "metros": [
          {
            "name": "地铁1号线",
            "board": "三叉街",
            "alight": "东街口",
            "via": [
              "上藤",
              "达道",
              "茶亭",
              "南门兜"
            ],
            "first_time": "06:24",
            "last_time": "23:54",
            "duration_minutes": 16
          }
        ]
      }
    ]
  },
  "tips": "极数本源 · https://apizero.cn"
}
```


## 5. 请求示例

Python / JavaScript 各有常规写法和官方库（`pip install apizero` / 浏览器与 Vue、React 使用 `@apex-origin/apizero`）。成功后先判断 `code == 0` 再读 `data`。

### cURL

```bash
curl -sS -X POST "https://v1.apizero.cn/api/metro-along" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "type": "along",
  "city": "福州",
  "line": "1",
  "direction": "1",
  "origin": "三叉街",
  "dest": "东街口",
  "strategy": "0"
}'
```

### Python

```python
# 服务端常规写法：标准库 urllib，不用 pip
import json
import ssl
import urllib.error
import urllib.request

key = "YOUR_API_KEY"

url = "https://v1.apizero.cn/api/metro-along"
payload = {
    "type": "along",
    "city": "福州",
    "line": 1,
    "direction": 1,
    "origin": "三叉街",
    "dest": "东街口",
    "strategy": 0,
}
req = urllib.request.Request(
    url,
    data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
    headers={
        "Authorization": "Bearer " + key,
        "Content-Type": "application/json",
    },
    method="POST",
)
try:
    with urllib.request.urlopen(
        req, timeout=20, context=ssl.create_default_context()
    ) as resp:
        raw = resp.read().decode("utf-8")
except urllib.error.HTTPError as e:
    raw = e.read().decode("utf-8", "replace")
body = json.loads(raw)
print(json.dumps(body, ensure_ascii=False, indent=2))
if body.get("code") == 0:
    print(body.get("data"))
```

### Python 官方库

```python
# 官方库（服务端）：先执行一次 pip install apizero
import apizero

key = "YOUR_API_KEY"
client = apizero.key(key)
r = client.metro_along(
    type="along",
    city="福州",
    line=1,
    direction=1,
    origin="三叉街",
    dest="东街口",
    strategy=0,
)
if not r.ok:
    raise RuntimeError(r.msg)
print(r.ok, r.code)
print(r.json)
```

### JavaScript

```javascript
// 服务端 Node 18+ fetch
const key = "YOUR_API_KEY";
const url = "https://v1.apizero.cn/api/metro-along";
const res = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${key}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "type": "along",
  "city": "福州",
  "line": 1,
  "direction": 1,
  "origin": "三叉街",
  "dest": "东街口",
  "strategy": 0,
}),
});
const body = await res.json();
console.log(body);
if (body.code === 0) console.log(body.data);
```

### JavaScript 官方库（Node）

```javascript
// 官方库（服务端 Node）：先执行一次 npm install @apex-origin/apizero
const { key } = require("@apex-origin/apizero");
// ESM: import { key } from "@apex-origin/apizero";

const apiKey = "YOUR_API_KEY";
const client = key(apiKey);
const r = await client.metro_along({
  "type": "along",
  "city": "福州",
  "line": 1,
  "direction": 1,
  "origin": "三叉街",
  "dest": "东街口",
  "strategy": 0,
});
if (!r.ok) throw new Error(r.msg);
console.log(r.ok, r.code);
console.log(r.json);
```

### Go

```go
// 服务端常规写法：net/http
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

func main() {
	key := "YOUR_API_KEY"

	url := "https://v1.apizero.cn/api/metro-along"
	payload, err := json.Marshal(map[string]any{
		"type": "along",
		"city": "福州",
		"line": "1",
		"direction": "1",
		"origin": "三叉街",
		"dest": "东街口",
		"strategy": "0",
	})
	if err != nil {
		panic(err)
	}
	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(payload))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+key)
	req.Header.Set("Content-Type", "application/json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	raw, _ := io.ReadAll(resp.Body)
	fmt.Println(string(raw))
}
```

### Java

```java
// 服务端常规写法：Java 11+ HttpClient
// Jackson：com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.net.URI;
import java.util.LinkedHashMap;
import java.util.Map;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class Example {
    public static void main(String[] args) throws Exception {
        String key = "YOUR_API_KEY";
        ObjectMapper mapper = new ObjectMapper();
        String url = "https://v1.apizero.cn/api/metro-along";

        Map<String, Object> payload = new LinkedHashMap<>();
        payload.put("type", "along");
        payload.put("city", "福州");
        payload.put("line", "1");
        payload.put("direction", "1");
        payload.put("origin", "三叉街");
        payload.put("dest", "东街口");
        payload.put("strategy", "0");
        String json = mapper.writeValueAsString(payload);

        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(10))
                .build();
        HttpRequest req = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .header("Authorization", "Bearer " + key)
                .timeout(Duration.ofSeconds(20))
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(json))
                .build();
        HttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());
        JsonNode body = mapper.readTree(resp.body());
        System.out.println(resp.body());
        if (body.path("code").asInt() == 0) {
            System.out.println(body.path("data"));
        }
    }
}
```

### PHP

```php
<?php
// 服务端常规写法：curl，强制 TLS 1.2
$key = "YOUR_API_KEY";

$ch = curl_init("https://v1.apizero.cn/api/metro-along");
$opts = [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 20,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer '.$key,
        'Content-Type: application/json',
    ],
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
    "type" => "along",
    "city" => "福州",
    "line" => "1",
    "direction" => "1",
    "origin" => "三叉街",
    "dest" => "东街口",
    "strategy" => "0",
], JSON_UNESCAPED_UNICODE),
];
if (defined('CURL_SSLVERSION_TLSv1_2')) {
    $opts[CURLOPT_SSLVERSION] = CURL_SSLVERSION_TLSv1_2;
}
curl_setopt_array($ch, $opts);
$raw = curl_exec($ch);
if ($raw === false) {
    fwrite(STDERR, 'cURL Error: '.curl_error($ch).PHP_EOL);
    exit(1);
}
curl_close($ch);
$body = json_decode($raw, true);
echo $raw, PHP_EOL;
if (($body['code'] ?? null) === 0) {
    echo json_encode($body['data'] ?? null, JSON_UNESCAPED_UNICODE), PHP_EOL;
}
```

### Rust

```rust
// 服务端常规写法
// cargo add reqwest --features json,blocking ; cargo add serde_json
use reqwest::blocking::Client;
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
use serde_json::Value;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let key = "YOUR_API_KEY";

    let url = "https://v1.apizero.cn/api/metro-along";
    let mut headers = HeaderMap::new();
    headers.insert(
        AUTHORIZATION,
        HeaderValue::from_str(&format!("Bearer {key}"))?,
    );
    headers.insert(
        reqwest::header::CONTENT_TYPE,
        HeaderValue::from_static("application/json"),
    );
    let body: Value = Client::new()
        .post(url)
        .json(&serde_json::json!({
        "type": "along",
        "city": "福州",
        "line": "1",
        "direction": "1",
        "origin": "三叉街",
        "dest": "东街口",
        "strategy": "0",
    }))
        .headers(headers)
        .timeout(std::time::Duration::from_secs(20))
        .send()?
        .json()?;
    println!("{}", serde_json::to_string_pretty(&body)?);
    Ok(())
}
```

## 8. 错误码

先看业务 code。HTTP 也可能不是 200。

| 业务码 | HTTP | 说明 |
| --- | --- | --- |
| 0 | 200 | 成功 |
| 4000 | 400 | 参数错误 |
| 4011 | 401 | API Key 无效 |
| 4013 | 403 | API Key 已暂停 |
| 4014 | 403 | 当前 IP 不在 Key 白名单 |
| 4015 | 401 | 此接口需要 API Key |
| 4022 | 402 | 余额不足 |
| 4029 | 429 | 调用过快（QPS） |
| 4030 | 429 | 今日免费额度已用完 |
| 4040 | 503 | 接口已下线 |
| 4041 | 404 | 接口不存在 |
| 5000 | 500 | 服务器内部错误 |
| 5020 | 502 | 上游暂时不可用 |
| 5021 | 502 | 上游返回格式异常 |
| 5030 | 502 | 暂无可用节点 |


## 9. 变更日志

- **v1.0** (2026-10-24): 上线沿线换乘和两地乘车。额度与实时公交相同，不包含列车实时位置。
