抖音开放平台调用手册
在授权期限内刷新用户凭证
使用尚有效的 refresh_token 刷新 access_token;此接口不会延长 refresh_token 本身期限,也不会新增授权。
← 返回平台功能接入准备
- 持有本站新版授权换取示例保存的同一用户凭证文件,含 client_key、open_id 及 refresh_expires_at 绝对截止时间;不从缺少元数据的旧文件猜测原期限。
- 用户未撤回授权,refresh_token 仍在有效期;私有 DY_TOKEN_FILE 目录已存在。
准备环境变量
示例值需要替换为自己的凭证或资源 ID。代码在本机或服务端运行;密钥不应放进公开网页。
DY_CLIENT_KEY- 与该用户 refresh_token 相同的网站应用。
DY_REFRESH_TOKEN- 该用户尚有效的 refresh_token。
DY_TOKEN_FILE- 本用户由新版初始交换生成的私有凭证文件,包含应用/用户归属与绝对截止时间;不能与其他用户共用。
export DY_CLIENT_KEY='tt_REPLACE_WITH_CLIENT_KEY'
export DY_REFRESH_TOKEN='RFT_EXAMPLE_NOT_A_REAL_TOKEN'
export DY_TOKEN_FILE='/secure/douyin-user-token.json'以上 export 适用于 macOS / Linux 的终端。按调用步骤保存代码文件,再在同一终端执行。
调用步骤
- 01
从该用户私有文件读取 refresh_token,设置 DY_CLIENT_KEY、DY_REFRESH_TOKEN、DY_TOKEN_FILE;代码在请求前检查归属、与原文件一致性、原截止时间和输出目录。
- 02
保存为 refresh.py,运行 python3 refresh.py;成功才原子替换当次结果并保存 received_at、access_expires_at 和 refresh_expires_at;多个服务应集中更新并使用锁。
- 03
access_expires_at 按本次响应计算;refresh_expires_at 保留原截止,若响应给出更早到期值则只取更早值,绝不能从刷新时刻重新计满原期限。过期或撤回后重新授权。
调用 01
刷新用户 access_token
POST 表单,固定 grant_type=refresh_token;不传 ClientSecret,不能把此接口与续期 refresh_token 的另一个能力混用。
POST
https://open.douyin.com/oauth/refresh_token/请求头
Content-Typeapplication/x-www-form-urlencoded
参数说明
参数表可左右滑动,查看完整说明。
| 字段 / 位置 | 类型 / 必填 | 说明 |
|---|---|---|
client_key请求体 | string必填 | 当前授权应用 ClientKey。 |
grant_type请求体 | string必填 | 固定 refresh_token。 |
refresh_token请求体 | string必填 | 尚有效的用户刷新凭证,不是 access_token。 |
调用代码
替换示例 ID 与请求体内容后运行。
import json
import os
import time
import tempfile
from pathlib import Path
from contextlib import contextmanager
from urllib.parse import urlencode
import urllib.error
import urllib.request
def env(name):
value = os.environ.get(name)
if not value or not value.strip() or "REPLACE_WITH" in value:
raise SystemExit("请设置真实环境变量: " + name)
return value
def positive_int(value, name):
if isinstance(value, bool):
raise SystemExit("无效期限字段: " + name)
try:
number = int(value)
except (TypeError, ValueError):
raise SystemExit("无效期限字段: " + name)
if number <= 0:
raise SystemExit("无效期限字段: " + name)
return number
def load_record(path):
try:
record = json.loads(path.read_text(encoding="utf-8"))
except (OSError, ValueError):
raise SystemExit("无法读取原凭证文件;请检查路径与安全存储格式")
if not isinstance(record, dict):
raise SystemExit("原凭证文件格式错误")
return record
@contextmanager
def pending_file(value):
path = Path(value).expanduser().absolute()
if path.is_symlink() or (path.exists() and not path.is_file()):
raise SystemExit("凭证目标必须是私有目录中的普通文件,不能是目录或符号链接")
# 先创建 0600 临时文件并确认可写,再消费授权码或发送刷新请求。
# 这能前置发现路径配置错误;不能保证请求后永不发生磁盘或系统故障。
try:
output = tempfile.NamedTemporaryFile("w", encoding="utf-8",
dir=path.parent, prefix=".douyin-", delete=False)
except OSError:
raise SystemExit("无法在凭证目录创建文件;尚未发送请求")
try:
os.chmod(output.name, 0o600)
output.flush()
os.fsync(output.fileno())
yield path, output
finally:
output.close()
try:
os.unlink(output.name)
except FileNotFoundError:
pass
def post(url, payload):
request = urllib.request.Request(url, data=payload,
headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST")
try:
with urllib.request.urlopen(request, timeout=30) as response:
result = json.load(response)
except urllib.error.HTTPError as error:
raise SystemExit("HTTP " + str(error.code) + ";不输出可能含凭证的原文")
except (urllib.error.URLError, TimeoutError):
raise SystemExit("网络或超时错误;不要盲目重复消费一次性授权码")
except (ValueError, UnicodeError):
raise SystemExit("响应不是有效 JSON,原凭证文件未覆盖")
if not isinstance(result, dict) or not isinstance(result.get("data"), dict):
raise SystemExit("响应缺少 data,原凭证文件未覆盖")
data = result["data"]
if str(data.get("error_code")) != "0" or not data.get("access_token"):
raise SystemExit("业务请求失败,业务码: " + str(data.get("error_code")))
if not isinstance(data.get("open_id"), str) or not data["open_id"]:
raise SystemExit("响应缺少用户归属,原凭证文件未覆盖")
return data
def commit(path, output, record):
json.dump(record, output, ensure_ascii=False)
output.flush()
os.fsync(output.fileno())
output.close()
os.replace(output.name, path)
print("凭证已安全保存;请按绝对截止时间检查有效期,不输出凭证")
# 凭证文件必须来自本页新版初始交换;缺少绝对截止时间时不能猜测补值。
client_key = env("DY_CLIENT_KEY")
refresh_token = env("DY_REFRESH_TOKEN")
token_file = env("DY_TOKEN_FILE")
with pending_file(token_file) as (path, output):
previous = load_record(path)
if previous.get("client_key") != client_key or not previous.get("open_id"):
raise SystemExit("原凭证文件缺少归属或属于其他应用;尚未发送请求")
if previous.get("refresh_token") != refresh_token:
raise SystemExit("环境刷新凭证与原文件不一致;尚未发送请求")
original_deadline = positive_int(previous.get("refresh_expires_at"), "refresh_expires_at")
started_at = int(time.time())
if original_deadline <= started_at:
raise SystemExit("原 refresh_token 已到期,请用户重新授权;尚未发送请求")
body = urlencode({"client_key": client_key, "refresh_token": refresh_token,
"grant_type": "refresh_token"}).encode("utf-8")
data = post("https://open.douyin.com/oauth/refresh_token/", body)
received_at = int(time.time())
if data["open_id"] != previous["open_id"]:
raise SystemExit("刷新结果用户不一致,原文件未覆盖")
# 普通 refresh_token 接口不延长 refresh_token 本身的有效期。
# 即使响应给了较长相对时长,也绝不从现在重新计满原期限。
refresh_deadline = original_deadline
if data.get("refresh_expires_in") is not None:
refresh_deadline = min(original_deadline, started_at +
positive_int(data["refresh_expires_in"], "refresh_expires_in"))
record = dict(previous)
record.update(data)
record.update(client_key=client_key, open_id=previous["open_id"], received_at=received_at,
access_expires_at=started_at + positive_int(data.get("expires_in"), "expires_in"),
refresh_expires_at=refresh_deadline)
if not isinstance(record.get("refresh_token"), str) or not record["refresh_token"]:
raise SystemExit("刷新结果缺少凭证,原文件未覆盖")
if min(record["access_expires_at"], refresh_deadline) <= received_at:
raise SystemExit("响应到达时期限已过,原文件未覆盖")
commit(path, output, record)
返回示例
结构节选;文档中的有效期字段可显示为字符串,保存时按当次值解析,不能固定照抄秒数。 以下为结构示意,非本站实测结果。
{
"data": {
"access_token": "ACT_REFRESHED_EXAMPLE",
"error_code": 0,
"expires_in": "86400",
"open_id": "OPEN_ID_EXAMPLE",
"refresh_expires_in": "86400",
"refresh_token": "RFT_EXAMPLE_NOT_A_REAL_TOKEN",
"scope": "user_info"
},
"message": "success"
}常见错误与排查
10010refresh_token 已过期- 请用户重新完成授权;不能不断重试旧刷新凭证。
10005 / 10002参数缺失或错误- 核对同一应用 ClientKey、grant_type 和刷新凭证,使用表单编码。
10001系统错误- 有限次退避后仍失败时记录必要日志标识,向官方反馈,勿把凭证原文写入日志。
调用注意
- 官方说明通常 access_token 15 天、refresh_token 30 天,最终按响应和用户撤回状态判断;本接口不重置 refresh_token 到期时间。
- 续期 refresh_token 另需 renew_refresh_token 能力,本手册不申请或调用该能力。
注意事项
- 在用户撤回授权后,原 access_token 会立即失效,不能靠 refresh_token 绕过用户选择。
- 请求代码只示意单次刷新;线上需按应用和用户隔离存储,避免多个实例并发刷新互相覆盖。
- 失败、跨应用或跨用户响应不覆盖原文件;请求前创建的临时文件最终清理。预检不能保证请求后完全不会发生磁盘或系统故障,线上应使用可靠凭证库、原子提交和单用户刷新锁。
核验来源
本站按官方资料整理本场景的必要调用步骤与代码,覆盖范围以本页列出的接口为准。官方资料用于追溯规范及查看变更。
- 01网站应用手机号和扫码登录授权核验日期 2026-10-15
- 02刷新 access_token核验日期 2026-10-15
