开发文档
设备标识服务对外开放。客户端采集后换取 token,业务后端用 token 换取设备标识;原始特征不经过业务系统。
快速开始
一次完整的接入分为三段:客户端取 token,前端把 token 交给自家后端,后端带签名换取标识。
1. 引入 SDK
<script src="https://fp.mima-code.com/sdk/core.js"></script>2. 初始化并获取 token
const app = AppSDK.init({
appKey: '<APP_KEY>',
server: 'https://fp.mima-code.com',
timeout: 6000,
});
const { code, token } = await app.getTokenAsync();
if (code === 200 || code === 201) {
await post('/api/device-token', { token }); // 交给自家后端
}3. 服务端换取标识
POST /sdk/v1/check
{
"app_id": "<APP_ID>",
"timestamp": 1730000000000,
"nonce": "<32 位随机串>",
"version": "1.0",
"sign": "<md5>",
"token": "<客户端 token>"
}返回的
device_token 可直接放进业务请求头,格式与现有 fp 头一致。初始化
初始化只使用公开的应用标识,不携带任何密钥。真正的鉴权在服务端由来源白名单与配额承担。
AppSDK.init({
appKey: '<APP_KEY>', // 必填,公开标识
server: 'https://fp.mima-code.com', // 可选,默认公网地址
timeout: 6000, // 可选,采集上报超时(毫秒)
callback(code, msg) {} // 可选,初始化回调
})| code | 含义 |
|---|---|
200 | 初始化完成 |
109 | 重复初始化,沿用已有实例 |
199 | 应用标识无效或已停用 |
获取 token
同步接口返回当前状态;异步接口每次都会重新采集并上报,属于耗时操作,不要在页面高频调用。
// 同步
const { code, token } = app.getToken();
// 异步
app.getTokenAsync().then((result) => {
if (result.code === 200 || result.code === 201) {
// result.token 交给业务后端
}
});| code | 状态 | 处理 |
|---|---|---|
200 | 正常模式,数据已上报,token 长度固定 | 直接送检 |
201 | 离线模式,上报失败,token 为本地加密载荷 | 同样送检,效果一致 |
202 | 获取异常,token 为空串 | 本次不送检,稍后重试 |
同步调用时若初始化回调尚未完成,会返回
202。需要稳定结果请使用异步接口。token 形态
服务端对两种 token 的处理方式不同,接入方不需要区分。
| 形态 | 触发 | 长度 | 服务端处理 |
|---|---|---|---|
| 短 token | 上报成功 | 固定 40 字符 | 从缓存读取计算结果 |
| 长 token | 上报失败 | 4–8KB | 解密后现场计算 |
短 token 建议在 30 分钟内送检,超时后返回 5512。离线 token 内含采集数据,传输与存储都应按敏感数据处理。
检测接口
POST https://fp.mima-code.com/sdk/v1/check
Content-Type: application/json公共参数与业务参数放在同一个 JSON 请求体里。
| 参数 | 必填 | 说明 |
|---|---|---|
app_id | 是 | 接入方标识 |
timestamp | 是 | 13 位毫秒时间戳,超过 5 分钟拒绝 |
nonce | 是 | 32 位随机串,服务端去重防重放 |
version | 是 | 接口版本,当前为 1.0 |
sign | 是 | 签名,见下节 |
token | 是 | 客户端获取的凭证 |
account | 否 | 业务方用户 ID |
ip | 否 | 用户 IP,缺省取请求来源 |
签名规则
所有参数(不含 sign)按参数名 ASCII 升序排序,依次拼接键与值,末尾追加 app_secret,整体做 MD5,取 32 位小写十六进制。
function sign(params, secret) {
const keys = Object.keys(params).filter(k => k !== 'sign').sort();
let raw = '';
for (const k of keys) raw += k + params[k];
raw += secret;
return md5(raw).toLowerCase();
}空值不要省略成
null,统一传空字符串,否则前后端签名会不一致。返回字段
{
"code": 200,
"msg": "ok",
"data": {
"device_id": "RZBW7ZSRJ7ID3SCVY2PIICNKEF654JUB",
"device_token": "RZBW7ZSRJ7ID3SCVY2PIICNKEF654JUB|v1|<md5>",
"risk_level": 0,
"hit_infos": [],
"coverage": 100,
"pending": false,
"first_seen": false,
"task_id": "trace-id",
"cached": true
}
}| 字段 | 说明 |
|---|---|
device_id | 32 位 Base32 设备标识;pending 时为空串 |
device_token | 可直接放入业务请求头的完整串 |
risk_level | 0 正常 / 1 低 / 2 中 / 3 高 / 4 可信 |
coverage | 核心特征覆盖率,100 表示采集完整 |
pending | 为 true 时应保留本地已有标识,不要覆盖 |
cached | 同一 token 重复送检时为 true |
返回码
| code | 含义 | 处理建议 |
|---|---|---|
200 | 正常 | — |
400 | 请求参数不合法 | 检查必填字段与类型 |
411 | 请求频率过高 | 降频后重试 |
5501 | 时间戳过期 | 校准服务器时间 |
5502 | 参数缺失或格式错误 | 对照参数表检查 |
5505 | nonce 格式错误或重复 | 每次请求生成新的 32 位随机串 |
5506 | 应用标识无效 | 确认 app_id |
5507 | token 为空 | 检查客户端是否成功获取 token |
5508 | 签名错误 | 核对排序与拼接规则 |
5509 | 应用已停用 | 联系管理员 |
5510 | 配额或调用次数超限 | 联系管理员调整配额 |
5511 | 重复请求 | 本服务默认返回 cached=true,不报错 |
5512 | token 非法或过期 | 重新获取 token 后重试一次 |
5514 | 处理超时 | 可按退避策略重试 |
5515 | 处理失败 | 重试仍失败请携带 task_id 反馈 |
5521 | 可重试失败 | 稍后重试 |
业务侧约定
- 返回的
device_token按现有方式加密存储,请求时解密放入请求头;存储层无需改动。 pending=true时保留本地已有标识,不要覆盖。- token 获取失败(
202)时本次不送检,不影响业务主流程。 - 服务不可用时沿用本地已有标识,避免阻塞下单、登录等主链路。
存量标识与旧值不同时,切换初期会出现同一设备换 ID 的情况,建议先在单一端灰度。