结论先拍:京东联盟 API(jd.union.open.)= 选品/CPS/比价,调用本身接近免费,赚钱靠成交抽佣*京东商家 JOS(jingdong. / jd.)= 店铺订单/真实库存/发货/电子面单,有日免额,超量按量计费**两套 Key、两套授权、两套配额,绝不能混用。
一、双轨制全景对照
维度 | 京东联盟 API | 京东商家 JOS |
|---|---|---|
前缀 | jd.union.open.* | jingdong.* / jd.pop.order.* / jd.order.* |
目的 | 选品、券价、佣金、CPS 推广 | ERP、订单同步、真实库存、发货回写 |
资质 | 联盟实名(可不绑店铺) | 企业认证 + 卖家 OAuth AccessToken |
库存 | 有货 / 无货 / 预售 | 可售库存、锁定库存、区域仓存 |
价格 | 促销价、券后价、佣金率 | 商家后台基准价、协议价 |
费用 | 调用免费,成交抽佣 5%~20% | 日免额内免费,超量云内 ¥0.01~0.05/百次,云外 ×3~×10 |
电子面单 | 无 | 按单计费(几分/单) |
典型 QPS | 5~30/s(可申) | 个人 1~2/s,企业 5~10/s |
适合 | 导购站、比价、铺货、达人系统 | 商家自建 ERP / WMS / 打单系统 |
二、接口边界(什么时候用哪套)
✅ 用「联盟 API」
商品搜索:
jd.union.open.goods.query商品详情:
jd.union.open.goods.detail.query推广链接:
jd.union.open.goods.promotiongoodsinfo.query券后价 / 佣金率 / 类目 CPS
跨店选品、内容电商、返利机器人
✅ 用「商家 JOS」
商品主数据:
jingdong.ware.read.getSKU:
jingdong.ware.sku.read真实库存:
jingdong.stock.get/ware.stock.get店铺订单:
jingdong.pop.order.search/jd.order.detail.get发货回写 / 物流轨迹:
jingdong.etms.trace.get电子面单:
jingdong.etms.waybill.get
❌ 禁止用法
联盟 Key 查自己店铺订单 → 无权
商家 Key 爬全站竞品 → 超量 + 合规风险
把联盟调用量“无限轮询”思维套到商家 Key → 月烧几千块
三、落地架构(生产可用)
┌──────────────┐ 定时任务/Webhook│ │ ───────────────▶│ API Router │ └──────┬───────┘ │ ┌───────────────┴───────────────┐ ▼ ▼ UnionClient JosMerchantClient (联盟:选品/比价/推广) (商家:订单/库存/发货) │ │ ▼ ▼ Quota: 只记调用量 Quota: 日额度 + 超量预警 Billing: CPS 佣金 Billing: 按量 + 电子面单
Key 不混用:配置层就隔离
商家接口走令牌桶:QPS 保守(企业 8/s,个人 1/s)
订单/库存优先消息推送,别轮询
商家调用打 meter:日调用量、接口级、店铺级
联盟接口可高频,但别用来“补商家数据”
四、业务踩坑清单(很值钱)
时间戳用秒不是毫秒
JOS 要int(time.time()),用毫秒 →Invalid Timestamp签名必须 ASCII 升序 + 空值剔除 + 紧凑 JSON
数组/JSON 字段别用json.dumps(obj)默认空格公开商品接口 ≠ 自己店铺数据
jingdong.ware.read.get不传 token 也能查公开字段
但库存 / 商家编码 / 区域仓 → 必须卖家 AccessToken订单类接口要“企业应用 + 接口权限申请 + token”
个人应用直接 403 / no permission日免额耗尽返回类似
accesscontrol.limited-by-app-access-count
不是代码错,是没钱了电子面单“取号即计费”
取消未揽收通常不扣,但取号动作本身计费沙箱只验签名
沙箱返 mock 商品/空订单是正常的,别调试到怀疑人生云外调用贵到离谱
商家系统尽量放京东云/聚石塔体系内
五、Python 双 Client 源码(可直接用)
# jd_dual_api_client.py
"""
京东双通道 API Client
1. UnionClient -> 京东联盟 API (jd.union.open.*)
2. JosMerchantClient -> 京东商家 JOS (jingdong.* / jd.*)
网关统一: https://api.jd.com/routerjson
"""
import hashlib
import json
import time
import requests
from typing import Any, Dict
class BaseJDClient:
GATEWAY = "https://api.jd.com/routerjson"
def __init__(self, app_key: str, app_secret: str):
self.app_key = app_key
self.app_secret = app_secret
def _build_sign(self, params: Dict[str, Any]) -> str:
"""
MD5 签名:
- 剔除 sign
- 空字符串可保留(京东一般要求非空,但实现上建议剔除 None)
- key 按 ASCII 升序
- secret + kv + secret
"""
filtered = {
k: v for k, v in params.items()
if k != "sign" and v is not None and v != ""
}
raw = self.app_secret
for k in sorted(filtered.keys()):
v = filtered[k]
if isinstance(v, (dict, list)):
v = json.dumps(v, ensure_ascii=False, separators=(",", ":"))
raw += f"{k}{v}"
raw += self.app_secret
return hashlib.md5(raw.encode("utf-8")).hexdigest().upper()
def _request(self, method: str, extra: Dict[str, Any], token: str = None) -> dict:
params = {
"app_key": self.app_key,
"method": method,
"timestamp": str(int(time.time())),
"format": "json",
"v": "1.0",
"sign_method": "md5",
}
if token:
params["access_token"] = token
# 业务参数
for k, v in extra.items():
if isinstance(v, (dict, list)):
v = json.dumps(v, ensure_ascii=False, separators=(",", ":"))
params[k] = v
params["sign"] = self._build_sign(params)
resp = requests.post(self.GATEWAY, data=params, timeout=10)
resp.raise_for_status()
return resp.json()
class UnionClient(BaseJDClient):
"""
京东联盟 API
场景:选品 / 比价 / 券后价 / 佣金 / CPS
"""
def goods_query(self, keyword: str, page_index: int = 1, page_size: int = 20):
biz = {
"goodsReqDTO": {
"keyword": keyword,
"pageIndex": page_index,
"pageSize": page_size,
}
}
return self._request(
method="jd.union.open.goods.query",
extra={"360buy_param_json": biz},
)
def goods_detail(self, sku_ids: list):
biz = {
"skuIds": sku_ids
}
return self._request(
method="jd.union.open.goods.detail.query",
extra={"360buy_param_json": biz},
)
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
class JosMerchantClient(BaseJDClient):
"""
京东商家 JOS
场景:商品主数据 / 真实库存 / 店铺订单 / 发货
"""
def ware_read(self, ware_id: int, access_token: str):
biz = {
"wareId": ware_id
}
return self._request(
method="jingdong.ware.read.get",
extra={"360buy_param_json": biz},
token=access_token,
)
def stock_get(self, sku_id: int, access_token: str):
biz = {
"skuId": sku_id
}
return self._request(
method="jingdong.stock.get",
extra={"360buy_param_json": biz},
token=access_token,
)
def pop_order_search(self, start_date: str, end_date: str, page: int, access_token: str):
biz = {
"orderReq": {
"startDate": start_date,
"endDate": end_date,
"page": page,
"pageSize": 100,
}
}
return self._request(
method="jingdong.pop.order.search",
extra={"360buy_param_json": biz},
token=access_token,
)
if __name__ == "__main__":
union = UnionClient("UNION_APP_KEY", "UNION_APP_SECRET")
print(union.goods_query("机械键盘"))
merchant = JosMerchantClient("JOS_APP_KEY", "JOS_APP_SECRET")
print(merchant.ware_read(1000123, "SELLER_ACCESS_TOKEN"))六、配额护栏(生产必加)
from collections import defaultdict
from datetime import date
daily_counter = defaultdict(int)
def count_and_guard(app_name: str, cost: int, daily_limit: int):
today = str(date.today())
key = f"{app_name}:{today}"
daily_counter[key] += cost
if daily_counter[key] > daily_limit:
raise RuntimeError(f"{app_name} 日调用量即将超免额:{daily_counter[key]}/{daily_limit}")联盟:只统计,不扣费
商家:日调用 > 80% 免额 → 告警
订单列表:能用 Webhook/消息就不翻页
库存:本地缓存 30~60s,别每秒查
七、一句话选型
**“看别人商品”用联盟,“管自己店铺”用 JOS;联盟烧的是佣金,JOS 烧的是调用次数和面单。”**
✅ 联盟推广链生成 + 佣金回执校验
✅ 商家订单增量同步(cursor / 消息 / 防超卖)
✅ 京东 + 淘宝 + 拼多多 三平台 API 统一抽象层