v2.0
控制台登录

短视频解析接口文档

一套接口覆盖抖音、快手、小红书、B站、微博、微信视频号。按次计费,失败不计费,多上游自动切换。
服务地址 https://nowater.lsqykj.com 返回格式 application/json 编码 UTF-8 更新 2026-11-05

一、接入流程

步骤说明
1. 开通联系商务,提供服务器出口 IP(或申请签名密钥)
2. 充值购买次数包,到账即可调用
3. 联调用 /api/v2/parse 跑通一条抖音链接
4. 上线按需加缓存与重试(失败不计费,可放心重试)

开通后会拿到两样东西:

二、鉴权

系统支持两种方式,开通时确定一种即可,调用方无感。

方式 A:IP 白名单(老客户沿用,最省事)

方式 B:HMAC 签名(推荐,可跨机器、可防重放)

每个请求带 4 个 Header:

Header必填说明
X-AppId是你的 AppID
X-Timestamp是秒级 Unix 时间戳,与服务器偏差不超过 300 秒
X-Nonce是随机串,至少 8 位,同一 nonce 5 分钟内只能用一次
X-Sign是签名值,小写十六进制

签名算法

text
待签串 = appid + "\n" + timestamp + "\n" + nonce + "\n" + md5(原始请求体)
sign   = HMAC_SHA256(待签串, secret)        # 输出小写 hex
!
三个最容易踩的坑
1. md5 必须对实际发出的原始 body 字节计算,不是重新序列化后的结果;body 为空时取 md5("")。
2. 四段之间是换行符 \n,不是 & 也不是空串。
3. X-Sign 必须转小写再比较。

三、接口清单

方法路径用途鉴权
POST/api/v2/parse解析短视频 / 图集(推荐)签名 / IP
GET/api/v2/quota查询剩余次数与限额签名 / IP
POST/api/video老接口,向下兼容(支持视频号分享链接)IP 白名单

四、解析接口 POST /api/v2/parse

请求

http
POST https://nowater.lsqykj.com/api/v2/parse
Content-Type: application/json

{
  "url": "https://v.douyin.com/bU_ILkJDCNw/"
}
参数类型必填说明
urlstring二选一 作品链接。可传整段分享文案,服务端会自动提取
codestring二选一 只有短码时传这个(如抖音 iRNBho6G),必须同时传 platform
platformstring否 平台提示,见附录 A。仅在传 code 或链接无法识别平台时需要

也支持 application/x-www-form-urlencoded 表单提交,字段名相同。

支持的输入格式

客户端拿到的往往不是干净链接。以下形态都能直接传,服务端会统一归一化:

形态示例
完整链接https://v.douyin.com/iRNBho6G/
整段分享文案7.68 复制打开抖音,看看【某某】的作品 https://v.douyin.com/iRNBho6G/ 复制此链接
缺协议头v.douyin.com/iRNBho6G/
协议相对//v.douyin.com/iRNBho6G/
被 URL 编码https%3A%2F%2Fv.douyin.com%2FiRNBho6G%2F
被二次编码https%253A%252F%252Fv.douyin.com%252FiRNBho6G%252F
带 HTML 实体https://v.douyin.com/iRNBho6G/?a=1&b=2
全角符号https://v.douyin.com/iRNBho6G/
带零宽字符 / emoji​https://v.douyin.com/iRNBho6G/ 🔥
只有短码{"code":"iRNBho6G","platform":"douyin"}
i
处理规则
· 文案里有多个链接时,优先挑已知平台的域名,避免抓到文案里的活动页、外链
· 链接尾部的 。,、!? 等中文标点自动去掉
· 只有短码且没传 platform 会直接报错(code=2002)——服务端不猜平台, 猜错只会打出一个必然失败的请求
· 短码拼接:抖音 v.douyin.com/{code}/、快手 v.kuaishou.com/{code}、 小红书 xhslink.com/a/{code}、B站 b23.tv/{code}

响应(成功)

json
{
  "code": 0,
  "msg": "ok",
  "request_id": "ae3a20d19ddfac9ac4eca414a889de26",
  "data": {
    "platform": "douyin",
    "type": "video",
    "title": "拿不下你 心好痛 #蔚蓝 #日常vlog",
    "cover": "https://p3-pc-sign.douyinpic.com/....jpeg",
    "videos": ["https://v26-default.365yg.com/....mp4"],
    "images": [],
    "audio": "",
    "duration": 15
  },
  "quota": {
    "balance": 482310,
    "charged": 1,
    "cache_hit": 0
  }
}
字段类型说明
codeint0 成功,非 0 见错误码表
msgstring成功固定 ok,失败为可读原因
request_idstring本次请求唯一号,报障时提供这个号
data.platformstring平台代码,见附录 A
data.typestringvideo 视频 / image 图集
data.titlestring作品文案,可能为空
data.coverstring封面图直链,可能为空
data.videosarray视频直链,按清晰度从高到低;type=image 时为空数组
data.imagesarray图集直链;type=video 时为空数组
data.audiostring音轨直链,仅部分海外/音乐类链接返回,其余为空串
data.durationint视频时长(秒),拿不到时为 0
quota.balanceint本次扣次后的剩余次数
quota.chargedint本次实际扣次(1 或 0)
quota.cache_hitint1 表示命中 30 分钟缓存
i
直链是临时的:videos / images 里的地址是平台 CDN 的临时签名链接, 通常 1~24 小时后失效。需要长期保存请自行转存到自己的对象存储。

响应(失败)

json
{
  "code": 3001,
  "msg": "解析失败,请稍后重试",
  "request_id": "3fdd33f7141ba9d23c4d8eff61b3a1b4",
  "data": null
}

五、余额查询 GET /api/v2/quota

json
{
  "code": 0,
  "msg": "ok",
  "data": {
    "balance_calls": 482310,
    "total_calls": 284572,
    "expire_at": null,
    "daily_quota": 3000,
    "qps_limit": 15
  }
}
字段说明
balance_calls剩余次数
total_calls累计成功调用次数
expire_at到期时间,null 表示永久
daily_quota每日上限,0 表示不限
qps_limit每秒请求上限

六、老接口(向下兼容)

已经在用旧版接口的客户,代码一行都不用改。响应结构、错误码、HTTP 状态码全部保持原样。

6.1 POST /api/video

http
POST /api/video
Content-Type: application/x-www-form-urlencoded

url=https://v.douyin.com/bU_ILkJDCNw/
成功响应
{
  "code": 200,
  "msg": "success",
  "data": {
    "image": "https://...jpeg",
    "title": "作品文案",
    "isweibo": false,
    "isyoutube": false,
    "type": "single",
    "images_url": "",
    "video_url": "https://....mp4",
    "audio_url": ""
  }
}
字段说明
image封面
title文案
isweibo / isyoutube平台标记,仅这两个平台为 true
type固定 "single"(历史字段,不要用来判断类型)
images_url图集时为数组,视频时为空字符串 ""(不是空数组)
video_url视频时为直链,图集时为空字符串
audio_url音轨,通常为空
code含义
-1通用失败(含剩余次数不足、接口停用)
-2url 链接不正确
-4视频解析失败
!
这个接口 HTTP 状态码恒为 200,请按 code 判断结果。

视频号走这个接口

微信视频号(https://weixin.qq.com/sph/xxxx)直接传分享链接即可,返回同样的老结构, 视频直链在 data.video_url,封面在 data.image:

bash
curl -X POST https://nowater.lsqykj.com/api/video \
     --data-urlencode "url=https://weixin.qq.com/sph/AsQc8pkmcK"
i
视频号走微信机器人网关,单次通常 0.5~3 秒,偶发慢时请把客户端超时设到 30 秒以上。

6.2 POST /api/sphvideo(微信视频号)— 已下线

视频号接口已停止服务。历史调用会收到:

json
{"code": -1, "msg": "视频号接口已下线", "data": ""}

原有客户端无需改动,但只会拿到 code=-1。如需重新开通请联系商务。

七、计费规则

场景是否扣次
解析成功✅ 扣 1 次
命中缓存(同链接 30 分钟内重复请求)✅ 扣 1 次
下游失败 / 上游超时❌ 不扣,自动退回
参数错误、签名错误、余额不足、超限流❌ 不扣
平台不支持 / 平台临时关闭❌ 不扣

八、限流

项说明
QPS按密钥维度限制(默认 10~15 次/秒),可单独调整
日额度按密钥维度限制(默认 3000 次/天,0 表示不限)
超出 QPSHTTP 429,code=1009
超出日额度HTTP 429,code=1010

建议的客户端策略:并发控制在 5 以内;收到 429 后按指数退避重试(1s → 2s → 4s),不要原地重打。

九、错误码表

codeHTTP含义 是否扣次处理建议
0200成功是—
1001401签名参数缺失否检查 4 个 Header 是否都带上
1002401签名错误否核对签名串拼接与 secret
1003401时间戳过期否校准服务器时间(NTP)
1004401nonce 重复否nonce 用随机串,不要复用
1005401密钥不存在或已停用否联系商务
1006401密钥已到期否续期
1007402剩余次数不足否充值
1008401IP 不在白名单否报备出口 IP
1009429超出 QPS否降并发 / 退避重试
1010429超出日额度否次日再试或提额
2001400平台不支持 / 已关闭否换链接或联系开通
2002400url 参数非法否检查链接
3001502上游解析失败否可重试 1~2 次
3002503暂无可用通道否稍后重试
5000500服务内部异常否拿 request_id 联系技术支持

十、代码示例

cURL

bash
curl -X POST 'https://nowater.lsqykj.com/api/v2/parse' \
  -H 'X-AppId: ak_1aea664195' \
  -H "X-Timestamp: $(date +%s)" \
  -H 'X-Nonce: 8f3a9c2e1b7d' \
  -H 'X-Sign: <计算出的签名>' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://v.douyin.com/bU_ILkJDCNw/"}'

PHP

php
<?php
$appid  = 'ak_1aea664195';
$secret = '你的secret';
$body   = json_encode(['url' => 'https://v.douyin.com/bU_ILkJDCNw/'], JSON_UNESCAPED_SLASHES);
$ts     = time();
$nonce  = bin2hex(random_bytes(8));

$sign = hash_hmac('sha256', $appid . "\n" . $ts . "\n" . $nonce . "\n" . md5($body), $secret);

$ch = curl_init('https://nowater.lsqykj.com/api/v2/parse');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $body,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'X-AppId: '     . $appid,
        'X-Timestamp: ' . $ts,
        'X-Nonce: '     . $nonce,
        'X-Sign: '      . $sign,
    ],
]);
$resp = curl_exec($ch);
curl_close($ch);

$data = json_decode($resp, true);
if ($data['code'] === 0) {
    echo $data['data']['videos'][0] ?? '';
} else {
    echo '失败: ' . $data['msg'] . ' (' . $data['request_id'] . ')';
}

Node.js

javascript
const crypto = require('crypto');

const APPID  = 'ak_1aea664195';
const SECRET = '你的secret';

async function parse(url) {
  const body  = JSON.stringify({ url });
  const ts    = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomBytes(8).toString('hex');
  const md5   = crypto.createHash('md5').update(body).digest('hex');
  const sign  = crypto.createHmac('sha256', SECRET)
                      .update(`${APPID}\n${ts}\n${nonce}\n${md5}`)
                      .digest('hex');

  const res = await fetch('https://nowater.lsqykj.com/api/v2/parse', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-AppId': APPID,
      'X-Timestamp': ts,
      'X-Nonce': nonce,
      'X-Sign': sign
    },
    body
  });
  return res.json();
}

Python

python
import hashlib, hmac, json, time, secrets, requests

APPID  = "ak_1aea664195"
SECRET = "你的secret"

def parse(url: str) -> dict:
    body  = json.dumps({"url": url}, separators=(",", ":"))
    ts    = str(int(time.time()))
    nonce = secrets.token_hex(8)
    plain = f"{APPID}\n{ts}\n{nonce}\n{hashlib.md5(body.encode()).hexdigest()}"
    sign  = hmac.new(SECRET.encode(), plain.encode(), hashlib.sha256).hexdigest()

    r = requests.post(
        "https://nowater.lsqykj.com/api/v2/parse",
        data=body.encode(),
        headers={
            "Content-Type": "application/json",
            "X-AppId": APPID,
            "X-Timestamp": ts,
            "X-Nonce": nonce,
            "X-Sign": sign,
        },
        timeout=30,
    )
    return r.json()
!
Python 的 json.dumps 默认带空格。签名和发送必须用同一个 body 字符串—— 上面用 separators=(",", ":") 固定格式,并把同一个 body 用于签名和发送。

Java

java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;

public class DspClient {
    static String hmacHex(String data, String key) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        return HexFormat.of().formatHex(mac.doFinal(data.getBytes(StandardCharsets.UTF_8)));
    }

    static String md5Hex(String s) throws Exception {
        MessageDigest md = MessageDigest.getInstance("MD5");
        return HexFormat.of().formatHex(md.digest(s.getBytes(StandardCharsets.UTF_8)));
    }

    public static void main(String[] args) throws Exception {
        String appid = "ak_1aea664195", secret = "你的secret";
        String body  = "{\"url\":\"https://v.douyin.com/bU_ILkJDCNw/\"}";
        String ts    = String.valueOf(System.currentTimeMillis() / 1000);
        String nonce = java.util.UUID.randomUUID().toString().replace("-", "").substring(0, 16);

        String plain = appid + "\n" + ts + "\n" + nonce + "\n" + md5Hex(body);
        String sign  = hmacHex(plain, secret);
        // 用 HttpClient 带上 X-AppId / X-Timestamp / X-Nonce / X-Sign 发出
    }
}

Go

go
appid, secret := "ak_1aea664195", "你的secret"
body  := []byte(`{"url":"https://v.douyin.com/bU_ILkJDCNw/"}`)
ts    := strconv.FormatInt(time.Now().Unix(), 10)
nonce := "8f3a9c2e1b7d"

sum   := md5.Sum(body)
plain := appid + "\n" + ts + "\n" + nonce + "\n" + hex.EncodeToString(sum[:])
h     := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(plain))
sign  := hex.EncodeToString(h.Sum(nil))

req, _ := http.NewRequest("POST", "https://nowater.lsqykj.com/api/v2/parse", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-AppId", appid)
req.Header.Set("X-Timestamp", ts)
req.Header.Set("X-Nonce", nonce)
req.Header.Set("X-Sign", sign)
resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)

十一、常见问题

返回 3001 上游解析失败,要不要重试?

可以重试 1~2 次,中间隔 1 秒。失败不计费,但频繁失败通常是作品已删除或平台限流。

同一条链接连续调用两次,为什么第二次更快?

30 分钟内命中缓存,直接返回上次结果(cache_hit=1),不请求上游。缓存命中仍计 1 次。

videos 数组有多个是什么情况?

不同清晰度。数组按无水印最高清在前,一般取 videos[0] 即可。

图集怎么判断?

看 data.type:video 取 videos,image 取 images。 不要用数组是否为空判断,个别平台会两个都返回。

链接里带一堆分享文案可以吗?

可以,服务端会自动提取第一个 http(s) 链接。

IP 白名单模式下,我加机器后调不通?

新机器的出口 IP 要报备。同一个客户可以申请多个密钥,分别绑不同 IP。

我在自己电脑上直接调 /api/video,返回"IP不在白名单内"?

这是正常的。IP 白名单只放行你报备过的服务器 IP,本地调试的宽带 IP 不在里面。两种办法:

为什么不用签名也能调 /api/video?

/api/video 是给老客户的兼容接口,沿用原来的 IP 白名单鉴权。 新客户建议直接用 /api/v2/parse,签名鉴权可跨机器、可防重放。

报障要提供什么?

request_id。一条日志能定位到渠道、耗时、扣次和上游原始返回。

附录

A. 平台代码

code平台状态
douyin抖音✅ 可用
kuaishou快手✅ 可用
xhs小红书✅ 可用
bilibiliB 站✅ 可用
weibo微博✅ 可用
sph微信视频号✅ 走 /api/video(传分享链接)
ytYouTube⏸ 需单独开通
twitterX / Twitter⏸ 需单独开通
instagramInstagram⏸ 需单独开通
tiktokTikTok⏸ 需单独开通

B. 变更记录

日期版本变更
2026-11-05v2.0.2 /api/video 恢复支持微信视频号(传 https://weixin.qq.com/sph/xxx 分享链接即可);/api/sphvideo 仍保持下线
2026-11-05v2.0.1 /api/sphvideo(微信视频号)已下线,调用返回 {"code":-1,"msg":"视频号接口已下线","data":""}
2026-11-05v2.0 新增 /api/v2/parse、/api/v2/quota; /api/video、/api/sphvideo 保持兼容; 改为按次计费,失败自动退款

技术支持:报障请提供 request_id + 调用时间 + 平台,可直接定位。