开放数据接口
XZPhotos 是航空摄影爱好者社区。我们把社区多年整理的民航注册号资料与机场 / 拍摄地点数据以 HTTP 接口形式向合作方开放,返回 JSON,按次计费,接入需事先授权。
25,000+注册号资料(机型、航司、序列号、座位布局、交付与运营状态)
10,000+机场 / 拍摄地点(IATA、ICAO、坐标、时区、所在城市)
HMAC-SHA256请求签名 + 时间戳 + 一次性随机数,防重放
按次计费只对成功返回数据的调用扣费,空结果不收费
基础地址
https://api.xzphotos.cn/api/v1。所有接口均为 GET,响应 application/json; charset=utf-8,一律不缓存。快速开始
- 向我们申请接入,获得
SecretId与SecretKey(密钥仅展示一次,请妥善保存)。 - 每次请求携带四个请求头:
X-SECRET-ID、X-TIMESTAMP(Unix 秒)、X-NONCE(随机串)、X-SIGNATURE(按下文算法计算)。 - 调用接口。响应头
X-Request-Cost为本次扣费(分),X-Account-Balance为扣费后余额(分)。
身份签名
签名对象是查询参数加上 timestamp 与 nonce 两项,按键名升序排列后以 key=value&key=value 拼接,用 SecretKey 做 HMAC-SHA256,十六进制小写。时间戳与服务器相差不得超过 5 分钟;同一 nonce 只能使用一次。
# 以查询 B-2001 为例(无查询参数时只签 timestamp 与 nonce)
SID=你的SecretId; KEY=你的SecretKey
TS=$(date +%s); NONCE=$(openssl rand -hex 8)
SIG=$(printf 'nonce=%s×tamp=%s' "$NONCE" "$TS" | openssl dgst -sha256 -hmac "$KEY" | awk '{print $2}')
curl "https://api.xzphotos.cn/api/v1/aircraft/B-2001" \
-H "X-SECRET-ID: $SID" -H "X-TIMESTAMP: $TS" -H "X-NONCE: $NONCE" -H "X-SIGNATURE: $SIG"import time, hmac, hashlib, secrets, requests
SID, KEY = "你的SecretId", "你的SecretKey"
def call(path, params=None):
params = dict(params or {})
ts, nonce = str(int(time.time())), secrets.token_hex(8)
signed = dict(params, timestamp=ts, nonce=nonce)
s = "&".join(f"{k}={signed[k]}" for k in sorted(signed))
sig = hmac.new(KEY.encode(), s.encode(), hashlib.sha256).hexdigest()
r = requests.get("https://api.xzphotos.cn/api/v1" + path, params=params,
headers={"X-SECRET-ID": SID, "X-TIMESTAMP": ts, "X-NONCE": nonce, "X-SIGNATURE": sig})
print(r.status_code, r.headers.get("X-Request-Cost"), r.headers.get("X-Account-Balance"))
return r.json()
print(call("/aircraft/B-2001"))
print(call("/airports", {"q": "上海", "limit": 5}))const crypto = require("crypto");
const SID = "你的SecretId", KEY = "你的SecretKey";
async function call(path, params = {}) {
const ts = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(8).toString("hex");
const signed = { ...params, timestamp: ts, nonce };
const s = Object.keys(signed).sort().map(k => `${k}=${signed[k]}`).join("&");
const sig = crypto.createHmac("sha256", KEY).update(s).digest("hex");
const url = "https://api.xzphotos.cn/api/v1" + path + (Object.keys(params).length ? "?" + new URLSearchParams(params) : "");
const r = await fetch(url, { headers: { "X-SECRET-ID": SID, "X-TIMESTAMP": ts, "X-NONCE": nonce, "X-SIGNATURE": sig } });
console.log(r.status, r.headers.get("x-request-cost"), r.headers.get("x-account-balance"));
return r.json();
}
call("/aircraft/B-2001").then(console.log);注意:参数值原样参与签名(不做 URL 编码);数组参数以 JSON 字符串参与。签名串示例:
limit=5&nonce=…&q=上海×tamp=…接口目录
注册号资料
GET
/aircraft/{registration}按次计费按注册号精确查询,如 /aircraft/B-2001。返回机型、系列、分类、制造商、发动机、航司(含 IATA/ICAO)、国家、序列号、座位布局、交付日期、运营状态、特殊涂装。查无此机号返回 404,不计费。
{
"success": true,
"data": {
"registration": "B-2001", "serial_number": "1234",
"aircraft": { "model": "Boeing 787-9", "series": "787", "classification": "宽体机", "manufacturer": "Boeing", "engine": "GEnx-1B" },
"airline": { "id": 12, "name": "China Southern", "name_cn": "中国南方航空", "iata": "CZ", "icao": "CSN" },
"country": "中国",
"seats": { "total": 293, "first": 0, "business": 28, "premium_economy": 0, "economy": 265 },
"operation_status": "运营中", "delivery_date": "2018-05-20",
"special_livery": { "is_painted": false, "name": null },
"is_active": true, "updated_at": "2026-08-31 10:12:03"
}
}
GET
/aircraft按次计费| 参数 | 说明 |
|---|---|
q | 关键字:注册号 / 序列号前缀,或机型 / 航司名包含匹配;至少 2 个字符 |
airline_id | 航司 ID |
type | 机型包含匹配,如 A350 |
country / status | 国家 / 运营状态精确匹配 |
page / limit | 分页,limit 最大 100,默认 20 |
以上条件至少提供一个。返回 data.items[] 与 data.pagination。
机场 / 拍摄地点
GET
/airports/{code}按次计费3 位 IATA 或 4 位 ICAO,大小写不敏感,如 /airports/PEK、/airports/ZBAA。返回中英文名、城市、地区、国家、坐标、时区、类型、站内图片数。
GET
/airports按次计费| 参数 | 说明 |
|---|---|
q | IATA / ICAO 精确,或中英文名 / 城市包含匹配;至少 2 个字符 |
country | 国家精确匹配 |
type | international / domestic / regional / military / private / other |
page / limit | 分页,limit 最大 100 |
账户
GET
/account免费当前密钥的余额、生效单价、今日用量、到期时间。
GET
/account/ledger免费最近流水(limit 默认 50,最多 200):充值、扣费、退款、调整。
图片接口(合作方专用)
航空器精选图片、机场场景图片、餐食图片等接口按合作协议单独开通,路径与字段以接入时提供的说明为准。
计费与限额
- 预付费:由我们为您的账户充值,余额以「分」计。每次调用前先按单价预扣,余额不足返回
402,不执行查询。 - 只对成功扣费:响应非
2xx(包括查无数据的404)时预扣金额原路退回,流水中可见refund记录。 - 单价可按接口、按合作方分别约定;
/account与探活接口免费。当前生效单价可通过/account查询。 - 频率限制:默认每分钟 120 次、每小时 1,200 次、每日 12,000 次;超出返回
429,不计费。有更高需求请联系我们单独配置。 - IP 白名单:授权时可绑定出口 IP;非白名单来源返回
403。出口 IP 变更请提前告知。
错误码
| HTTP | 含义 | 是否计费 |
|---|---|---|
| 400 | 参数缺失或不合法(message 说明原因) | 否 |
| 401 | 缺少签名头、签名错误、时间戳超出 5 分钟、nonce 重复、密钥停用或过期 | 否 |
| 402 | 余额不足(errors.balance_cents / errors.price_cents) | 否 |
| 403 | IP 不在白名单,或无权访问该接口 | 否 |
| 404 | 查无此注册号 / 机场 | 否(预扣已退回) |
| 429 | 超出频率限制 | 否 |
| 500 | 服务端错误,响应含 request_id,反馈时请附上 | 否 |
所有错误响应结构一致:{"success": false, "message": "…", "timestamp": …},部分错误附 errors 对象。
申请接入
接入采用授权制,不提供自助注册。请通过站内「联系我们」的邮件联系提交以下信息,我们审核后为您创建密钥并充值:
- 单位 / 项目名称与用途说明
- 预计调用量(每日 / 峰值每分钟)
- 出口 IP 列表(如需绑定白名单)
- 技术联系人邮箱