◈
哨卡云
开发者接入文档
协议 v1 · 0.1.0
网络验证 · 卡密发行

把 哨卡云 的 /v1 接口接进你的程序

一次登录换令牌,之后用心跳保活。这一页讲清楚四件事:签名怎么算、每条接口传什么、返回里那些字段是什么、以及用户报「验证失败」时该看哪个码。示例可直接抄,口径全按表里的写。

接口基址 https://api.dlam.cc/v1/

01接入准备

三样东西就够:接口基址、app_key、app_secret。后两个在控制台「软件管理」里,一个软件一对,app_secret 可以随时重置。

接口基址
https://api.dlam.cc/v1/…
所有调用都是 基址 + 端点路径,路径同时是签名要素。
app_key
软件管理页
软件的身份证,明文随每次请求上传。认不出来回 10230。
app_secret
同页,可一键重置
只参与算签名。重置后旧签名立即全失效,客户端要跟着换。

发卡、建账号、看调用流水都在 开发者控制台 里做;接口不需要你点头,客户端自己就能把卡激活。

软件在控制台被停用、或者开发者账号被停用时,这一整套接口对那个软件回 10212。这不是用户的问题,客户端最好提示「服务暂停,请联系作者」。

02请求与公共参数

一律打到 https://api.dlam.cc/v1/…。写动作只认 POST;允许 GET 的只有下面这几条只读接口,其余用 GET 打过去就是 404。参数用表单或 JSON 都收,同名时请求体覆盖查询串——建议全放请求体,别把卡密和签名挂进 URL。

只开 GET 的:/v1/card/config /v1/user/config /v1/software/config /v1/software/notice /v1/software/latest_ver /v1/af/remote_var /v1/af/remote_data

每条请求都要带的公共参数(它们也全部参与签名):

参数要求说明
app_key必传软件的 Key。缺失直接 400,errs 里点名是哪个参数。
timestamp必传秒级 10 位。比服务器快超过 5 秒 → 10013;与服务器相差 60 秒以上 → 10011。客户端时钟不可信,用登录返回的 server_time 校准。
nonce必传不超过 36 位的随机串,每次都要新(UUID 去横线就行)。服务端按软件维度记 60 秒,重复即 10014。空串与超长串一律判重复。
sign必传请求签名,见下一节。缺了先回 400 而不是 10010。
校验顺序就是报错顺序:先认软件(10230 / 10212)→ 再验签(10010)→ 再判时间(10013 / 10011)→ 最后判 nonce(10014)。SDK 里按 code 分支,不要靠 message 的字句判断。
唯一的例外是 探活接口 GET /v1/ping:不签名、不要 app_key,正文就是一句裸文本 Pong。它给客户端在「还没定位到软件」的时候判断这台 API 是否活着,不通就换备用域名。除 GET 以外一律按未找到处理。

03签名算法

五个要素按顺序拼成一串,做一次 MD5,十六进制小写:

请求签名
sign = md5( METHOD + host + path + k1=v1&k2=v2&…&kn=vn + app_secret )
要素怎么取
METHOD大写,POST 或 GET。它参与签名,所以方法用错必算不过。
host只含域名,不带协议与端口。必须是你实际访问的那个 Host:换成 IP、换成别名,签名就废了(这是 10010 最常见的来源)。软件自建在非 80/443 端口上时,请求地址照旧带端口,签名里那一段要把 :端口 去掉——服务端读 Host 时也是从第一个冒号截断的。
path带 /v1 的原始路径,例 /v1/card/login。
k=v 串除 sign 外的全部参数,格式化成 k=v 后排序,用 & 连接。
app_secret软件密钥,接在末尾,无分隔符。

三个必须照做的口径

  • 排的是整条 k=v 字符串,不是参数名。差别在数字后缀上:app_key2=b 排在 app_key=a 前面(因为 2 的字节小于 =)。
  • value 原样拼,不做 URL 编码。编码发生在 HTTP 层,签名算的是编码前那串。值里带 &、=、空格时尤其容易错。
  • 数组与对象类型的参数不参与签名。要传结构化数据(比如远程函数的 params),先自己 JSON 化成字符串再传。
PHP 参考实现(可直接抄)
<?php
$HOST   = 'api.dlam.cc';          // 只写域名:不带 https://、不带端口。它参与签名
$SECRET = '你的 app_secret';       // 只参与计算,不打印、不写日志

function scx_sign($method, $path, array $p, $host, $secret)
{
    $kv = [];
    foreach ($p as $k => $v) {
        if (is_array($v)) continue;          // 数组不参与签名
        $kv[] = $k . '=' . $v;               // value 原样拼,不做 URL 编码
    }
    usort($kv, 'strcmp');                    // 排的是整条 k=v,不是参数名
    return md5(strtoupper($method) . $host . $path . implode('&', $kv) . $secret);
}

function scx_call($path, array $p, $method = 'POST')
{
    global $HOST, $SECRET;
    $p['app_key']   = '你的 app_key';
    $p['timestamp'] = time();
    $p['nonce']     = bin2hex(random_bytes(8));        // 每次都要新,≤36 位
    $p['sign']      = scx_sign($method, $path, $p, $HOST, $SECRET);

    $ch = curl_init('https://' . $HOST . $path);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_POSTFIELDS     => http_build_query($p),
    ]);
    $raw = curl_exec($ch);
    if ($raw === false) {
        $err = curl_error($ch); curl_close($ch);
        return ['code' => -1, 'message' => '网络异常:' . $err];
    }
    curl_close($ch);
    $j = json_decode((string)$raw, true);
    return is_array($j) ? $j : ['code' => -2, 'message' => '返回不是合法 JSON'];
}

$r = scx_call('/v1/card/login', ['card' => '卡密', 'device_id' => '机器码']);
if (isset($r['code']) && (int)$r['code'] === 0) {
    $res = $r['result'];
    echo '可用到 ' . ($res['expires'] !== '' ? $res['expires'] : '不限时长')
       . ',每 ' . $res['hg'] . ' 秒发一次心跳' . PHP_EOL;
    // 之后:scx_call('/v1/card/heartbeat', ['card'=>'卡密','token'=>$res['token']]);
} else {
    echo $r['code'] . ' · ' . $r['message'] . PHP_EOL;   // 含义见「返回码对照」
}
Python 参考实现
import hashlib, hmac, json, time, uuid, requests

HOST   = "api.dlam.cc"            # 只写域名,它参与签名
SECRET = "你的 app_secret"
APP_KEY = "你的 app_key"

def sign(method, path, params):
    kv = [f"{k}={params[k]}" for k in params if k != "sign" and not isinstance(params[k], (list, dict))]
    kv.sort()                                   # 排的是整条 k=v,不是参数名
    raw = method.upper() + HOST + path + "&".join(kv) + SECRET
    return hashlib.md5(raw.encode()).hexdigest()

def call(path, params, method="POST"):
    p = dict(params)
    p.update(app_key=APP_KEY, timestamp=int(time.time()), nonce=uuid.uuid4().hex)
    p["sign"] = sign(method, path, p)
    r = requests.request(method, f"https://{HOST}{path}", data=p, timeout=10)
    return r.json()

res = call("/v1/card/login", {"card": "卡密", "device_id": "机器码"})
if res["code"] == 0:
    print("可用到", res["result"]["expires"], "心跳", res["result"]["hg"], "秒")
else:
    print(res["code"], res["message"])
签名由客户端算,意味着 app_secret 必然存在于你分发出去的程序里。它能挡住「别人不知道密钥就来白嫖你的 app_key」,挡不住铁了心拆包的人。要更强的控制,请把敏感逻辑放到 远程函数 里,让答案只在服务端算出来。

04响应与双向签名

返回永远是这一个信封。成功是 code = 0,不是 200;HTTP 状态码一律 200,成败只看 code。

一次卡密登录的成功返回(result 字段见端点表)
{
  "code": 0,
  "message": "ok",
  "result": {
    "card_type": "会员",
    "token": "k3f9dq2m1x7b5p0c",
    "expires": "2026-11-19 14:20:31",
    "expires_ts": 1795742431,
    "config": "",
    "server_time": 1792548031,
    "hg": 600
  },
  "nonce": "bqzp3k1k00a00",
  "sign": "0d4f6a9c2b1e5837a0c94d2f6b1e0735"
}
字段说明
code结果码,0 才算成功。没有 result 这个 key 也是正常:成功且无额外数据时它整个缺席,不是空对象、不是 null。
message给人看的一句话,和 返回码对照 里的文案一致。可以原样显示给用户,除了 10543 这类给开发者看的。
result业务数据,只有非空时出现。
errs只在 400 时出现:逐字段说清哪个参数没带对。只有公共参数缺失那类 400 带它,业务参数缺失的 400 由 message 点名缺的是哪个。
nonce服务端 nonce,趋势递增。客户端记下上一个,比它小或相等的响应就是重放。
sign响应签名,用同一个 app_secret 验。
响应签名
sign = md5( code + message + k1=v1&k2=v2&… + nonce + app_secret )

中间的 k=v 串取的是响应 result 里所有非数组字段,同样按整条 k=v 排序后连接;拼接处没有分隔符。result 为空时那一段是空字符串——不是 0,也不是 null。失败响应照样签名,SDK 里对 code≠0 不校验也不亏。

05接口清单(24 条)

按组排开,每条列请求参数、成功返回的字段、以及实践里真会撞上的失败码。参数名一字不差照抄——多带一个不认识的参数,签名就算不过。

01卡密组 · 8 条

登录

POST /v1/card/login 客户端拿卡密换 token,顺带完成首次激活;次数卡与积分卡在这一步扣量
参数要求说明
card必传卡密原文,控制台「卡密管理」生成或导入的那一串,原样传,不用带软件前缀之外的加工。
device_id看情况机器码,最长 120 位。软件开了「绑定机器码」时必传;建议用 CPU+主板+磁盘序列号做哈希,重装系统尽量不变,别用纯 MAC(虚拟机可改)。

成功时 result 里带:

字段形态说明
card_typestring这张卡的卡类名;没挂卡类时是类型本身(会员 / 单码 / 次数 / 积分 / 充值)。适合显示在「当前授权」那一栏。
tokenstring会话令牌。心跳、退出、解绑、读带校验的远程变量都要带它。
expiresstring到期时间 Y-m-d H:i:s。次数卡与积分卡没有到期概念,回空串。
expires_tsint同一个到期时间的秒级时间戳;0 表示这张卡不限时长。
configstring这张卡的配置串,和 GET /v1/card/config 读到的是同一个值。
server_timeint服务器当前秒级时间戳。拿它校准本地时钟,别只信客户端时间。
hgint心跳间隔,单位秒(最少 60)。按这个值发心跳,别写死。

可能的失败:400 参数错误 10230 软件不存在,请检查 app_key 10218 卡密不可用 10212 卡密已被冻结 10210 卡密已过期 10215 只能在首次登录绑定的设备上使用 10213 卡密超过多开上限

首次登录那一刻就是激活:会员与单码的到期时间从这时起算,卖卡前提醒用户「装好再激活」。同一台设备重复登录会复用还没过期的那个令牌,不会多占一个多开名额;软件开了「顶号」时反过来——每次登录把其它在线会话撤掉。次数卡与积分卡在这一步扣一次,心跳不扣量。

心跳

POST /v1/card/heartbeat 在线保活,按软件上的间隔上报;只续令牌,扣量不在这里
参数要求说明
card必传登录时用的那张卡密。
token必传登录返回的会话令牌。

成功时 result 里带:

字段形态说明
expiresstring这个令牌的下一次生死线:现在 + token 有效期。不是卡的到期时间——卡的到期时间在登录那一步就定下来了。
expires_tsint同上,秒级时间戳。
server_timeint服务器当前秒级时间戳。

可能的失败:400 参数错误 10214 登录状态已失效 10218 卡密不可用 10212 卡密已被冻结

心跳只续令牌,不续卡。超过 token 有效期没打心跳,令牌自己失效,下一次要回登录;卡在这期间到期或被冻结,心跳同样回失败,客户端要立刻锁功能,不能拿登录时的旧结果继续跑。

退出

POST /v1/card/logout 主动下线,这个 token 立刻失效
参数要求说明
card看情况传了就和令牌所属的卡核对一次,不传只按令牌处理。
token必传要作废的令牌。

成功只有 code=0,没有 result——别去取返回体里的字段。

可能的失败:400 参数错误 10214 登录状态已失效

已经失效的令牌再退一次也回 10214。这不是「退出失败」,本地配置清干净就行,别弹错误给用户看。

解绑

POST /v1/card/unbind_device 带 token 换绑机器码,扣不扣时看软件上的解绑设置
参数要求说明
card必传要换机的卡密。
token必传当前在线令牌。解绑成功后这个令牌一并作废。
device_id看情况要解掉的机器码。不传就解掉发起这次请求的那个令牌所绑的设备。

成功只有 code=0,没有 result——别去取返回体里的字段。

可能的失败:400 参数错误 10216 软件未开启解绑设备功能 10255 该卡密不可解绑设备 10257 卡密剩余时间已不足以解绑扣除 10214 登录状态已失效 10212 卡密已被冻结 10218 卡密不可用

解绑成功后这个令牌一并作废,客户端要重新走登录。软件设了「解绑扣时」的话,卡的到期时间会往前挪那个分钟数;剩余时间不足以扣时就直接拒(10257)。

设解绑密码

POST /v1/card/unbind_password 给这张卡设一个 6-10 位解绑密码,忘带电脑时用
参数要求说明
card必传卡密。
token必传当前在线令牌。
password必传6-10 位解绑密码,明文存在这张卡上。

成功只有 code=0,没有 result——别去取返回体里的字段。

可能的失败:400 参数错误 10214 登录状态已失效 10218 卡密不可用

解绑密码存在卡上,是给人「忘带电脑」用的兜底,不要拿它当账号密码,也别用它替代 token。

密码解绑

POST /v1/card/unbind_device/by_password 不要 token,只凭卡密加解绑密码换绑
参数要求说明
card必传卡密。
password必传解绑密码:优先认这张卡自己设过的,卡上没设过就回落到软件上的默认解绑密码。
device_id看情况要解掉的机器码。新用户常常拿新机器码来,那也行——先解旧设备,再回「登录」重新绑。

成功只有 code=0,没有 result——别去取返回体里的字段。

可能的失败:400 参数错误 10216 软件未开启解绑设备功能 10254 解绑密码不正确 10255 该卡密不可解绑设备 10257 卡密剩余时间已不足以解绑扣除 10212 卡密已被冻结 10218 卡密不可用

这条是免登录换机的入口:在新机器上直接调,解完还要回 /v1/card/login 才能真正登录,它自己不签发令牌。

充值

POST /v1/card/recharge 以卡充卡:烧掉一张新卡,把它的时长与次数接到在用卡上
参数要求说明
card必传要被续期的那张卡:必须已经激活(登录过一次)。
use_card必传拿来烧的那张新卡:必须是「未使用」,且多开上限与目标卡一致。

成功只有 code=0,没有 result——别去取返回体里的字段。

可能的失败:400 参数错误 10240 卡密不存在或已被使用 10241 卡密已被使用 10244 不能对未使用的卡密进行充值 10256 卡密多开配置不相等,不能充值 10212 卡密已被冻结

供体卡的天数(含零头分钟)、次数、积分都并进目标卡,随后供体变成「已使用」。时长是从现在往后加,不追回目标卡已经过掉的那几天。成功只有 code=0,想知道续到了哪天,再调一次登录看 expires。

卡密配置

GETPOST /v1/card/config 读写这张卡自己的那串配置,同一 URL 靠方法区分
参数要求说明
card必传卡密。
config看情况只在 POST 写的时候必传,最长 512 位,内容随你(想存结构化数据就自己 JSON 化)。GET 读的时候不用带。

成功时 result 里带:

字段形态说明
configstring只有 GET 读的时候有:这张卡的配置串原文,和登录返回里那个 config 是同一个值。POST 写成功没有 result。

可能的失败:400 参数错误 10218 卡密不可用 10212 卡密已被冻结

这对接口只验软件签名,不认任何卡密凭证:拿着 app_secret 的人能读写任意一张卡的配置。别在这串里放密钥。另外 GET 成功不写用卡记录,POST 才写一行。

02用户组 · 8 条

用户注册

POST /v1/user/register 用卡密开一个账号,卡进这个账号的名下
参数要求说明
card必传用来开号的卡密,最长 45 位。注册成功这张卡就被烧进这个账号名下。
username必传账号名,不超过 20 位。
password必传登录密码,6-30 位,入库走强哈希,接口永不返回。
device_id必传注册时的设备,最长 120 位。这台会直接成为首个绑定设备。

成功只有 code=0,没有 result——别去取返回体里的字段。

可能的失败:400 参数错误 10240 卡密不存在或已被使用 10241 卡密已被使用 10242 账号已存在 10210 卡密已过期 10212 卡密已被冻结

注册即激活,不需要先调 /v1/card/login:卡当场变成「已使用」并挂进账号名下,账号与这张主卡共用同一个到期点。注册成功后直接走用户登录拿令牌。

用户登录

POST /v1/user/login 账号密码登录,有效期看账号,额度看名下卡池
参数要求说明
username必传账号名。
password必传登录密码。
device_id看情况机器码。软件开了「绑定机器码」时必传。

成功时 result 里带:

字段形态说明
card_typestring账号名下那张主卡的卡类名;名下没有卡时回空串。
tokenstring账号侧会话令牌。
expiresstring账号的到期时间。账号与名下主卡共用同一个到期点。
expires_tsint同上,秒级时间戳。
configstring账号的配置串,和 GET /v1/user/config 同源。
server_timeint服务器当前秒级时间戳。
hgint心跳间隔,单位秒。

可能的失败:400 参数错误 10243 用户登录密码错误 10250 用户已到期 10252 用户已被冻结 10218 卡密不可用 10253 用户超过多开上限 10215 只能在首次登录绑定的设备上使用

用户侧的扣量发生在登录:账号名下若有带次数或积分的卡,这次登录从其中一张扣一次;心跳只保活。名下卡池烧空时回 10218,这时该引导用户「用户充值」补一张进去。

用户心跳

POST /v1/user/heartbeat 账号侧保活,同样只续令牌;扣量在用户登录那一步
参数要求说明
username必传账号名,必须和令牌持有者是同一个。
token必传用户登录返回的令牌。

成功时 result 里带:

字段形态说明
expiresstring令牌的下一次生死线(现在 + token 有效期),不是账号到期时间。
expires_tsint同上,秒级时间戳。
server_timeint服务器当前秒级时间戳。

可能的失败:400 参数错误 10214 登录状态已失效 10250 用户已到期 10252 用户已被冻结

心跳期间账号到期(10250)或被冻结(10252)都会在这里爆出来,和卡密侧一样要立刻锁功能。

用户退出

POST /v1/user/logout 账号侧主动下线,撤掉这个 token
参数要求说明
username必传账号名。
token必传要作废的令牌。

成功只有 code=0,没有 result——别去取返回体里的字段。

可能的失败:400 参数错误 10214 登录状态已失效

用户改密

POST /v1/user/password 改登录密码,改完该账号的在线令牌全部作废
参数要求说明
username必传账号名。
password必传当前登录密码,用来验身份。
new_password必传新密码,6-30 位。

成功只有 code=0,没有 result——别去取返回体里的字段。

可能的失败:400 参数错误 10243 用户登录密码错误

改密成功即下线:这个账号名下所有在线令牌全部作废,其它设备下一次心跳就是 10214,得重新登录。这是设计,不是 bug。

用户充值

POST /v1/user/recharge 拿一张新卡充进账号名下那张主卡
参数要求说明
username必传要充的账号。
card必传拿来烧的那张新卡:必须「未使用」,且多开上限与账号名下那张主卡一致。

成功只有 code=0,没有 result——别去取返回体里的字段。

可能的失败:400 参数错误 10240 卡密不存在或已被使用 10241 卡密已被使用 10244 不能对未使用的卡密进行充值 10252 用户已被冻结 10256 卡密多开配置不相等,不能充值

充进去的是账号名下那张主卡的额度:天数并进到期时间,次数与积分留在供体卡上、变成卡池里可烧的一张。账号侧的流水记在「以卡充卡记录」里,充值对象那一列写的是用户名。

用户解绑

POST /v1/user/unbind_device 账号侧换绑机器码
参数要求说明
username必传账号名。
token必传当前在线令牌。
device_id看情况要解掉的机器码,不传就用这个令牌所绑的那台。

成功只有 code=0,没有 result——别去取返回体里的字段。

可能的失败:400 参数错误 10216 软件未开启解绑设备功能 10255 该卡密不可解绑设备 10257 卡密剩余时间已不足以解绑扣除 10214 登录状态已失效

上限取账号上的多开数,超限回 10253(卡密侧同类情况回 10213)。解掉的是这台设备在账号侧与卡侧的全部绑定记录。

用户配置

GETPOST /v1/user/config 读写这个账号的配置串;这条只认签名不认用户凭证
参数要求说明
username必传账号名。
config看情况只在 POST 写的时候必传,最长 512 位。

成功时 result 里带:

字段形态说明
configstring只有 GET 读的时候有:这个账号的配置串原文,和用户登录返回里那个 config 同源。POST 写成功没有 result。

可能的失败:400 参数错误 10243 用户登录密码错误 10252 用户已被冻结

和卡密配置同样的信任模型:只认签名不认用户凭证,别放敏感数据。GET 成功不写用卡记录。

03软件组 · 3 条

软件配置

GET /v1/software/config 读软件级的配置(公告、当前版本、开关),不带用户身份

除公共参数外不带业务参数。

成功时 result 里带:

字段形态说明
configstring软件级配置串,控制台「配置软件 → 软件配置」里那一栏写的原文。

可能的失败:400 参数错误 10230 软件不存在,请检查 app_key 10212 卡密已被冻结

软件公告

GET /v1/software/notice 只读公告正文,适合启动页弹一次

除公共参数外不带业务参数。

成功时 result 里带:

字段形态说明
noticestring公告正文原文。没填就是空串,不是 null。

可能的失败:400 参数错误 10230 软件不存在,请检查 app_key 10212 卡密已被冻结

版本检查

GET /v1/software/latest_ver 比客户端上报的版本号和服务端当前版本,决定要不要升级
参数要求说明
version必传客户端当前版本号,写成 1.2 或 1.2.3 这种语义化形状,服务端按段比大小(1.10 比 1.9 新)。

成功时 result 里带:

字段形态说明
versionstring服务端当前版本号。
noticestring这条版本的更新说明;那条没写说明时回落到软件公告。
urlstring安装包下载地址;这条版本没填时回落到软件上的默认下载地址。
server_timeint服务器当前秒级时间戳。

可能的失败:400 参数错误 10304 已经是最新版本 10230 软件不存在,请检查 app_key 10212 卡密已被冻结

已是最新时回 10304,不是 code=0 加一个空结果——把 10304 当「没更新」处理,别弹错误窗口。当前版本取「配置软件 → 版本配置」里最新那条,历史版本只用来查说明与下载地址。成功不写用卡记录。

04试用组 · 2 条

试用登录

POST /v1/trial/login 不记名试用:设备码就是身份,按软件上的试用时长给一次
参数要求说明
device_id必传机器码,最长 120 位。试用不记名,这个值就是身份,换一台机器就是一个新的试用名额。

成功时 result 里带:

字段形态说明
expiresstring这次试用的到期时间:登录那一刻 + 软件上设的试用时长。
expires_tsint同上,秒级时间戳。
server_timeint服务器当前秒级时间戳。
hgint心跳间隔,单位秒。试用的心跳照这个值发。

可能的失败:400 参数错误 10401 软件未开启试用 10402 试用中,请勿重复登录 10403 已试用过了 10407 本周期试用次数已用完

试用没有令牌:device_id 就是身份,心跳带同一个设备码。正在试用中重复登录回 10402,那是「你已经在试用里」,改用心跳即可;软件开了「一台设备只能试用一次」时用过就永久 10403,否则按周期次数计,用完 10407。

试用心跳

POST /v1/trial/heartbeat 试用会话保活,把到期时间往前推
参数要求说明
device_id必传和试用登录时同一个机器码。

成功时 result 里带:

字段形态说明
expiresstring续后的试用到期时间(现在 + 试用时长)。
expires_tsint同上,秒级时间戳。
server_timeint服务器当前秒级时间戳。

可能的失败:400 参数错误 10401 软件未开启试用 10405 试用已到期 10406 本周期试用已到期

试用心跳把到期时间整段往前推,所以「一直开着程序」能一直用到周期上限;到期后回 10405/10406,该引导付费了。

05高级功能组 · 3 条

远程变量

GET /v1/af/remote_var 读开发者在控制台写好的一个值,改完立刻生效不用发版
参数要求说明
key必传变量名,最长 64 位,控制台「远程变量」里写好的那一个。注意参数名是 key,不是 name。
card看情况这个变量在控制台勾了「校验 token」时变为必传,和 token 成对。
token看情况同上。没勾校验 token 时带上也没坏处,服务端不看。

成功时 result 里带:

字段形态说明
valuestring变量的值原文。服务端不按类型解释它:数字、JSON、句子都是这一串,客户端自己解。

可能的失败:400 参数错误 10501 远程变量不存在 10214 登录状态已失效 10601 本月高级功能调用量已用完

参数名是 key。变量勾了「校验 token」时,card+token 变成必传,读的人必须是正在用的卡。成功不写用卡记录(这是热路径),但照样进高级功能的月调用量。

远程数据

GETPOST /v1/af/remote_data 客户端往服务端写一个小值(读回用 GET,增改删用 POST 的 action)
参数要求说明
key必传数据键,最长 64 位。GET 读回时只认这一个参数。
value看情况create/update 时必传(最长 256 位);delete 时不要带,带了也不参与删除,还会让签名多算一项。
action看情况只在 POST 用:create / update / delete 三选一。GET 读回不带。

成功时 result 里带:

字段形态说明
valuestringGET 读回时是存着的那串值;POST 写成功没有 result。

可能的失败:400 参数错误 10521 远程数据不存在 10522 远程数据 key 已存在 10601 本月高级功能调用量已用完

读写同一个 URL,靠 METHOD 区分:读用 GET+key,增改删用 POST+action。delete 不要带 value——带了会让签名多算一项、必报 10010(服务端虽然会抹掉空值,但你的签名是在抹掉之前算的)。写成功会留下一行用卡记录,写着 create · 键名。

远程函数

POST /v1/af/call_remote_func 让服务端算一道题:跑控制台里那条受限表达式,回 return 值
参数要求说明
func_name必传函数名,最长 64 位。注意参数名是 func_name,不是 name。
params看情况实参列表,必须是「JSON 数组序列化后的字符串」,例如 [3,4]。对象、嵌套数组、标量都拒(10542)。函数没声明形参可以不带。

成功时 result 里带:

字段形态说明
returnstring返回值先被 JSON 编码成字符串再放进信封,形如 {"return":7}。客户端要再解一次才是 7。整数不会带小数点(4 而不是 4.0)。

可能的失败:400 参数错误 10541 远程函数不存在 10542 参数(params)必须为 json 列表序列化后的 string 10543 远程函数调用错误 10601 本月高级功能调用量已用完

跑的是控制台里那条受限表达式,白名单解释器,不执行 PHP、读不到服务器文件。实参按声明的形参名按位置绑定。返回值是 JSON 字符串,要再解一次。撞 10543 时 message 里是具体原因(哪一步算错了)。

06五种卡与计时

客户端的调用方式对五种卡完全一样,差别只在服务端扣什么、expires 有没有值。类型在控制台生成卡密时选定。

类型扣量与计时
会员首次登录起算「天数+零头分钟」,之后每次登录只校验有没有到期,不扣量。到期时间回在 expires。适合订阅制。
单码带天数时等同会员;天数与分钟都是 0 就是一次一用:登录过一次即作废,第二次直接拒。适合发放体验码。
次数每成功登录一次扣 1 次,扣到 0 变「已用尽」。心跳不扣。没有到期概念,expires 是空串、expires_ts 是 0。
积分每成功登录一次扣掉软件上的「积分步长」(默认 1 分)。适合按量计费的工具。
充值额度是金额,不是时长、次数、积分:接口侧的登录与心跳都不消费它,也不算到期时间。它该在会员门户里兑换,不该出现在客户端的验证入口上。

时长按「天数+零头分钟」两个字段给,所以能出 30 天的卡,也能出 45 分钟的卡。时长从激活那一刻起算,不是从卖出去那天。

能做什么走哪条接口
续时长/v1/card/recharge:烧一张新卡,把它的天数、次数、积分并进正在用的那张。已过期的卡也能充,从现在往后加。
换绑设备/v1/card/unbind_device(带令牌)或 /v1/card/unbind_device/by_password(免令牌,用解绑密码)。
账号侧一个账号名下是一个卡池:主卡管到期时间,带次数或积分的卡排队被烧。/v1/user/recharge 往这个池子里补卡。

07会话、心跳与多开

这一节是「为什么用户说他还开着程序,却突然不能用了」的答案。

事项口径
令牌有效期软件上设的「token 有效期」(分钟)。登录签发、心跳整段续到 现在 + 有效期。超时没心跳就自动失效,回 10214,得重新登录。
心跳间隔登录响应里的 hg(秒,最少 60),来自软件的「心跳间隔」。照 hg 发,别写死 60 秒——心跳打太密只会把用卡记录刷满。
扣量时机次数卡与积分卡在登录时扣一次,心跳不扣。所以「挂着程序不动」不烧次数,重启一次算一次。
多开上限卡侧取这张卡的多开数(0 为不限),超限回 10213;账号侧取账号的多开数,超限回 10253。同一台设备重复登录复用令牌,不额外占名额。
顶号软件开了「顶号」时,每次登录把同一主体的其它在线令牌全撤掉——被顶掉那一台下一次心跳就是 10214。
解绑开关、次数上限、每次扣多少分钟,都设在软件上。扣时先判剩余时间够不够,不够直接拒(10257),不会把卡扣成负数。
改密/v1/user/password 成功即撤掉该账号全部在线令牌。这是设计:改完密码不该还有别的设备继续在线。
试用组没有令牌:device_id 自己就是身份,心跳带同一个设备码把到期时间往前推。软件按「一台设备一辈子一次」或「每周期给 N 次名额」两种模式之一计。

08高级功能与配额

三条接口解决「不改客户端也能调整」的三件事:读一个服务端写的值、存一个小值、让服务端算一道题。

能力用来干什么
远程变量开发者在控制台维护,客户端只读。改完立刻生效,不用发版。变量可以勾「校验 token」,勾了就只有正在用的卡读得到。
远程数据客户端自己写的键值对,读用 GET、增改删用 POST 的 action。适合存设备唯一的计数、开关、灰度标记。
远程函数客户端调名字,服务端跑控制台里那条受限表达式并回返回值。白名单解释器:不执行 PHP、读不到服务器文件、也不给租户代码留口子。

这一组按自然月计调用量,控制台给每个开发者设了每月上限,撞上限后整组回 10601。当前阶段只挡量、不收费。

三个读接口是热路径,成功时不写用卡记录(不然排障现场会被冲掉);失败照样记一行。想知道客户端读过什么,看概览的调用统计。

09返回码对照(44 个)

message 已经是给人看的话,直接显示即可;下表这一列告诉你该让用户做什么。码出自服务端那张表,和 message 永远一致。

codemessage客户端该怎么办
400参数错误调用方的问题:漏参数、参数超长、格式不合。按 message 或 errs 里那个字段名改代码,别提示给用户。
404资源未找到路径或方法不对。写动作只认 POST,用 GET 打过来就是这个。核对基址与 /v1/ 前缀。
500服务器错误,请稍后重试服务端异常。可以让用户重试一次,连续出现就把时间、软件名报给开发者,控制台的用卡记录里有那一行。
10010无效的签名,请检查签名算法签名不对。九成是三个口径之一:排的是整条 k=v 不是参数名、host 写成了常量或被换成了 IP、value 提前 URL 编码了。用本页的示例逐字对照。
10011签名已过期时间戳和服务器差了 60 秒以上。客户端别用本地时间,用登录返回的 server_time 校准。
10013时间戳大于当前服务器时间,请检查 timestamp时间戳比服务器还快,多半是用户把系统时间往前调了。同样用 server_time。
10014重复的 noncenonce 重复。每次请求换新随机串;空串或超过 36 位一律按重复处理。重试同一个请求必须重算 nonce。
10210卡密已过期卡到期了。给续费入口,别再自动重试。
10212卡密已被冻结卡被冻结(也可能是软件或开发者账号被停用)。原样把 message 显示给用户,引导联系客服。
10213卡密超过多开上限卡密侧多开超限。提示「已达最多可开机台数」,把当前台数显示出来。
10214登录状态已失效令牌没了:超时没心跳、被顶号、改密下线、解绑作废。回登录流程重取,别当业务失败弹错。
10215只能在首次登录绑定的设备上使用这张卡只允许首次登录绑定的设备。引导走解绑,或者联系开发者。
10216软件未开启解绑设备功能软件没开自助解绑。这条不该出现在界面上,把按钮置灰。
10217超出可绑定设备上限,请在已绑定设备上使用保留码,本平台不会返回它;同一件事用 10213 表达。SDK 里当多开超限处理即可。
10218卡密不可用这张卡现在不能用(不存在、不属于这个软件、已用尽)。让用户核对卡号,不要自动重试。
10230软件不存在,请检查 app_keyapp_key 认不出来:写错了,或软件在控制台被删了。这是接入期问题,与用户无关。
10240卡密不存在或已被使用卡密或账号查不到。用于充值与注册:核对用户抄的那一串。
10241卡密已被使用供体卡已经被用过了(慢了一步被别人抢掉,或这张卡本来就不是新的)。不能再拿它充。
10242账号已存在账号名重名。换个名字重试,或引导直接登录。
10243用户登录密码错误密码错,或用户名配置查不到账号。故意不区分两种,避免撞库探测。
10244不能对未使用的卡密进行充值目标卡还没激活,不能给它充值。先让用户登录一次。
10250用户已到期账号到期。给续费入口。
10252用户已被冻结账号被冻结。显示 message,引导联系客服。
10253用户超过多开上限账号侧多开超限。
10254解绑密码不正确解绑密码不正确。
10255该卡密不可解绑设备这张卡不给解绑(没绑过设备、或自助解绑次数用完)。把剩余次数显示出来。
10256卡密多开配置不相等,不能充值两张卡的多开上限不一致,不能充。让两张卡来自同一个卡类。
10257卡密剩余时间已不足以解绑扣除卡的剩余时间不够「解绑扣时」的,宁可拒绝也不把卡扣成负数。
10304已经是最新版本已经是最新版本。按「没得升级」处理,不要弹错误。
10401软件未开启试用软件没开试用。界面上不该有这个入口。
10402试用中,请勿重复登录这台设备正在试用中,别重复登录,改用心跳续时间。
10403已试用过了这台设备已经用掉过试用名额(软件开了「只能试用一次」)。引导购买。
10404请先登录需要先有登录态。
10405试用已到期试用到期。引导付费。
10406本周期试用已到期本周期试用到期,下个周期还能试(软件按周期给名额)。
10407本周期试用次数已用完本周期试用次数用完。
10501远程变量不存在远程变量不存在:名字写错,或开发者还没在控制台建。客户端该有一个兜底默认值。
10521远程数据不存在远程数据不存在(读一个没写过的 key,或 update/delete 一个不存在的 key)。
10522远程数据 key 已存在这个 key 已经存在,create 撞名了。改用 update。
10541远程函数不存在远程函数不存在。同样是客户端接错了名字。
10542参数(params)必须为 json 列表序列化后的 stringparams 不是 JSON 数组字符串。序列化成 [...] 再传。
10543远程函数调用错误函数跑挂了,message 里是具体原因。这条消息给开发者看,别原样弹给用户。
10601本月高级功能调用量已用完这个月的调用量到上限了,被拦的是软件不是用户。让开发者在控制台调额度。

10客户端落地建议

事项建议
调用时机启动必验一次;运行中按 hg 发心跳。长时间挂机不心跳的话令牌会自己过期,这是故意的。
超时与重试10 秒超时。网络类失败最多重试 2 次,而且每次重算 timestamp 与 nonce(照抄上一次的 sign 就是 10014)。业务失败(code≠0)不要自动重试。
离线宽限断网时可用上次成功的结果宽限一小段时间,但判断依据要存 server_time 与 expires,别只信本地时钟——用户改系统时间就能白嫖。
机器码取硬件指纹做哈希,长度固定、重装系统尽量不变。不要用纯 MAC,虚拟机一改就换了一台「新设备」。
本地存储令牌和卡密存用户目录,别存安装目录(同机多用户会串号)。日志里不要写 sign,也不要写 app_secret。
错误处理message 原样显示,后面补一句你的客服入口。10010/10230 这类接入期错误写进本地日志就行,别弹给用户。
首次激活时长从第一次成功登录起算。卖卡前提醒「装好再激活」,能省掉一半退款。
版本升级/v1/software/latest_ver 用 10304 表示「没得升」,不是错误。客户端上报的版本号要随发布递进。
最小可用流程(伪代码)
启动:
  本地卡密 = 读配置()
  没有卡密 → 输入卡密 → 调 /v1/card/login(card, device_id)
  code != 0 → 按返回码分支:
      10218 / 10240 → 让用户核对卡号,不要自动重试
      10215 / 10213 → 走解绑或联系客服
      10212 / 10210 → 原样显示 message,给续费或客服入口
      10010 附近    → 这是接入代码的问题,写进本地日志,别弹给用户
  code == 0 → 存下 result.token / result.expires,用 result.server_time 校准本地时钟
             用 result.hg 起一个心跳定时器
运行中:
  每 hg 秒 → /v1/card/heartbeat(card, token)
      0       → 继续跑
      10214   → 令牌没了,回登录流程
      其它    → 立即锁功能并显示 message
  网络失败 → 最多重试 2 次;期间可用上次成功结果宽限,但宽限不超过 expires
退出/关机:
  /v1/card/logout(card, token) → 无论成败都清本地令牌

用户报「验证失败」时开发者怎么查

控制台 用卡记录 是排障第一现场:按卡密、账号、机器码或 IP 查那一次调用,写明返回码与人话原因。要区分的是——

  • 成功的调用由接口按端点记账:写动作(登录、心跳、注册、充值、解绑、写配置、写远程数据)成功都留一行。
  • 失败的不管哪条都留一行,包括签名没过、软件被停用。
  • 成功时不写这张表的有七条纯读接口:软件配置、软件公告、版本检查、远程变量、远程函数,以及只认签名的 GET 卡密配置、GET 用户配置。它们的量只进概览的调用统计。
对完了协议还想省事:控制台里有现成的软件与卡密,先在这里把卡发出来、把开关设好,再用本页的示例接客户端——顺序反过来的话,你会对着一堆 10218 猜是哪步错了。