结论先拍:京东开放体系里联盟Key(jd.union.open.)和商家JOS Key(jingdong. / jd.order.*)是两套完全独立的账。联盟Key备案后调用本身免费、赚钱靠CPS抽佣5%~20%;商家JOS Key有日免额(企业应用通常30万~100万次/天),超量云内≈¥0.02~0.10/百次、云外×3~×10。 惨案的根因不是"联盟收费",而是把本该用商家Key走免额的商品/订单同步,错配成联盟Key轮询,发现联盟不限频就狂拉,结果——联盟虽然API费¥0,但团队误以为"京东API都免费"顺手把云外商家Key也开着,或者更常见的是:用联盟Key拿不到真实库存,为了补数据又开商家Key云外轮询,两套Key一起爆。我们复盘的那家店实际账单:联盟Key本身¥0,但同进程里商家JOS Key云外跑订单列表翻页 110万次/天 × 30天 × ¥0.15/百次 ≈ ¥4950,加上云内超免额 60万次/天 × ¥0.05/百次 ≈ ¥2700,电子面单按单另算,合计月烧¥8000+。
一、两套Key的边界(2026现行口径)
维度
京东联盟 Key(jd.union.open.*)
京东商家 JOS Key(jingdong. / jd.pop.order.)
授权对象
联盟实名(无店铺也可)
企业认证 + 卖家OAuth token
代表接口
union.open.goods.query / union.open.goods.detail.query / union.open.goods.promotiongoodsinfo.query
jingdong.ware.read.get / jingdong.stock.get / jd.pop.order.search / jingdong.etms.waybill.get
库存字段
只返"有货/无货/预售",无真实stockNum
返真实可售库存、区域仓存、锁定库存
订单权限
无(看不到自己店铺订单)
查自己店铺订单/发货回写
接口费
¥0(备案后),成交后CPS抽佣5%~20%
免额内¥0,超量云内¥0.02~0.10/百次,云外×3~×10
电子面单
无
按单计费(几分/单),取消未揽收不扣
适用场景
选品/CPS导购/比价/跨平台铺货
ERP进销存/防超卖/WMS/店铺一体化
二、惨案还原:110万次/天的错误链路
ERP启动
├─ 商品同步线程:用 union.open.goods.detail.query 拉竞品/供货(✅免费但无真实库存)
│ → 发现库存只有"有货",防超卖没法做
│ → 开发补一道:用【商家Key】jingdong.stock.get 补真实库存
│ └─ 商家Key部署在IDC(云外),QPS调到20,日调 60万次
│
├─ 订单同步线程:本该用商家Key jd.pop.order.search(免额内¥0)
│ → 但配置错配成"联盟Key也能拉订单?试了报错"
│ → 回退用商家Key,但忘了切回云内,继续云外跑
│ → 订单列表翻页 日调 50万次(云外¥0.15/百次)
│
└─ 月账单:
联盟Key调用 3000万次 → API费 ¥0(但CPS成交抽佣另算,不在本账单)
商家Key云外 stock.get 60万×30=1800万次 ×0.15/百 = ¥2700
商家Key云外 pop.order.search 50万×30=1500万次 ×0.15/百 = ¥2250
商家Key云内超免额(免额50万/天,实际110万/天,超60万×30=1800万)×0.05/百 = ¥900
电子面单 3万单×0.03 = ¥900
合计 ≈ ¥6650~8000(视免额档位)
根因三条:
Key类型不隔离——配置中心jd_app_key一个字段,联盟/商家混着填,没有key_type强制校验。
method前缀不守卫——union.open.出现却用商家token、或jingdong.出现却走联盟Key,网关不拦。
云外×10无告警——前篇CloudResidencyGuard只拦敏感禁外,没拦"商家基础接口云外×10烧钱"。
三、Python:JdKeyTypeGuard(联盟/商家Key错配拦截器)
jd_key_type_guard.py
"""
京东联盟Key / 商家JOS Key 错配拦截 + 云外烧钱预警
- method前缀 → 期望key_type 映射
- app_key后缀/配置标记 → 实际key_type
- 错配直接抛异常(CI/运行时)
- 商家Key云外超频估算月费,超阈值告警
"""
from dataclasses import dataclass
from typing import Dict, List
UNION_PREFIXES = ("jd.union.open.", "union.open.")
JOS_PREFIXES = ("jingdong.", "jd.pop.", "jd.order.", "jd.item.", "jd.sku.")
京东2026计费参数(云内/云外,元/百次)
JD_PRICE = {
"base_in": 0.05 / 100,
"base_out": 0.15 / 100,
"value_in": 0.05 / 100,
"free_daily": 500_000, # 企业商家Key典型免额
}
@dataclass
class JdCall:
app_key: str
key_type: str # "union" | "jos"
method: str
in_cloud: bool
calls: int = 1
is_value: bool = False
class JdKeyTypeGuard:
def init(self, alert_monthly_yuan: float = 1000.0):
self.alert = alert_monthly_yuan
@staticmethod
def expected_key_type(method: str) -> str:
if method.startswith(UNION_PREFIXES):
return "union"
if method.startswith(JOS_PREFIXES):
return "jos"
return "unknown"
def validate(self, call: JdCall):
exp = self.expected_key_type(call.method)
# 1. 错配拦截
if exp == "union" and call.key_type != "union":
raise RuntimeError(
f"❌ method={call.method} 属联盟接口,必须用union Key,"
f"但收到key_type={call.key_type}(app_key={call.app_key[:6]}…)")
if exp == "jos" and call.key_type != "jos":
raise RuntimeError(
f"❌ method={call.method} 属商家JOS接口,必须用jos Key+token,"
f"但收到key_type={call.key_type}(联盟Key无店铺权限必失败且烧错账)")
# 2. 商家Key云外烧钱预估
if exp == "jos" and not call.in_cloud:
monthly = call.calls * 30 * JD_PRICE["base_out"]
if monthly > self.alert:
raise RuntimeError(
f"🚨 商家JOS Key云外调用预估月费¥{monthly:.0f} > 告警线¥{self.alert:.0f},"
f"请迁云内或降频(method={call.method})")
return True
def estimate_jos_monthly(self, calls_per_day: int, in_cloud: bool) -> Dict:
over = max(0, calls_per_day - JD_PRICE["free_daily"])
unit = JD_PRICE["base_in"] if in_cloud else JD_PRICE["base_out"]
monthly_over = over * 30 * unit
monthly_total_if_no_free = calls_per_day * 30 * unit
return {
"daily_calls": calls_per_day,
"free_daily": JD_PRICE["free_daily"],
"over_daily": over,
"unit_yuan_per_100": round(unit * 100, 4),
"monthly_over_fee": round(monthly_over, 2),
"monthly_full_fee_if_no_free": round(monthly_total_if_no_free, 2),
"in_cloud": in_cloud,
}
if name == "main":
g = JdKeyTypeGuard(alert_monthly_yuan=1000)
# 场景1:联盟method配商家Key → 应拦
try:
g.validate(JdCall("app_jos_01", "jos", "jd.unium.open.goods.query", True))
except RuntimeError as e:
print("拦截①:", e)
# 场景2:商家订单method配联盟Key → 应拦
try:
g.validate(JdCall("app_uni_02", "union", "jd.pop.order.search", True))
except RuntimeError as e:
print("拦截②:", e)
# 场景3:商家Key云外 50万次/天 → 应拦(月费=50万*30 * 0.15/百=¥2250)
try:
g.validate(JdCall("app_jos_03", "jos", "jd.pop.order.search", False, calls=500_000))
except RuntimeError as e:
print("拦截③:", e)
# 场景4:商家Key云内 50万次/天(未超免额50万)→ 通过
ok = g.validate(JdCall("app_jos_04", "jos", "jingdong.stock.get", True, calls=500_000))
print("通过④ 云内50万/天未超免额:", ok)
# 场景5:商家Key云内 110万次/天 → 超免额60万,月费估算
est = g.estimate_jos_monthly(1_100_000, in_cloud=True)
print("估算⑤ 云内110万/天:", est)
# 云外同量
est_out = g.estimate_jos_monthly(1_100_000, in_cloud=False)
print("估算⑥ 云外110万/天:", est_out)
跑出来关键三行:
拦截①: ❌ method=jd.unium.open.goods.query 属联盟接口,必须用union Key...
拦截②: ❌ method=jd.pop.order.search 属商家JOS接口,必须用jos Key+token...
拦截③: 🚨 商家JOS Key云外调用预估月费¥2250 > 告警线¥1000,请迁云内或降频...
估算⑥ 云外110万/天: {'monthly_over_fee': 2700.0, 'monthly_full_fee_if_no_free': 4950.0}
(云外110万/天按全量算¥4950,按超免额算¥2700,正是惨案账单区间)
四、避免月烧¥8000的四条铁律
配置中心Key必须带key_type字段,不允许只填app_key——JdKeyTypeGuard.validate在ApiGateway.call()入口强校验,CI单测里放一条"联盟Key+jos method"必崩。
联盟Key只许调jd.union.open.*,且禁止用于ERP库存/订单——代码层把union Key绑死到UnionClient,JosClient永远拿不到union Key。
商家Key强制云内:京东虽不像淘宝/抖店明文"禁外",但云外×3~×10是隐式烧钱锁,把in_cloud=False的商家JOS调用在ObservabilityMiddleware里标红,日预估>¥30即告警。
库存别用联盟补:联盟只返有货/无货,防超卖必须jingdong.stock.get(商家Key云内),接受"QPS 2~50"的限制,用推送替轮询,别为补数据开云外狂拉。
五、和前几篇的衔接
要不要我把 jd_key_type_guard.py 和前篇 unified_mock_server.py + api_cost_attributor.py 合成 commerce-mesh/jd/ 子模块,增加"联盟Key Mock返回有货/无货、商家Key Mock返回真实stockNum"的双轨Fixture,直接进你CI?