# 接口规范

# 请仔细阅读

  • 请严格按照接口文档开发,有问题请联系工作人员,或下载实例查看学习代码,接口支持base64加密,AES加密,DES加密,RC4加密,接口验签,IP限制等..在登录土豆API后APP设置里面均可配置,严禁用于非法用途,违规者将给予封号惩罚,一切解释权归 土豆API 所有

# 接口验证方式

  • 接口支持post,get方式请求(未标注是哪个接口表示都可以),任何接口请求必须携带askKey(在APP控制台可查看,详见下方请求头参数说明),部分接口需要携带apiUserToken(用户登录后获取的令牌,详见下方请求头参数说明),接口支持form-data,x-www-form-urlencoded,json等格式请求,如果开启加密时,不需要对key进行加密,需要对value进行对应加密,使用加密时,建议不要使用get请求,否则会出现加密后的数据被转码而导致服务端解密失败

# 接口验签

  • 接口验签建议开启,可防止非法抓包伪造请求。签名使用北京时间毫秒时间戳。
  • 请求验签:time、sign(及 V3 的 nonce)放在请求头;参与签名的业务参数为本次请求体字段(开启加密时,对明文 value 参与计算后再加密传输,以控制台配置为准)。
  • 响应验签:开启验签后,响应 JSON 内会返回 time、sign(及 V3 的 nonce),客户端可用同样规则对响应字段自行校验,保证回包未被篡改。

# 验签规则V1

# 规则说明

验签字符串拼接规则:验签秘钥 + 时间戳,通过 MD5 算法加密(32 位小写)。

  • 请求头需携带:time(当前系统时间戳,毫秒)、sign(MD5 结果)。

# 验签算法

sign = MD5(验签秘钥 + 当前系统时间戳(毫秒))
time = 当前系统时间戳(毫秒)
1
2

javascript demo

/**
 * 
 * @param {*} signSecretKey 验签秘钥
 * @param {*} timestamp 时间戳
 * @returns 
 */
function signV1(signSecretKey, timestamp) {
    return md5(signSecretKey + timestamp)
}
1
2
3
4
5
6
7
8
9

java demo

    /**
     * @param time 时间戳
     * @param signSecretKey 验签秘钥
     * @return
     */
    public static String signApiV1Sign(String time,String signSecretKey){
        String s = DigestUtils.md5DigestAsHex((signSecretKey+time).getBytes());
        return s;
    }

1
2
3
4
5
6
7
8
9
10

lua demo

   local md5 = require("md5")
   
   -- 定义函数signApiV1Sign
   function signApiV1Sign(time, signSecretKey)
       -- 将验签秘钥和时间戳拼接成字符串
       local strToHash = signSecretKey.. time
       -- 计算MD5并返回十六进制格式的结果
       local result = md5.sumhexa(strToHash)
       return result
   end
1
2
3
4
5
6
7
8
9
10

# 验签规则V2

# 规则说明

在 V1 基础上增加业务参数参与签名,MD5(32 位小写)。

  • 请求头需携带:time、sign。
  • 下方示例以响应验签说明(对响应 JSON 字段签名);请求验签步骤相同,将 param 换成请求体字段即可(如云函数的 name、params)。

# 验签步骤

  1. 过滤参数:移除值为 null(nil)、undefined 或空字符串的参数。
  2. 排序参数:按键名(key)字典升序排序。
  3. 对象/数组取值:若值为对象或数组,序列化为 JSON 字符串后再拼接(与 Java Demo 一致,保留 null 字段策略以服务端为准)。
  4. 拼接字符串:&key1=value1&key2=value2&...&signSecretKey&timestamp(每个参数前加 &,末尾追加秘钥与时间戳)。
  5. 生成签名:sign = MD5(拼接结果)(32 位小写)。
time = 当前系统时间戳(毫秒)
sign = MD5( &k1=v1&k2=v2&...&验签秘钥&time )
1
2
// 返回500例子
signSecretKey:1eff25397db71c4af5019e72c31d6737
timestamp:1752131260250
最终拼接参数:&code=500&message=卡密错误&status=false&1eff25397db71c4af5019e72c31d6737&1752131260250
签名:e7f1dded50c64e82d15f98ec33498681

{
	"code": "500",
	"data": null,
	"message": "卡密错误",
	"nonce": null,
	"pc": null,
	"sign": "e7f1dded50c64e82d15f98ec33498681",
	"status": false,
	"time": "1752131260250"
}

// 返回200例子
signSecretKey:1eff25397db71c4af5019e72c31d6737
timestamp:1752133734750
最终拼接参数:&code=200&data={"verify":true,"remark":null,"type":0,"exTime":"2025-07-11 15:04:07"}&status=true&1eff25397db71c4af5019e72c31d6737&1752133734750
签名:77e2e4b5fb0f156e20a278d4d31bfe7c

{
	"code": "200",
	"data": {
		"verify": true,
		"remark": null,
		"type": 0,
		"exTime": "2025-07-11 15:04:07"
	},
	"message": "",
	"nonce": null,
	"pc": null,
	"sign": "77e2e4b5fb0f156e20a278d4d31bfe7c",
	"status": true,
	"time": "1752133734750"
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

javascript demo

/**
 * 生成V2版本签名
 * @param {string} signSecretKey - 验签秘钥
 * @param {string} timestamp - 时间戳
 * @param {Object} data - 请求数据对象
 * @returns {string} - 生成的MD5签名
 */
function signV2(signSecretKey, timestamp, data) {
    // 1. 过滤空值参数
    var filteredData = {};
    for (var key in data) {
        if (data.hasOwnProperty(key)) {
            var value = data[key];
            if (value !== null && value !== undefined && value !== '') {
                filteredData[key] = value;
            }
        }
    }
    
    // 2. 获取排序后的键名数组
    var sortedKeys = [];
    for (var key in filteredData) {
        if (filteredData.hasOwnProperty(key)) {
            sortedKeys.push(key);
        }
    }
    sortedKeys.sort();
    
    // 3. 拼接参数字符串(每个参数前加&)
    var resultSign = '';
    for (var i = 0; i < sortedKeys.length; i++) {
        var key = sortedKeys[i];
        resultSign = resultSign + '&' + key + '=' + filteredData[key];
    }
    
    // 4. 添加秘钥和时间戳
    resultSign = resultSign + '&' + signSecretKey + '&' + timestamp;
    
    // 5. 计算MD5签名(假设md5函数已全局可用)
    return md5(resultSign);
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

java demo

    /**
     * @param time 时间戳
     * @param signSecretKey 验签秘钥
     * @param param 数据
     * @return
     */
    public static String signApiV2Sign(String time, String signSecretKey, Map<String, String> param) {
        TreeMap<String, Object> treeMap = new TreeMap<>(param);
        StringBuilder sb = new StringBuilder();

        for (Map.Entry<String, Object> entry : treeMap.entrySet()) {
            Object value = entry.getValue();
            if (value != null && !"".equals(value)) {
                String jsonValue;
                if (value instanceof Map || value instanceof List || value instanceof JSONObject) {
                    jsonValue = JSONObject.toJSONString(value, SerializerFeature.WriteMapNullValue);
                } else {
                    jsonValue = value.toString();
                }

                sb.append("&").append(entry.getKey()).append("=").append(jsonValue);
            }
        }

        sb.append("&").append(signSecretKey).append("&").append(time);
        return DigestUtils.md5DigestAsHex(sb.toString().getBytes());
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

lua demo


function signV2(signSecretKey, timestamp, data)
    local map = {}
    -- 将表数据转换为类似Map的结构,添加空值过滤
    for key, value in pairs(data) do
        if value ~= nil and value ~= "" then -- 过滤nil和空字符串
            map[key] = value
        end
    end
    
    -- 获取所有的键并进行排序
    local sortedKeys = {}
    for key in pairs(map) do
        table.insert(sortedKeys, key)
    end
    table.sort(sortedKeys)

    local resultSign = ""
    -- 拼接字符串
    for _, key in ipairs(sortedKeys) do
        resultSign = resultSign .. "&" .. key .. "=" .. map[key]
    end
    resultSign = resultSign .. "&" .. signSecretKey .. "&" .. timestamp
    
    -- 计算md5并返回结果
    return md5.sumhexa(resultSign)
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 验签规则V3

# 规则说明

在 V2 基础上增加随机字符串 nonce,用于防重放。

  • 请求头需携带:time、sign、nonce。
  • nonce 在服务端约 6 分钟内不可重复使用(Redis 防重放 TTL = 6 分钟;与时间戳允许窗口一致:服务端校验 time 相对当前时间上下各约 3 分钟)。
  • 每次请求应重新生成 nonce;同一 nonce 在 TTL 内再次使用会验签失败。
  • 响应开启验签时同样返回 time、sign、nonce,客户端可按相同规则校验回包(响应侧 nonce 由服务端每次新生成)。

# 验签步骤

  1. 过滤参数:同 V2 步骤 1。
  2. 排序参数:同 V2 步骤 2。
  3. 对象/数组:同 V2 步骤 3。
  4. 拼接字符串:&key1=value1&key2=value2&...&signSecretKey&time&nonce(相对 V2 多追加 &nonce)。
  5. 生成签名:sign = MD5(拼接结果)(32 位小写)。
sign = MD5( &k1=v1&k2=v2&...&验签秘钥&time&nonce )
1

sign、time、nonce 不参与中间参数拼接,只作为末尾追加;其它非空字段(含有值的 reqSign)按 key 字典序参与拼接。对象 data 按服务端 JSON 序列化结果原样拼接,不要自行重排内部字段。

// 返回200例子(卡密验证)
signSecretKey:1eff25397db71c4af5019e72c31d6737
timestamp:1789373132542
nonce:02bb8ec6-eef0-460a-9b1d-fc66e4825e8a
最终拼接参数:&code=200&data={"carMyToken":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJDQVJEX01ZX0hFQVJUIjoiMTI1OTM4NzY0MjUxNTY0NDQxNiIsIkVYUF9USU1FX1JBTkQiOiJrY2h0aW4t5LiD5pyILmY2MWU2NTZlZjM1YjJlMzAiLCJFWFBfVElNRV8iOjE3ODk0NTk0MzIzMjIsIlRJTUVfU0FNUF8iOjE3ODkzNzMxMzIzNDd9.b_OlXFOtka37DQCaFTZYd5GBb5bpbGhKvAFmai9edPQ","cardStr":"GSKH7Q","verify":true,"exTime":"2026-09-15 16:03:52","type":0}&reqSign=18ae870b37388c1bbe67a0143cc67d25&status=true&1eff25397db71c4af5019e72c31d6737&1789373132542&02bb8ec6-eef0-460a-9b1d-fc66e4825e8a
签名:3154227f84bef85b8c4ab609a5173452

{
	"code": "200",
	"data": {
		"carMyToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJDQVJEX01ZX0hFQVJUIjoiMTI1OTM4NzY0MjUxNTY0NDQxNiIsIkVYUF9USU1FX1JBTkQiOiJrY2h0aW4t5LiD5pyILmY2MWU2NTZlZjM1YjJlMzAiLCJFWFBfVElNRV8iOjE3ODk0NTk0MzIzMjIsIlRJTUVfU0FNUF8iOjE3ODkzNzMxMzIzNDd9.b_OlXFOtka37DQCaFTZYd5GBb5bpbGhKvAFmai9edPQ",
		"cardStr": "GSKH7Q",
		"verify": true,
		"exTime": "2026-09-15 16:03:52",
		"type": 0
	},
	"message": "",
	"nonce": "02bb8ec6-eef0-460a-9b1d-fc66e4825e8a",
	"pc": null,
	"reqSign": "18ae870b37388c1bbe67a0143cc67d25",
	"sign": "3154227f84bef85b8c4ab609a5173452",
	"status": true,
	"time": "1789373132542"
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

javascript demo

/**
 * 生成V3签名
 * @param {string} time - 时间戳
 * @param {string} signSecretKey - 验签秘钥
 * @param {Object} param - 请求参数对象
 * @param {string} nonce - 随机字符串
 * @returns {string} - MD5签名结果
 */
function signApiV3Sign(time, signSecretKey, param, nonce) {
    // 1. 过滤空值参数
    var filteredParam = {};
    for (var key in param) {
        if (param.hasOwnProperty(key)) {
            var value = param[key];
            if (value !== null && value !== undefined && value !== '') {
                filteredParam[key] = value;
            }
        }
    }
    // 2. 按键名排序
    var sortedKeys = [];
    for (var key in filteredParam) {
        if (filteredParam.hasOwnProperty(key)) {
            sortedKeys.push(key);
        }
    }
    sortedKeys.sort();
    
    // 3. 拼接参数字符串
    var sb = '';
    for (var i = 0; i < sortedKeys.length; i++) {
        var key = sortedKeys[i];
        sb = sb + '&' + key + '=' + filteredParam[key];
    }
    
    // 4. 添加秘钥、时间戳和随机字符串
    sb = sb + '&' + signSecretKey + '&' + time + '&' + nonce;
    
    // 5. 计算MD5(假设md5函数已全局可用)
    return md5(sb);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

java demo

    /**
     * @param time 时间戳
     * @param signSecretKey 验签秘钥
     * @param param 数据
     * @param nonce 随机字符串
     * @return
     */
   public static String signApiV3Sign(String time, String signSecretKey, Map<String, String> param, String nonce) {
        // 按照字典顺序排序
        TreeMap<String, Object> treeMap = new TreeMap<>(param);
        StringBuilder sb = new StringBuilder();

        for (Map.Entry<String, Object> entry : treeMap.entrySet()) {
            Object value = entry.getValue();

            // 忽略 null 和空字符串的 key
            if (value != null && !"".equals(value)) {
                String jsonValue;

                // 如果是 Map/List/JSONObject 等结构,使用 JSON 序列化,并保留 null
                if (value instanceof Map || value instanceof List) {
                    jsonValue = JSONObject.toJSONString(value, SerializerFeature.WriteMapNullValue);
                } else {
                    jsonValue = value.toString();
                }

                sb.append("&").append(entry.getKey()).append("=").append(jsonValue);
            }
        }

        sb.append("&").append(signSecretKey).append("&").append(time).append("&").append(nonce);
        return DigestUtils.md5DigestAsHex(sb.toString().getBytes());
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

lua demo


function signApiV3Sign(time, signSecretKey, param, nonce)
    -- 1. 过滤空值参数
    local filteredParam = {}
    for key, value in pairs(param) do
        if value ~= nil and value ~= "" then
            filteredParam[key] = value
        end
    end
    
    -- 2. 按键名排序
    local sortedKeys = {}
    for key in pairs(filteredParam) do
        table.insert(sortedKeys, key)
    end
    table.sort(sortedKeys)
    
    -- 3. 拼接参数字符串
    local sb = ""
    for _, key in ipairs(sortedKeys) do
        sb = sb .. "&" .. key .. "=" .. filteredParam[key]
    end
    
    -- 4. 添加秘钥、时间戳和随机字符串
    sb = sb .. "&" .. signSecretKey .. "&" .. time .. "&" .. nonce
    
    -- 5. 计算MD5
    return md5.sumhexa(sb)
end

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# 加解密规则

# 加密类型一览

在 APP 控制台配置请求/响应加密类型;SDK 中 requestEncryptType / responseEncryptType 取值如下:

值 类型 说明
0 不加密 明文传输
1 Base64 对 value / 响应体做 Base64
2 AES/ECB/PKCS5Padding 见下方 AES 明细(推荐;云函数 aesEncrypt / aesDecrypt 与此一致)
3 DES/ECB/PKCS7 密钥截取/补齐至 8 字节
4 AES/ECB/NoPadding 密钥固定 16 字节,明文需自行补齐到 16 的倍数
5 RC4 流加密

# AES/ECB/PKCS5Padding(类型 2)明细

与云函数 potato.aesEncrypt / potato.aesDecrypt 同一实现约定:

项 值
算法 / 模式 / 填充 AES/ECB/PKCS5Padding(AES 块长 16;Java PKCS5Padding 对 AES 即 PKCS7)
IV 无(ECB)
字符编码 明文与密钥均为 UTF-8
密钥处理 不足 32 字节时右侧用字符 0 补齐至 32(AES-256);≥32 按原串
密文格式 Base64(无换行)
数据填充 由 PKCS5Padding 自动完成,无需手动 pad

# 请求 / 响应加密形态

请求加密,请求加密规范,请求内容为半加密,需要以key-加密(value),方式请求,其中加密(value)指的是加密算法加密后的值,其中key不需要加密,土豆API给予响应内容,为全加密

正确加密请求实例 实例接口/api/login

请求
{
    "userNumber":"MTIzNDc=",
    "passWord":"MTIzNDU2"
}
响应内容:
eyJjb2RlIjoiMjAwIiwibWVzc2FnZSI6IiIsInN0YXR1cyI6dHJ1ZSwiZGF0YSI6eyJhcGlVc2VyVG9rZW4iOiJleUowZVhBaU9pSktWMVFpTENKaGJHY2lPaUpJVXpJMU5pSjkuZXlKamRYSnlaVzUwSWpveE5qWTBPVE0wTlRrNU9UTXpMQ0pWVTBWU1ZHOXJaVzVMWlhraU9pSTJPVEUwTlRJNE1UWTRNRFU0TnprNE1EaGZNVEl6TkRjaUxDSjFjMlZ5U1dRaU9qWTVNVFExTWpneE5qZ3dOVGczT1Rnd09Dd2lkWE5sY2s1MWJXSmxjaUk2SWpFeU16UTNJbjAuR0VJMldjWF85TjhuT21fZURxelQyclJzOUJfeXAwcms3aTdHVGFXS3dUQSJ9LCJwYyI6bnVsbCwidGltZSI6bnVsbCwic2luIjpudWxsfQ==
1
2
3
4
5
6
7

错误加密请求实例 实例接口/api/login

请求
{
    "userNumber":"12347",
    "passWord":"123456"
}
响应内容:
eyJjb2RlIjoiNTAwIiwibWVzc2FnZSI6IuaVsOaNruino+WvhuWksei0pSIsInN0YXR1cyI6ZmFsc2V9
1
2
3
4
5
6
7

# 注意事项

注意,后端http状态码为500时,一般表示为请求数据解析失败或者请求数据解密失败,或者系统错误, 如果http状态码为500,当然解密失败情况只会出现在开发阶段,正式阶段出现加解密异常则为开发者更换了秘钥导致,返回报文为明文数据对象, 开发者可以直接获取数据,如果是业务类型错误,比如用户余额不足,返回的http状态码为200, 错误信息为密文,开发者使用对应解密方法解密数据即可, 凡是上传文件的接口均不支持加解密(不包含base64),后续优化该功能

解密失败请求实例 实例接口api/updateUserBalance 请求:

{
    "type":12,
    "amount":10
}
1
2
3
4

响应内容: 此时http状态码为500,不是返回数据码,这点请注意

数据解密失败报文:
{
    "timestamp": 1676518671836,
    "status": 500,
    "error": "Internal Server Error",
    "message": "数据解密失败",
    "path": "/api/updateUserBalance"
}
数据解析失败报文:
{
    "timestamp": 1676519332339,
    "status": 500,
    "error": "Internal Server Error",
    "message": "内容解析失败,请检查提交信息是否正确",
    "path": "/api/updateUserBalance"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 请求地址

# 生产环境地址(baseUrl)

  • https://api.potatocloud.cn

# 请求URL格式

  • {baseUrl}/api/test
  • 举例https://api.potatocloud.cn/api/test

# 请求方式

  • POST | GET | DELETE | PUT

# 请求头参数

参数名 必选 类型 说明
Content-Type 是 string 此参数必传,当请求数据非json时此参数传对应格式如(multipartform-data,application/x-www-form-urlencoded,等)是json请传application/json
askKey 是 string 此参数必传,验证app的令牌
sign 否 string 此参数如果在app设置开启了验签是必传,规则上方有描述
time 否 string 当开启验签时,此参数为必传
apiUserToken 否 string 除了部分不验证用户令牌的接口以外此参数为必传(注册/登录/发送验证码)不是必传,其余都是必传

# 请求参数

接口参数每个接口文档有详细的参数描述

# 返回示例

{
	"code": "200",
	"message": "",
	"status": true,
	"data": null,
	"pc": null,
	"time": null,
	"sign": null,
    "nonce": null
}

1
2
3
4
5
6
7
8
9
10
11

# 返回参数说明

参数名 类型 说明
code string 200请求成功,403无权限或令牌过期,返回该状态码建议跳转登录界面让用户进行重新登录500为错误,message会携带错误信息返回,可以将该详细提示给用户
message string 错误信息,成功是不会返回错误信息
status boolean 状态,true 为成功,false 为失败
data json 返回数据 格式为json,如果需要返回数据时候,该参数会携带数据返回
pc json 分页对象,携带分页信息,如果不是列表查询该参数为null
time String 时间戳(开启验签时返回不为空)
sign String 签名(开启验签时返回不为空)
reqSign String 请求签名
nonce String 随机字符串(开启验签V3时返回不为空)
# 备注

# OCR识别

OCR支持中,英文,以及数字,其他字库联系官方添加 如果OCR识别不支持PNG后缀直接改成jpg这种类型文件,如果出现该情况,请保存新的图像上传识别,由于字库是使用默认字库,如需识别特殊字符可以联系官方训练字库,

lastUpdate: 9/14/2026, 4:11:43 PM