XZPhotos 开放数据接口民航注册号资料 · 机场与拍摄地点 · 供合作方程序化查询

开放数据接口

XZPhotos 是航空摄影爱好者社区。我们把社区多年整理的民航注册号资料、机场 / 拍摄地点数据和摄影师拍摄的航空器图片以 HTTP 接口形式向合作方开放,返回 JSON,按次计费,接入需事先授权。

25,000+注册号资料(机型、航司、序列号、座位布局、交付与运营状态)
10,000+机场 / 拍摄地点(IATA、ICAO、坐标、时区、所在城市)
HMAC-SHA256请求签名 + 时间戳 + 一次性随机数,防重放
四档套餐免费版每日 800 次;付费 0.05 / 0.08 / 0.10 元每次,只对成功返回扣费
基础地址 https://api.xzphotos.cn/api/v1。所有接口均为 GET,响应 application/json; charset=utf-8,一律不缓存。

快速开始

  1. 向我们申请接入,获得 SecretId 与 SecretKey(密钥仅展示一次,请妥善保存)。
  2. 每次请求携带四个请求头:X-SECRET-ID、X-TIMESTAMP(Unix 秒)、X-NONCE(随机串)、X-SIGNATURE(按下文算法计算)。
  3. 调用接口。响应头 X-Request-Cost 为本次扣费(分),X-Account-Balance 为扣费后余额(分)。

性能建议:复用连接

接口不可缓存,每次调用都要经 CDN 边缘回到源站。实测同一条 HTTP/2 连接上连续调用,大陆约 60 毫秒一次;每次新建 HTTPS 连接则要 150 到 280 毫秒,港澳与海外更明显。请让 HTTP 客户端保持长连接,不要每次调用都新建连接:

  • Python:用 requests.Session() 或 httpx.Client(http2=True) 复用同一个实例发请求。
  • Node.js:用 http2.connect() 保持会话,或给 fetch / axios 配置 keepAlive: true 的 Agent。
  • Java / Go / PHP:默认的连接池即可,避免每次调用都创建新的客户端对象。
  • curl 调试时把多次请求放在同一条命令里即可复用连接。

签名中的时间戳与 nonce 每次请求都必须重新生成,与连接是否复用无关。

身份签名

签名对象是查询参数加上 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&timestamp=%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=上海&timestamp=…

接口目录

注册号资料

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/aircraft/{registration}/photos

按注册号返回本站已通过审核的航空器图片,如 /aircraft/B-2001/photos。图片为带 XZPhotos 水印的展示图和缩略图,不提供原图。一次调用返回一页(最多 20 张),计一次;该注册号没有图片返回 404,不计费。付费各档可用,免费版套餐不含此接口;个人、教育和非营利用途可申请免费使用,见这里。

参数说明
page / limit分页,limit 默认 10、最大 20
sortlatest(默认,最新上传在前)或 popular(浏览最多在前)
{
  "success": true,
  "data": {
    "registration": "B-2001",
    "items": [{
      "id": 400001, "title": "中国南方航空 B-2001 降落广州白云",
      "photographer": { "name": "spotter01", "profile_url": "https://xzphotos.cn/gallery/user-profile.php?id=10001" },
      "airline": "中国南方航空", "aircraft": "Boeing 787-9",
      "location": { "name": "广州白云国际机场", "iata": "CAN", "icao": "ZGGG" },
      "taken_at": "2026-08-30", "uploaded_at": "2026-08-31 20:15:02",
      "width": 5000, "height": 3333, "views": 1520, "likes": 36, "is_featured": false,
      "image_url": "https://images.xzphotos.net/uploads/watermarked/…jpg",
      "thumbnail_url": "https://images.xzphotos.net/uploads/thumbnails/…jpg",
      "page_url": "https://xzphotos.cn/gallery/image.php?id=400001"
    }],
    "pagination": { "total": 57, "page": 1, "limit": 10, "pages": 6 },
    "attribution": "图片版权归摄影师所有。展示时请在图片旁注明摄影师和「XZPhotos」,并链接到 page_url;不得去除或遮挡水印,不得二次分发。"
  }
}
使用要求:图片版权归摄影师所有。展示时请在图片旁注明摄影师名称和「XZPhotos」,并链接到 page_url;不得去除、裁掉或遮挡水印;不得把图片另存后二次分发、转售,或用于训练 AI 模型。width / height 为原图像素尺寸,可用来计算宽高比。

机场 / 拍摄地点

GET/airports/{code}

3 位 IATA 或 4 位 ICAO,大小写不敏感,如 /airports/PEK、/airports/ZBAA。返回中英文名、城市、地区、国家、坐标、时区、类型、站内图片数。

GET/airports
参数说明
qIATA / ICAO 精确,或中英文名 / 城市包含匹配;至少 2 个字符
country国家精确匹配
typeinternational / domestic / regional / military / private / other
page / limit分页,limit 最大 100

账户

GET/account免费

当前密钥的余额、生效单价、今日用量、到期时间。

GET/account/ledger免费

最近流水(limit 默认 50,最多 200):充值、扣费、退款、调整。

图片接口(合作方专用)

航空器精选图片、机场场景图片、餐食图片等接口按合作协议单独开通,路径与字段以接入时提供的说明为准;未开通的密钥调用返回 403。按注册号取带水印的图片,请用上面的 航空器图片 接口。

套餐与计费

按需查询、灵活付费。所有套餐均需审核开通,升级同样经审核。按「次」计费:一次注册号查询计一次;查无此机号(404)与非 2xx 响应不收费,但计入免费版的每日次数。

套餐单价返回内容限额
免费版0注册号、机型(型号 / 系列 / 制造商)、航空公司每日 800 次;仅限单个注册号查询;实行至 2026-12-31,后续待定
0.05 元档0.05 元 / 次上一档 + 序列号、国家、发动机、交付日期、运营状态、特殊涂装默认频率限制
0.08 元档0.08 元 / 次上一档 + 舱位数据(头等 / 公务 / 超经 / 经济 / 总座位数)默认频率限制
0.10 元档0.10 元 / 次上一档 + 舱位图(带水印,返回 24 小时有效的图片链接,过期后重新查询即可)默认频率限制
付费套餐的注册号检索(GET /aircraft?q=)、机场查询和航空器图片(GET /aircraft/{registration}/photos,一页计一次)按套餐同一单价计费;免费版只能查询单个注册号。未指定套餐的既有合作方按原约定计费、返回全部字段。

个人、教育与非营利用途免费

现阶段,XZPhotos 面向个人、教育和非营利用途免费开放航空器图片接口(GET /aircraft/{registration}/photos)。申请接入时请在用途说明中写明项目性质,审核通过后免费开通。使用时仍须遵守图片使用要求:注明摄影师和「XZPhotos」并链接到图片页,不得去除水印,不得二次分发。

用于商业产品、商业推广或有广告收入的项目,按上方套餐计费。本政策可能根据使用情况调整,调整前会提前通知已接入的合作方。

  • 预付费:由我们为您的账户充值,余额以「分」计。每次调用前先按单价预扣,余额不足返回 402,不执行查询。
  • 只对成功扣费:响应非 2xx(包括查无数据的 404)时预扣金额原路退回,流水中可见 refund 记录。
  • 默认频率限制:每分钟 120 次、每小时 1,200 次、每日 12,000 次;超出返回 429,不计费。免费版另有每日 800 次上限。
  • IP 白名单:授权时可绑定出口 IP;非白名单来源返回 403。出口 IP 变更请提前告知。
  • 当前套餐、余额与用量可在 合作方控制台 或 GET /api/v1/account 查看。

错误码

HTTP含义是否计费
400参数缺失或不合法(message 说明原因)否
401缺少签名头、签名错误、时间戳超出 5 分钟、nonce 重复、密钥停用或过期否
402余额不足(errors.balance_cents / errors.price_cents)否
403IP 不在白名单,或无权访问该接口否
404查无此注册号 / 机场否(预扣已退回)
429超出频率限制否
500服务端错误,响应含 request_id,反馈时请附上否

所有错误响应结构一致:{"success": false, "message": "…", "timestamp": …},部分错误附 errors 对象。

申请接入

接入采用授权制,不提供自助注册。请直接发邮件给创始人 / CEO提交以下信息,我们审核后为您创建密钥并充值:

  • 单位 / 项目名称与用途说明(申请免费使用图片接口的,请写明项目是否为个人、教育或非营利用途,有无广告、会员等收入)
  • 预计调用量(每日 / 峰值每分钟)
  • 出口 IP 列表(如需绑定白名单)
  • 技术联系人邮箱

已接入的合作方可登录合作方控制台,用 SecretId 与 SecretKey 查看余额、单价、用量、流水与失败调用。

申请邮箱:创始人 / CEO ceo@xzphotos.cn 邮件标题请写「申请接入 XZPhotos 开放数据接口」。