[特殊字符]《京东开放平台JOS接入全指南:注册、AppKey、OAuth2.0授权与沙箱调试(2026最新)》(附Python源码)
🟦《京东开放平台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 + 拼接串 + AppSecretMD5 → 大写
待签名 = 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=(',', ':')) # ========================================================= # 使用示例 # ========================================================= 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 生产注意点
项目 | 沙箱 | 生产 |
|---|---|---|
网关 |
|
|
数据 | Mock(商品ID原样回显/空订单) | 真实店铺数据 |
订单接口 | 返回空列表正常 | 需卖家AccessToken + 申请权限 |
AppKey | 同一应用Key | 同一应用Key |
淘宝客类 | 不适用 | 京东无淘宝客API |
✅沙箱能调通签名+JSON结构 = 生产一定通,返回空不报错属预期。
七、高频避坑
坑 | 现象 | 解决 |
|---|---|---|
timestamp 毫秒 |
| JOS用秒级 |
|
| 值是紧凑JSON字符串(无空格),中文不URL编码但 ensure_ascii=False |
订单403 | 个人应用/未申请 | 创建商家自用应用 + 申请 |
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翻页同步完整字段解析 吗?
