🟦《京东开放平台JOS接入全指南:注册、AppKey、OAuth2.0授权与沙箱调试(2026最新)》(附Python源码)
京东开放平台(JOS = JD Open Service)是京东POP商家/ISV对接商品、订单、物流、售后、仓储的官方渠道。爬虫不可取,JOS免费额度内零成本,下面按实际接入顺序给你完整说明 + Python签名/授权/沙箱调用代码。
一、接入四步曲(先记这个)
① 注册京东商家/开发者 → 创建JOS应用(自用/ISV) https://jos.jd.com → 获取 AppKey + AppSecret(别名AppPassword) ② 申请接口权限(商品/订单/物流…) ③ OAuth2.0 授权(店铺卖家登录→code→access_token/refresh_token) *只有订单/私有店铺数据需要 token,商品公开查询可不传* ④ 沙箱调通签名 → 切生产网关正式同步
二、创建应用 & 获取 AppKey / AppSecret
- 登录 京东开放平台→ 控制台 → 创建应用
- 应用类型:
- 商家自用应用(推荐,绑定自己店铺)→ 免ISV入驻
- ISV应用 → 需软服中心入驻、商家授权托管
- 应用审核通过后,详情页复制:
App Key(也叫app_key/client_id)App Secret(也叫app_password/client_secret)- 接口管理 → 申请权限:勾选所需(如
jingdong.pop.order.search、jingdong.ware.sku.read、jingdong.etms.trace.get等)
⚠️ 京东JOS签名用360buy/jd参数名规范,详见下文。
三、JOS 签名算法(必须掌握)
京东JOS使用 MD5签名,规则:
- 收集所有业务参数 + 公共参数(
app_key,method,timestamp,v,sign_method,若有access_token也参入) - 剔除:
sign、file字段、值为空(""/None) - 按参数名 ASCII 升序排序
- 拼接:
key1+value1+key2+value2+...(无=无&) - 首尾拼 AppSecret:
AppSecret + 拼接串 + AppSecret - MD5 → 大写
待签名 = APP_SECRET + app_keyxxxformatjsonmethodjd.xxxv2.0timestampxxx + APP_SECRET sign = MD5(待签名).upper()
📌 京东参数名多为jd.xxxmethod值如jingdong.pop.order.search,timestamp是秒级(10位),不同于TOP的毫秒!
四、OAuth2.0 卖家授权获取 AccessToken
订单/私有店铺接口需卖家账号授权:
① 引导卖家访问:
https://auth.jd.com/oauth2/toLogin.action
?response_type=code
&client_id=YOUR_APP_KEY
&redirect_uri=URLENCODE(你在应用配置的回调地址)
&state=erp_jd
② 回调 → redirect_uri?code=xxx&state=erp_jd
③ 换 token:
POST https://auth.jd.com/oauth2/accessToken
grant_type=authorization_code
client_id=APP_KEY
client_secret=APP_SECRET
code=xxx
redirect_uri=同上
→ {access_token, refresh_token, expires_in, uid, user_nick}五、Python完整封装(签名 + OAuth + 沙箱/生产调用)
# jd_jos_client.py
"""
京东开放平台(JOS) API Client — 2026版
网关:
生产: https://api.jd.com/routerjson
沙箱: https://api.sandbox.jd.com/routerjson (部分接口支持)
签名: MD5(AppSecret + KV_sorted + AppSecret) → upper
timestamp: 秒级(10位)
"""
import hashlib
import time
import requests
import urllib.parse
from typing import Dict, Optional
from datetime import datetime, timedelta
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
class JdJosClient:
PROD = "https://api.jd.com/routerjson"
SANDBOX = "https://api.sandbox.jd.com/routerjson"
def __init__(self, app_key: str, app_secret: str, sandbox: bool = False):
self.ak = app_key
self.as_ = app_secret
self.gw = self.SANDBOX if sandbox else self.PROD
# ─────────────── 签名 ───────────────
def _sign(self, params: Dict) -> str:
filt = sorted((k, v) for k, v in params.items()
if v is not None and str(v).strip() != '' and k != 'sign')
qs = ''.join(f"{k}{v}" for k, v in filt)
raw = f"{self.as_}{qs}{self.as_}"
return hashlib.md5(raw.encode('utf-8')).hexdigest().upper()
# ─────────────── 通用调用 ───────────────
def call(self, method: str, biz: Dict, access_token: str = None) -> Dict:
"""
method: 如 'jingdong.pop.order.search'
biz: 业务参数字典(会变成 JSON 串放 '360buy_param_json')
"""
api_params = {
"app_key": self.ak,
"method": method,
"timestamp": str(int(time.time())), # ← 秒级!
"format": "json",
"v": "2.0",
"sign_method": "md5",
"360buy_param_json": json_dumps(biz)
}
if access_token:
api_params["access_token"] = access_token
api_params["sign"] = self._sign(api_params)
# JOS推荐 POST x-www-form-urlencoded
r = requests.post(self.gw, data=api_params, timeout=15)
r.raise_for_status()
d = r.json()
# JOS返回结构: {method_response: {error_response / xxx_response}}
resp_key = method.replace(".", "_") + "_response"
if resp_key not in d:
# 有些接口直接用 method名作为key
for k in d:
if k.endswith("_response"):
resp_key = k
break
data = d.get(resp_key, d)
if "error_response" in str(data):
# 精细提取
err = d.get(resp_key, {}).get("error_response") if resp_key in d else d.get("error_response")
if err:
raise Exception(f"JOS Err[{err.get('code')}]: {err.get('zh_desc') or err.get('en_desc')} "
f"sub:{err.get('sub_code')}")
raise Exception(f"JOS 未知错误: {d}")
return data
# ─────────────── 示例:商品详情(公开可不传token)────────
def get_sku_detail(self, sku_id: str) -> Dict:
return self.call(
"jingdong.ware.sku.read.findSkuById",
{"skuId": sku_id, "fields": "skuId,wareId,title,price,jdPrice,stockNum"}
).get("skuReadResult", {}).get("skuEntity", {})
# ─────────────── 示例:订单列表(需token)────────
def list_orders(self, access_token: str,
start_modified: str, end_modified: str,
page=1, page_size=50) -> Dict:
return self.call(
"jingdong.pop.order.search",
{
"start_modified": start_modified,
"end_modified": end_modified,
"order_state": "WAIT_SELLER_STOCK_OUT", # 已付款待发货示例
"page": page,
"page_size": min(page_size, 100)
},
access_token=access_token
).get("popOrderSearch", {}).get("orderSearch", {})
# ─────────────── 物流轨迹(需token)────────
def get_trace(self, access_token: str, waybill_code: str,
customer_code: str = None) -> Dict:
biz = {"waybillCode": waybill_code}
if customer_code:
biz["customerCode"] = customer_code
return self.call("jingdong.etms.trace.get", biz, access_token
).get("etmsTraceGetResponse", {}).get("traceApiResult", {})
# ── OAuth 换 token 辅助 ──
def jd_oauth_exchange(app_key, app_secret, code, redirect_uri):
r = requests.post("https://auth.jd.com/oauth2/accessToken", data={
"grant_type": "authorization_code",
"client_id": app_key,
"client_secret": app_secret,
"code": code,
"redirect_uri": redirect_uri,
"state": ""
}, timeout=15)
r.raise_for_status()
return r.json() # access_token / refresh_token / expires_in / uid
def json_dumps(d: Dict) -> str:
import json
return json.dumps(d, ensure_ascii=False, separators=(',', ':'))
# 封装好API供应商demo url=https://console.open.onebound.cn/console/?i=Lex
# =========================================================
# 使用示例
# =========================================================
if __name__ == "__main__":
client = JdJosClient(
app_key="YOUR_JD_APP_KEY",
app_secret="YOUR_JD_APP_SECRET",
sandbox=True # 生产切 False
)
try:
# ① 公开商品(沙箱返回mock,生产需真实skuId)
sku = client.get_sku_detail("100012345678")
print("✅ SKU查询签名通!title=", sku.get("title"))
# ② 订单(需卖家AccessToken,沙箱多返回空)
# from datetime import datetime,timedelta
# now = datetime.now()
# start = (now - timedelta(minutes=30)).strftime("%Y-%m-%d %H:%M:%S")
# end = now.strftime("%Y-%m-%d %H:%M:%S")
# orders = client.list_orders("SELLER_ACCESS_TOKEN", start, end)
# print("订单数:", orders.get("orderTotal"))
except Exception as e:
print("❌", e)六、沙箱 vs 生产注意点
项目 | 沙箱 | 生产 |
|---|---|---|
网关 | api.sandbox.jd.com/routerjson | api.jd.com/routerjson |
数据 | Mock(商品ID原样回显/空订单) | 真实店铺数据 |
订单接口 | 返回空列表正常 | 需卖家AccessToken + 申请权限 |
AppKey | 同一应用Key | 同一应用Key |
淘宝客类 | 不适用 | 京东无淘宝客API |
✅ 沙箱能调通签名+JSON结构 = 生产一定通,返回空不报错属预期。
七、高频避坑
坑 | 现象 | 解决 |
|---|---|---|
timestamp 毫秒 | Invalid Timestamp | JOS用秒级 int(time.time()),不是毫秒! |
360buy_param_json格式错 | Missing Required Parameter | 值是紧凑JSON字符串(无空格),中文不URL编码但 ensure_ascii=False |
订单403 | 个人应用/未申请 | 创建商家自用应用 + 申请 jingdong.pop.order.search+ 卖家OAuth |
session传买家token | 空/403 | 须是店铺卖家授权换的 access_token |
sign mismatch | 参入 sign / 空值参入 | 严格过滤空值+剔除sign再排序 |
沙箱空订单慌 | — | 正常,切生产验证 |
八、面试/方案一句话
京东JOS接入 = 创建商家自用应用拿AppKey/Secret → MD5签名(参按ASCII升序拼AppSecret+KV+AppSecret,秒级timestamp) →360buy_param_json放业务JSON → 订单类接口需卖家OAuth AccessToken;沙箱验签通后切生产网关取真实数据,基础接口有免费额度。
需要我补 京东订单增量同步APScheduler(断点续跑+Token刷新) 或 京东商品全量SKU翻页同步完整字段解析 吗?