1. 接口说明
提供两类接口:
- OPENAPI
- 基于 WebSocket 提供实时事件通知的事件中心
2. 对接方式
2.1. OPENAPI
2.1.1. URL地址(POST方式,JSON编码)
https://api.xiaoduoai.com/gate/open
2.1.2. 公共参数
调用任何一个API都必须传入的参数,目前支持的公共参数有:
参数名称 | 参数类型 | 是否必须 | 参数描述 | 示例 |
method | string | 是 | 具体API接口名称 | xd.gate.demo |
appkey | string | 是 | 配给企业的AppKey | key123456 |
ts | int | 是 | 10位的秒级时间戳 | 1668654293 |
sig | string | 是 | 参数签名结果,签名算法参照下面的介绍 | |
body | map[string]interface{} | 是 | 业务参数 |
示例:
curl -H "content-type: application/json" https://api.xiaoduoai.com/gate/open -d '
{
"method": "'${method}'",
"body": {
"key": "value"
},
"appkey": "key123456",
"ts": '${ts}',
"sig": "'${sig}'"
}'2.1.3. 签名
为了防止API调用过程中被篡改,调用任何一个API都需要携带签名,服务端会根据请求参数,对签名进行验证,签名不合法的请求将会被拒绝。
签名方式:将method、ts、app_secret拼接得到的字符串MD5取16进制小写32位字符串
示例:
参数method=xd.gate.demo,ts=1668654293,app_secret=secret456
拼接后字符串 xd.gate.demo1668654293secret456
最终得到的签名为:159235348d54ea0c0d42bc8c849f75ed
2.1.4. demo.sh
#!/bin/bash
method="xd.gate.demo"
ts=`date +%s`
sig=`echo -n "xd.gate.demo1668654293secret456"|md5sum|cut -d ' ' -f1`
curl -H "content-type: application/json" https://api.xiaoduoai.com/gate/open -d '
{
"method": "xd.gate.demo",
"body": {
"body_key": "body_val"
},
"appkey": "key123",
"ts": 1668664279,
"sig": "b09914f8721c16617994e0ac9c9c45fc"
}'
2.1.5. 接口调用频率限制
调用API接口每分钟允许请求的次数,限制说明详情如下
API接口 | 接口名称 | 并发量 & 频率/分钟 |
xd.ticket.create | 创建工单 | 1 & 60次/分钟 |
xd.ticket.update | 更新工单 | 1 & 60次/分钟 |
xd.ticket.list | 查询工单 | 1 & 60次/分钟 |
xd.ticket.batch_pay_list | 查询批量打款工单 | 1 & 60次/分钟 |
2.2. 使用WebSocket接入
2.2.1. 连接到Gateway
ws://api.xiaoduoai.com/event/open
2.2.2. 鉴权连接
建立 websocket 连接之后,需要进行鉴权
参数名称 | 参数类型 | 是否必须 | 参数描述 | 示例 |
sub_event_ids | []string | 是 | 订阅消息的接口名称 | xd.gate.demo |
app_key | string | 是 | 配给企业的AppKey | key123456 |
ts | int | 是 | 10位的秒级时间戳 | 1668654293 |
sig | string | 是 | 参数签名结果,签名算法参照下面的介绍 |
示例:
ws.onopen = function(evt)
{
const ts = parseInt(Date.now() / 1000)
const auth = {
"sub_event_ids" : ["xd.event.demo"],
"appkey": "key123",
"ts": ts
};
auth.sig = md5("xd.event.demo"+ts+"secret456")
const data = JSON.stringify(auth);
ws.send(data)
};2.2.3. 签名
签名方式:将sub_event_ids(用,连接)、ts、app_secret拼接得到的字符串MD5取16进制小写32位字符串
示例:
参数 sub_event_ids=["xd.gate.demo","xd.gate.demo2"],ts=1668654293,app_secret=secret456
拼接后字符串xd.gate.demo,xd.gate.demo21668654293secret456
最终得到的md5签名为:b0ce12faf07c250aa799a243ad4a9de5
2.2.4. ws_client.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>打开F12开发者工具查看浏览器控制台标准输出</title>
</head>
<body>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/blueimp-md5/2.10.0/js/md5.min.js"></script>
<script type="text/javascript">
window.onload = function(){
// 后端针对每个appkey仅支持一个并发接入,
// 后接入的会踢掉已建立连接的
const ws = new WebSocket("ws://api.xiaoduoai.com/event/open");
// 第一个包,连接鉴权
ws.onopen = function(evt)
{
const ts = parseInt(Date.now() / 1000)
const auth = {
"sub_event_ids" : ["xd.event.demo"],
"appkey": "key123",
"ts": ts
};
auth.sig = md5("xd.event.demo"+ts+"secret456")
const data = JSON.stringify(auth);
ws.send(data)
window.setInterval(function () {
sendPing(ws);
}, 20000);
};
// 发送心跳,后端会立即返回event_id = ping的数据
const sendPing = function(ws)
{
const ping = {
"event_id" : "ping"
}
const data = JSON.stringify(ping);
ws.send(data)
}
ws.onmessage = function(evt)
{
console.log("message " + evt.data)
const ret = JSON.parse(evt.data)
console.log("message " + ret["event_id"])
if (ret["event_id"] == "ping") {
} else {
console.log("message " + ret["data"].message)
}
};
ws.onclose = function(evt)
{
// 关闭 websocket
console.log("closed " + evt)
};
ws.onerror = function(evt)
{
// 异常退出,需要处理断线重连
console.log("error " + evt)
};
}
</script>
</html>注意事项
- 连接
1min内没有发消息会自动断开 - 同一个
appkey只能保持一个连接,多个连接只会保留最后一次连接
2.3. 回调地址
- 回调地址是用来接收工单变更事件,工单字段变更后,会在回调地址中接收到加密之后的变更消息。
POST方式请求,将相应的消息存放在请求体中,使用企业的app_secret直接对请求体进行AES解密即可获取对应的信息。- 若回调失败,即第三方响应
code不为0,那么会每隔5s进行重试,最多重试3次。
- 若
3次都没有成功响应,则会在对应工单中记录一条推送失败的操作记录。 - 若多次推送都失败,第三方可通过
api接口xd.ticket.list拉取工单数据。
2.3.1. 回调地址要求
- 推送的消息是
POST方式,编码格式为utf-8 - 不支持传自定义参数
2.3.2. 消息解析
通过解析消息体,可以获取每条消息的具体信息
(1)推送给第三方的加密消息
curl --location '{{host}}'
--header 'Content-Type: application/json'
--data '{
"version": "v0.0.1",
"data": "jF5gtaRtxPX1XqOhuN+uFs+FgkYfTLBUh29OrghmTEAfVMqXGYAO4k1mbKuzhZnkQuAE8sAfhTIrqrgGMycDeJ00Bbx8H2rQxJdpCQGlK5wHhIo20takjXy0VIqQ+wy/sYMjwcPDVVcyB4tdb5S4d7CroP00o0mREloX4kmq4N3bum2dl4+X5iKV0aRTlYWcVFsre7PcF7OF/oBTQ14eF2yYILTTopTWHsPBUId0jARGhFIPnBLiv6a6gqMmouIfq4I4ckHCugmEduZP0sF5ITFIkqgL/ttDK/dcdY8UN1MgrUQrteT/1juz/CV35o/8Y9GeZRWCJNqXJaX5rBgXUM4Dx0a+IcRLcxydlegmi3uzxKacEUw9X76q/S7ePdbzJe/6ma5HTLl8zi+6uuunmZMUUg8Oo70yKuomVJylgCXiX3RgBmbDLXT6q/osfcyueSiHVqcOpw23+qSZ1ibkjC4ln1ouJjBQVBpRKLcTKkszBUTXLwRsHXTzYQIn8ZUMruJTiiVPw9rebCAKctUZZmPIRurikao6h4gHLLlYMDErq1KhCOf4WIpiB1jTXTfusJG1bdUBSTmQoFguwdVc90BENFGKI6iVADBlgVUyRhdscd2d6fp2+MDSdMaeZR/bt1o+gvqw7hrCST6Cj+ndKREDRSbV5yNPau7YAjcK8ikQyMVBd1OjXDCwuy/0dImcNpdzy3ZkI9BFc5lA4OC/w7POsBpwQaqRrCIqBJM4Phir7KcatSua51lHhLTrPuwXC7i/YLzLAkUhDqpRMkjUiej7Jf6MXdAGhnmqgpxOjS/JcvKqbObR5qNlEXSKrZzbkIOGXxZcADBn60AXqZ+Gn+izsH+mGce3tSbWgGTeIDRdWcD57kHIh/JNSalU5MckoWoOdXZYvUuHzpjgX765+BrPZ2SIewWl5MZobUHEVyGkqpHSBXmRlyYQROrl5a0G"
}'(2)app_secret
nkwpdVyagzTcFXJt(3)解密之后的消息
{
"event_id": "xd.ticket.result",
"event_time": 1670240307806,
"version": "v0.0.1",
"data": {
"version": "v0.0.1",
"category": "仓库",
"cf_values": [{
"name": "优先级",
"value": "普通",
"type": "优先级",
"is_changed": true
}, {
"name": "处理人",
"value": "待分配",
"type": "处理人"
}, {
"name": "补发原因",
"value": "破损",
"type": "下拉"
}],
"create_time": 1670240307000,
"creator_name": "杜可风按",
"enterprise_id": "q-610a49e0db1d8875d8980e41",
"source": "第三方",
"ticket_entry_id": 1,
"ticket_seq": 1,
"ticket_status": "已完成",
"ticket_type": "换货",
"ticket_type_id": 1,
"update_time": 1670240307000,
"message_type": "ticket_update",
"comments": [{
"content": "备注内容",
"create_time": 1670240307000,
"account_name": "张三"
}]
}
}(4)第三方返回的响应(无需加密):
若失败,需返回message错误原因
{
"code": 0,
"message": ""
}2.3.3. 解密算法
Go解密代码:
func main() {
// 回调地址接受到的加密内容
msgSecret := "jF5gtaRtxPX1XqOhuN+uFs+FgkYfTLBUh29OrghmTEAfVMqXGYAO4k1mbKuzhZnksf5BPW2/8jmi1zIYwUG9jtZTWbLgwZB57EowtXP5H8qfsu4gBClufVT9cebZHYyFtYZ64btsIF8EUtsU9+p5bvNNB/k7urXfR+e7hk8XOMhfZI70ZRa+BEoNY8Br6kkcfZdV4S6mimViJJkhLBkbbCN22ZKDx/swzp8wGmI9y9RLLtjGPzUv6DJ+xyKu8fYTQjeYXHu9T/fGOhT6nbXbod0KBv8zODT9glbAXQg5ZSEKb8H2wSTBMy2SzswAksUPfRPnhMtSMbL164gybAd7TAXA7F+HLYxfvN2gPd4yDTlH1s2/CYGVLXAMoZVxR+hBY8QHn+f+sxLiAU1TstuRscT1AxIh5UY8KGKIrZi86qEa657JEPGFS2m1i4s5/eZwHxTj2D79l5uV0ek6vmqAumz/VLC0Uf7tpK96pAwdkA8ttifxkn3aknfzPgZgsp56czqk4sci88GHCQwv4IQn5zqA/GrmmmHNLxjsQ4tojMavrGMjrOI28k+s8H1YUWwvoBH6J5cyMi9Yb9JdL7/BKz9mtv4zpr7rQBuVe+rVyMHNa+bZ/eL2HCbqoj7OT8FGogPZQntmTfoXjfPn4AscUQrAIwpT5Z6edo19Ae4qj3kjGJ/Q0JwoTjTslIeAfRc9LRCjECNpHLTzHICi7NDDLrVE2L+OT6FeRrbYwpNGFQvcKNtL5ad3dXXj17jIob9SaijRKsDJZBHfIgxOGjHNhA20NPF/mRfxdU1erqX1N3gZsOEtPIui0yQt1X+Q02hD" //加密之后的消息
// 企业的 app_secret
appSecret := "nkwpdVyagzTcFXJt"
// 获取解密后的内容
sourceMsg := AesDecrypt(msgSecret, appSecret)
fmt.Printf("解密后:%sn", sourceMsg)
}
func AesDecrypt(msgSecret, appSecret string) string {
bytesPass, err := base64.StdEncoding.DecodeString(msgSecret)
if err != nil {
fmt.Println(err)
return "解密失败!!!"
}
sourceMsg, err := DoAesDecrypt(bytesPass, []byte(appSecret))
if err != nil {
fmt.Println(err)
return "解密失败!!!"
}
return string(sourceMsg)
}
func DoAesDecrypt(encryptedMsg, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
//AES分组长度为128位,所以blockSize=16,单位字节
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) //初始向量的长度必须等于块block的长度16字节
origData := make([]byte, len(encryptedMsg))
blockMode.CryptBlocks(origData, encryptedMsg)
origData = PKCS5UnPadding(origData)
return origData, nil
}
//去除填充数据
func PKCS5UnPadding(origData []byte) []byte {
length := len(origData)
unfilledNum := int(origData[length-1])
return origData[:(length - unfilledNum)]
}Python解密代码:
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
def aes_decrypt(msg_secret, app_secret):
key = app_secret.encode('utf-8')
encrypted_msg = base64.standard_b64decode(msg_secret)
cipher = AES.new(key, AES.MODE_CBC, key[:16])
decrypted_data = cipher.decrypt(encrypted_msg)
unpadded_data = unpad(decrypted_data, 16)
return unpadded_data.decode('utf-8')
msg_secret = "jF5gtaRtxPX1XqOhuN+uFs+FgkYfTLBUh29OrghmTEAfVMqXGYAO4k1mbKuzhZnksf5BPW2/8jmi1zIYwUG9jtZTWbLgwZB57EowtXP5H8qfsu4gBClufVT9cebZHYyFtYZ64btsIF8EUtsU9+p5bvNNB/k7urXfR+e7hk8XOMhfZI70ZRa+BEoNY8Br6kkcfZdV4S6mimViJJkhLBkbbCN22ZKDx/swzp8wGmI9y9RLLtjGPzUv6DJ+xyKu8fYTQjeYXHu9T/fGOhT6nbXbod0KBv8zODT9glbAXQg5ZSEKb8H2wSTBMy2SzswAksUPfRPnhMtSMbL164gybAd7TAXA7F+HLYxfvN2gPd4yDTlH1s2/CYGVLXAMoZVxR+hBY8QHn+f+sxLiAU1TstuRscT1AxIh5UY8KGKIrZi86qEa657JEPGFS2m1i4s5/eZwHxTj2D79l5uV0ek6vmqAumz/VLC0Uf7tpK96pAwdkA8ttifxkn3aknfzPgZgsp56czqk4sci88GHCQwv4IQn5zqA/GrmmmHNLxjsQ4tojMavrGMjrOI28k+s8H1YUWwvoBH6J5cyMi9Yb9JdL7/BKz9mtv4zpr7rQBuVe+rVyMHNa+bZ/eL2HCbqoj7OT8FGogPZQntmTfoXjfPn4AscUQrAIwpT5Z6edo19Ae4qj3kjGJ/Q0JwoTjTslIeAfRc9LRCjECNpHLTzHICi7NDDLrVE2L+OT6FeRrbYwpNGFQvcKNtL5ad3dXXj17jIob9SaijRKsDJZBHfIgxOGjHNhA20NPF/mRfxdU1erqX1N3gZsOEtPIui0yQt1X+Q02hD"
app_secret = "nkwpdVyagzTcFXJt"
decrypted_msg = aes_decrypt(msg_secret, app_secret)
print(f"解密后: {decrypted_msg}")
#请注意,Python 中使用了 pycryptodome 库来提供 AES 加密和解密的功能。如果您尚未安装该库,可以通过运行 pip install pycryptodome 命令进行安装。Java解密代码
public class Main {
public static void main(String[] args) {
try {
String msgSecret = "jF5gtaRtxPX1XqOhuN+uFs+FgkYfTLBUh29OrghmTEAfVMqXGYAO4k1mbKuzhZnksf5BPW2/8jmi1zIYwUG9jtZTWbLgwZB57EowtXP5H8qfsu4gBClufVT9cebZHYyFtYZ64btsIF8EUtsU9+p5bvNNB/k7urXfR+e7hk8XOMhfZI70ZRa+BEoNY8Br6kkcfZdV4S6mimViJJkhLBkbbCN22ZKDx/swzp8wGmI9y9RLLtjGPzUv6DJ+xyKu8fYTQjeYXHu9T/fGOhT6nbXbod0KBv8zODT9glbAXQg5ZSEKb8H2wSTBMy2SzswAksUPfRPnhMtSMbL164gybAd7TAXA7F+HLYxfvN2gPd4yDTlH1s2/CYGVLXAMoZVxR+hBY8QHn+f+sxLiAU1TstuRscT1AxIh5UY8KGKIrZi86qEa657JEPGFS2m1i4s5/eZwHxTj2D79l5uV0ek6vmqAumz/VLC0Uf7tpK96pAwdkA8ttifxkn3aknfzPgZgsp56czqk4sci88GHCQwv4IQn5zqA/GrmmmHNLxjsQ4tojMavrGMjrOI28k+s8H1YUWwvoBH6J5cyMi9Yb9JdL7/BKz9mtv4zpr7rQBuVe+rVyMHNa+bZ/eL2HCbqoj7OT8FGogPZQntmTfoXjfPn4AscUQrAIwpT5Z6edo19Ae4qj3kjGJ/Q0JwoTjTslIeAfRc9LRCjECNpHLTzHICi7NDDLrVE2L+OT6FeRrbYwpNGFQvcKNtL5ad3dXXj17jIob9SaijRKsDJZBHfIgxOGjHNhA20NPF/mRfxdU1erqX1N3gZsOEtPIui0yQt1X+Q02hD";
String appSecret = "nkwpdVyagzTcFXJt";
String decrypt = decrypt(msgSecret, appSecret);
System.out.println("解密后: " + decrypt);
} catch (Exception e) {
System.out.println("解密失败!!!");
e.printStackTrace();
}
}
public static String decrypt(String sSrc, String sKey) throws Exception {
try {
byte[] raw = sKey.getBytes(StandardCharsets.US_ASCII);
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
int blockSize = cipher.getBlockSize();
IvParameterSpec iv = new IvParameterSpec(sKey.substring(0, blockSize).getBytes());
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] encrypted1 = Base64.getDecoder().decode(sSrc); // 先用Base64解密
byte[] original = cipher.doFinal(encrypted1);
return new String(original, StandardCharsets.UTF_8);
} catch (Exception ex) {
throw ex;
}
}
}2.3.4. 结构体展示
参数 | 类型 | 描述 | 示例 |
event_id | string | 推送事件类型 | xd.ticket.result |
event_time | int64 | 消息推送时间 | 1670240307806 |
version | string | 推送接口的版本信息 | v0.0.1 |
data | object | 对应的消息内容 | |
data.version | string | 推送事件的版本信息 | v0.0.1 |
data.message_type | string | 工单变更类型 | ticket_create/ticket_update/ticket_comment (创建工单/更新工单/评论(备注)工单) |
data.ticket_entry_id | int | 工单的id | 1 |
data.ticket_seq | int | 工单编号 | 1 |
data.ticket_type_id | int | 工单类型id | 1 |
data.category | string | 一级分类名称 | 仓库 |
data.ticket_type | string | 二级工单类型名称 | 换货 |
data.ticket_status | string | 工单状态 | 待分配 |
data.cf_values | []object | 对应工单类型管理中当前二级工单类型中的组件 | { "name": "店铺", "value": "小宏v/淘宝", "is_changed": true, "type":"店铺" } |
data.cf_values.name | string | 组件名称 | 店铺 |
data.cf_values.value | string | 填入的值 | 小宏v/淘宝 |
data.cf_values.is_changed | boolean | 本次变更后,该字段值是否改变 | false |
data.cf_values.type | string | 组件类型 | 店铺 |
data.creator_name | string | 创建人 | 张三 |
data.create_time | int | 创建时间 | 1670240307000 |
data.update_time | int | 更新时间 | 1670240307000 |
data.completed_time | int | 完成时间 | 1670240307000 |
data.source | string | 创建来源 | 后台/小程序/导入/第三方 |
data.comments | []object | ||
data.comments.content | string | 备注内容 | xxx |
data.comments.create_time | string | 备注时间 | 1670240307000 |
data.comments.account_name | string | 备注人员账号名称 | 张三 |
data.relations | []object | ||
data.relations.is_parent | boolean | 目标是否是本工单父单,false则为子单 | |
data.relations.ticket_entry_id | int | 父子关联单的工单id | |
data.relations.ticket_entry_seq | int | 父子关联单的工单序列 |
示例
{
"event_id": "xd.ticket.result",
"event_time": 1670240307806,
"version":"v0.0.1",
"data": {
"version":"v0.0.1",
"category": "仓库",// 一级工单类型
"cf_values": [{
"name": "优先级", // 组件名称
"value": "普通", // 组件值
"type":"优先级", // 组件类型
"is_changed": true // 本次变更后,该字段值是否改变
}, {
"name": "处理人",
"value": "待分配",
"type":"处理人"
}, {
"name": "补发原因",
"value": "破损",
"type":"下拉"
}],
"relations":[
{
"is_parent":false,
"ticket_entry_id":370198,
"ticket_entry_seq":56680,
"ticket_type_id":12118
}
],
"create_time": 1670240307000, // 创建时间
"creator_name": "杜可风按",// 创建人
"enterprise_id": "q-610a49e0db1d8875d8980e41",
"source": "第三方", // 工单创建来源
"ticket_entry_id": 1, // 工单id
"ticket_seq": 1, // 工单编号
"ticket_status": "已完成",// 工单状态
"ticket_type": "换货", // 二级工单类型
"ticket_type_id": 1,// 工单类型id
"update_time": 1670240307000,// 更新时间
"message_type": "ticket_update",// 推送事件类型
"comments":[{
"content":"备注内容",
"create_time":1670240307000,//备注时间
"account_name":"张三"//备注人
}]
}
}3. API接口
3.1. 创建工单xd.ticket.create
- method:
xd.ticket.create - 参数:
参数 | 类型 | 必填 | 描述 | 示例值 |
enterprise_id | string | 是 | 企业ID | 1234567890 |
operator | string | 是 | 操作人,对应青鸟中人员账号名称 | 张三 |
category | string | 是 | 一级分类名称 | 仓库 |
ticket_type | string | 是 | 二级工单类型名称 | 换货 |
ticket_status | string | 是 | 工单状态 | 待分配 |
cf_values | []object | 是 | 对应工单类型管理中当前二级工单类型中的组件,传入要创建或更新的组件的值 (填入值可参考 5.工单传入参数规则) | { "name": "店铺", "value": "小宏v/淘宝" } |
cf_values.name | string | 是 | 组件名称 | 店铺 |
cf_values.value | string | 是 | 填入的值 | 小宏v/淘宝 |
version | string | 是 | 版本 | v0.0.1 |
relations | []object | 否 | 父子单关系。不填则不关联父子关系 | [{ "is_parent":true, "ticket_entry_id":370000 }] is_parent:对方是否是本工单父工单,false则为子工单。 支持关联最多三个工单。 |
- 示例
###创建工单
{
"version":"v0.0.1",
"enterprise_id": "1234567890",
"operator": "张三",
"category": "仓库",
"ticket_type": "换货",
"cf_values": [{
"name": "店铺",
"value": "小宏v/淘宝"
},{
"name": "订单号",
"value": "2245853052090782746"
}]
}
@return
{
"code": 0,
"error_message": "",
"data": {
"code": 0,
"data": {
"data": {
"fields": [
"店铺"
],
"ticket_entry_id": 1, // 标识工单唯一的id,可用于更新工单
"ticket_seq":2 // 工单编号,可用于web页面查询工单编号
},
"err_code": 10204,
"message": "存在非必填组件,字段因不匹配被忽略"
}
}
}3.2. 更新工单xd.ticket.update
- method:
xd.ticket.update - 参数:
参数 | 类型 | 必填 | 描述 | 示例值 |
enterprise_id | string | 是 | 企业ID | 1234567890 |
operator | string | 是 | 操作人 | 张三 |
ticket_entry_id | int | 是 | 需更新工单的id,由创建接口返回 | 1 |
ticket_status | string | 否 | 工单状态 | 待分配 |
cf_values | []object | 否 | 对应工单类型管理中当前二级工单类型中的组件,传入要创建或更新的组件的值 (填入值可参考 5.工单传入参数规则) | { "name": "店铺", "value": "小宏v/淘宝" } |
cf_values.name | string | 组件名称 | 店铺 | |
cf_values.value | string | 填入的值 | 小宏v/淘宝 | |
comment | string | 否 | 备注内容 | 备注内容 |
version | string | 是 | 版本 | v0.0.1 |
silent | boolean | 否 | 是否推送第三方消息 | true |
relations | []object | 否 | 更新父子单关系。不填或空则为删除父子单关系 | [{ "is_parent":true, "ticket_entry_id":370000 }] is_parent:对方是否是本工单父工单,false则为子工单。 |
- 示例
###更新工单
{
"version":"v0.0.1",
"enterprise_id": "1234567890",
"operator": "张三",
"ticket_entry_id":1,
"cf_values": [{
"name": "订单号",
"value": "2245853052090782746"
}]
}
@return
{
"code": 0,
"error_message": "",
"data": {
"code": 0,
"data": {
"data": [],
"err_code": 10102,
"message": "工单状态不存在"
}
}
}3.3. 查询工单xd.ticket.list
- method:
xd.ticket.list - 参数:
参数 | 类型 | 必填 | 描述 | 示例值 |
enterprise_id | string | 是 | 企业ID | 1234567890 |
limit | Int | 是 | 默认值10,不超过1000 | 10 |
skip | int | 否 | 默认值0 | 0 |
category | string | 否 | 一级分类名称 | 仓库 |
ticket_type | string | 否 | 二级工单类型名称 | 换货 |
create_time_begin | int | 否 | 工单创建开始时间(毫秒级时间戳) | 1670083200000 |
create_time_end | int | 否 | 工单创建截止时间(毫秒级时间戳) | 1670169599999 |
update_time_begin | int | 否 | 工单更新开始时间(毫秒级时间戳) | 1670083200000 |
update_time_end | int | 否 | 工单更新截止时间(毫秒级时间戳) | 1670169599999 |
completed_time_begin | int | 否 | 工单完成开始时间(毫秒级时间戳) | 1670083200000 |
completed_time_end | int | 否 | 工单完成截止时间(毫秒级时间戳) | 1670169599999 |
ticket_seq | int | 否 | 工单编号 | 1 |
ticket_status | []string | 否 | 工单状态 | ["已关闭","已完成"] |
version | string | 是 | 版本 | v0.0.1 |
cf_values | []object | 否 | 对应工单类型管理中当前二级工单类型中的组件,传入要创建或更新的组件的值 (填入值可参考 5.工单传入参数规则) | { "name": "订单号", "value": "123456", "type": "订单信息" } |
cf_values.name | string | 组件名称 | 订单号 | |
cf_values.value | string | 填入的值 | 123456 | |
cf_values.type | string | 组件类型 | 订单信息 | |
search_relations | boolean | 查询父子单时必填 | 是否查询父子单关系 | true: 查询父子单关系。可跨普通/批量打款查询工单 |
ticket_entry_ids | []int | 查询父子单时必填 | 父子单工单id | 说明: 需要通过父子单id进一步查询工单列表获取父子单详情。 |
- 示例:
###查询工单
{
"version":"v0.0.1",
"enterprise_id": "1234567890",
"category":"仓库",
"ticket_type": "换货",
"limit":30,
"skip":60,
"cf_values": [
{
"type": "订单信息",
"name": "订单信息",
"value": "123"
}
]
}
@return
{
"code": 0,
"error_message": "",
"data": {
"code": 0,
"data": {
"data": {
"count": 8536,
"entries": [{
"category": "群对接",// 一级工单类型
"cf_values": [{
"name": "处理人1",// 组件名称
"value": "22222" // 组件值
},{
"name": "优先级22",
"value": "普通"
},{
"name": "多选14",
"value": "[]"
},{
"name": "多级下拉18",
"value": "["11111","234543"]"
},{
"name": "订单信息10",
"value": "{"order_id":"1730441211855860970"}"
},{
"name": "订单金额",
"value": "0.01"
},{
"name": "顾客昵称23",
"value": "tb43288734"
},{
"name": "店铺",
"value": "不像实力派/淘宝"
},{
"name": "平台",
"value": "淘宝"
},{
"name": "收货人手机号3",
"value": "15775555555"
},{
"name": "发货仓库19",
"value": "成都"
},{
"name": "下单时间7",
"value": "1667875139000"
}],
"relations":[
{"is_parent":true,"ticket_entry_id":95,"ticket_entry_seq":25,"ticket_type_id":93}
],
"create_time": 1680854756000,// 创建时间
"creator_name": "杜可风按",// 创建人
"source": "后台", // 创建工单来源
"ticket_entry_id": 5761752,// 工单id
"ticket_seq": 17720,//工单编号
"ticket_status": "待受理",// 工单状态
"ticket_type": "群对接(全组件)",// 二级工单类型
"ticket_type_id": 5876,
"update_time": 1680854757000,// 更新时间
"comments":[{
"content":"备注内容",
"create_time":1670240307000,//备注时间
"account_name":"张三"//备注人
}]
}
]
}
}
}
}3.4. 查询批量打款xd.ticket.batch_pay_list
- method:
xd.ticket.batch_pay_list - 参数:
参数 | 类型 | 必填 | 描述 | 示例值 |
enterprise_id | string | 是 | 企业ID | 1234567890 |
limit | Int | 是 | 默认值10,不超过1000 | 10 |
skip | int | 否 | 默认值0 | 0 |
category | string | 否 | 一级分类名称 | 仓库 |
ticket_type | string | 否 | 二级工单类型名称 | 换货 |
create_time_begin | int | 否 | 工单创建开始时间(毫秒级时间戳) | 1670083200000 |
create_time_end | int | 否 | 工单创建截止时间(毫秒级时间戳) | 1670169599999 |
ticket_seq | int | 否 | 工单编号 | 1 |
pay_time_begin | int | 否 | 支付开始时间(毫秒级时间戳) | 1670083200000 |
pay_time_end | int | 否 | 支付截止时间(毫秒级时间戳) | 1670169599999 |
ticket_status | []string | 否 | 工单状态 | ["待审核"] |
version | string | 是 | 版本 | v0.0.1 |
cf_values | []object | 否 | 对应工单类型管理中当前二级工单类型中的组件,传入要创建或更新的组件的值 (填入值可参考 5.工单传入参数规则) | { "name": "订单号", "value": "123456", "type": "订单信息" } |
cf_values.name | string | 组件名称 | 订单号 | |
cf_values.value | string | 填入的值 | 123456 | |
cf_values.type | string | 组件类型 | 订单信息 |
- 示例:
###查询工单
{
"version":"v0.0.1",
"enterprise_id": "1234567890",
"category":"仓库",
"ticket_type": "换货",
"limit":30,
"skip":60,
"cf_values": [
{
"type": "订单信息",
"name": "订单信息",
"value": "123"
}
]
}
@return
{
"code": 0,
"error_message": "",
"data": {
"count":2100,
"entries":[{
"ticket_entry_id": 330080, // 工单id
"ticket_seq":321321, //工单编号
"category": "全组件-组件",// 一级工单类型
"ticket_type": "全部组件", // 二级工单类型
"creator_name": "杜可风按",// 创建人
"ticket_status": "已完成",// 工单状态
"create_time": 1670240307000, // 创建时间
"update_time": 1670240307000// 更新时间
"pay_account":"908392819@126.com", // 付款支付宝账号
"pay_time":1670240307000, //付款时间
"pay_operator":"小王", //付款人
"cf_values": [{
"name": "优先级",// 组件名称
"value": "普通" // 组件值
}, {
"name": "处理人",
"value": "待分配"
}],
"comments":[{
"content":"备注内容",
"create_time":1670240307000,//备注时间
"account_name":"张三"//备注人
}]
}]
}
}4. 订阅事件
4.1. 工单变更消息xd.ticket.result
将消费者通过小程序、客服通过青鸟工单后台、导入创建或更新的工单推送消息到第三方。
- sub_event_ids:
xd.ticket.result - 参数:
参数 | 类型 | 描述 | 示例值 |
ticket_entry_id | int | 工单的id | 1 |
ticket_seq | int | 工单编号 | 1 |
ticket_type_id | int | 工单类型id | 1 |
category | string | 一级分类名称 | 仓库 |
ticket_type | string | 二级工单类型名称 | 换货 |
ticket_status | string | 工单状态 | 待分配 |
cf_values | []object | 对应工单类型管理中当前二级工单类型中的组件 | { "name": "店铺", "value": "小宏v/淘宝", "is_changed": true } |
cf_values.name | string | 组件名称 | 店铺 |
cf_values.value | string | 填入的值 | 小宏v/淘宝 |
cf_values.is_changed | boolean | 本次变更后,该字段值是否改变 | false |
creator_name | string | 创建人 | 张三 |
create_time | int | 创建时间 | 1670240307000 |
source | string | 创建来源 | 后台/小程序/导入/第三方 |
message_type | string | 推送事件类型 | ticket_create/ticket_update/ticket_comment (创建工单/更新工单/评论(备注)工单) |
- 示例:
{
"event_id": "xd.ticket.result",
"event_time": 1670240307806,
"data": {
"category": "仓库",// 一级工单类型
"cf_values": [{
"name": "优先级",// 组件名称
"value": "普通", // 组件值
"is_changed": true // 本次变更后,该字段值是否改变
}, {
"name": "处理人",
"value": "待分配"
}],
"create_time": 1670240307000, // 创建时间
"creator_name": "杜可风按",// 创建人
"enterprise_id": "q-610a49e0db1d8875d8980e41",
"source": "第三方", // 工单创建来源
"ticket_entry_id": 330080, // 工单id
"ticket_seq": 17632, // 工单编号
"ticket_status": "已完成",// 工单状态
"ticket_type": "换货", // 二级工单类型
"ticket_type_id": 1480,// 工单类型id
"update_time": 1670240307000,// 更新时间
"message_type": "ticket_update",// 推送事件类型
"comments":[{
"content":"备注内容",
"create_time":1670240307000,//备注时间
"account_name":"张三"//备注人
}]
},
"appkey": ""
}5. 工单传入参数规则
在创建工单、更新工单时,传入组件值的数据规则与工单类型中组件类型对应的,对应规则如下,若表格中无对应的组件,则不支持传入。

5.1. 版本v0.0.1
组件类型 | 新建、更新工单 传入示例 | 查询工单 传入示例 | 说明 | 数据来源 | |
电商组件 | |||||
店铺 | {"name": "组件名称","value": "测试店铺非卖品/淘宝"} | {"name": "组件名称","value": "["测试店铺非卖品/淘宝"]","type": "店铺"} | 店铺管理中已有的店铺,格式:店铺名称/平台 |
| |
平台 | {"name": "组件名称","value": "淘宝"} | {"name": "组件名称","value": "["淘宝"]","type":"平台"} | 青鸟协同里平台组件已有的平台 |
| |
订单信息 | 订单号 | {"name": "组件名称","value": "{"order_id":"20220000000","sku_ids":["1000000222"],"goods_ids":["9999000000"]}"} | {"name": "组件名称","value": "20220000000","type":"订单信息"} | order_id为订单号 | |
订单商品 | sku_id为商品SKU的ID,goods_id为商品的ID 传入SKU的ID时,将勾选对应的SKU商品;传入商品ID时,可能出现多个不同SKU的商品,将勾选商品ID对应的该商品在订单中的所有SKU商品。 | ||||
订单金额 | {"name": "组件名称","value": "1.02"} | {"name": "组件名称","value": "[1,2]","type":"订单金额"} | 传入不为空时则为传入的内容,没有则默认按照订单自动回填;任意数字(格式支持最多3位小数) | ||
顾客昵称 | {"name": "组件名称","value": "tb425743344"} | {"name": "组件名称","value": "tb425743344","type":"顾客昵称"} | 传入不为空时则为传入的内容,没有则默认通过订单号自动回填 | ||
商品 | {"name": "组件名称","value": "[{"outer_sku_id":"PT004","item_name":"衣架,二手衣架","num":1}]"} | ERP商品 value.outer_sku_id 为商家编码 value.item_name 为商品名称 value.num 为商品数据 | |||
发货仓库 | {"name": "组件名称","value": "成都"} | {"name": "组件名称","value": "成都","type":"发货仓库"} | 传入不为空时则为传入的内容,没有则默认按照订单自动回填;请填写工单系统内发货仓库组件中已有的选项 | ||
下单时间 | {"name": "组件名称","value": "1667977603000"} | 传入不为空时则为传入的内容,没有则默认按照订单自动回填;填写13位的时间戳 | |||
收货人信息套件 | 收货人 | {"name": "组件名称","value": "张三"} | {"name": "组件名称","value": "张三","type":"收货人"} | 收货人姓名 | |
收货人手机号 | {"name": "组件名称","value": "180000000"} | {"name": "组件名称","value": "180000000","type":"收货人手机号"} | 180000000 | ||
收货人地址 | {"name": "组件名称","value": "{"province":{"name":"四川省"},"town":{"name":"成都市"},"district":{"name":"双流区"},"street":"中和街道","detail":"中和街道朝阳路229号城南春天"}"} | {"name": "组件名称","value": "四川省成都市双流区中和街道朝阳路229号城南春天","type":"收货人地址"} | 省,市,区,街道,详细地址 (每个字段都需填写,地址信息才能生效) | ||
物流信息 | {"name":"组件名称","value":"[{"logistics_company":"中通","logistics_no":"123465489"}]"} | logistics_company为承运商 logistics_no为物流单号 | |||
基础组件 | |||||
单行文本 | {"name": "组件名称","value": "单行文本"} | {"name": "组件名称","value": "单行文本","type": "单行文本"} | 一则单行文本 | ||
多行文本 | {"name": "组件名称","value": "多行文本"} | {"name": "组件名称","value": "多行文本","type":"多行文本"} | 一则多行文本 | ||
日期 | {"name": "组件名称","value": "1667977603000"} | {"name": "组件名称","value": "[1667977603000,1667977603000]","type":"日期"} | 填写毫秒级时间戳 | ||
日期&时间 | {"name": "组件名称","value": "1667977603000"} | {"name": "组件名称","value": "[1667977603000,1667977603000]","type":"日期&时间"} | 填写毫秒级时间戳 | ||
下拉 | {"name": "组件名称","value": "下拉选项"} | {"name": "组件名称","value": "["下拉选项"]","type":"下拉"} | 请填写该组件内已有的选项 | ||
多级下拉 | {"name": "组件名称","value": "["第1层选项","第2层选项"]"} | {"name": "组件名称","value": "["第1层选项>第2层选项"]","type":"多级下拉"} | 请填写该组件内已有的选项 | ||
单选 | {"name": "组件名称","value": "单选选项"} | {"name": "组件名称","value": "["单选选项"]","type":"单选"} | 请填写该组件内已有的选项 | ||
多选 | {"name": "组件名称","value": "["第1个多选选项","第2个多选选项"]"} | {"name": "组件名称","value": "["第1个多选选项","第2个多选选项"]","type":"多选"} | 请填写该组件内已有的选项 | ||
NPS量表 | {"name": "组件名称","value": "NPS选项值"} | {"name": "组件名称","value": "["NPS选项值"]","type":"NPS量表"} | 请填写该组件内已有的选项 | ||
数值 | {"name": "组件名称","value": "1.2"} | {"name": "组件名称","value": "[1,2]","type":"数值"} | 任意数字(格式支持最多3位小数) | ||
上传图片 | {"name": "组件名称","value":"[{"name":"f5dd25c2527841ec883df8f3ceadc983.jpg","url":"https://wangcai-client-new.oss-cn-hangzhou.aliyuncs.com/1647949042024_f5dd25c2527841ec883df8f3ceadc983.jpg"}]"} | value.name 为图片名称 value.url 为图片的地址 | |||
附件 | {"name": "组件名称","value":"[{"file_name":"f5dd25c2527841ec883df8f3ceadc983.jpg","url":"https://wangcai-client-new.oss-cn-hangzhou.aliyuncs.com/1647949042024_f5dd25c2527841ec883df8f3ceadc983.jpg"}]"} | value.file_name为附件名称 value.url 为附件的地址 | |||
优先级 | {"name": "组件名称","value": "普通"} | {"name": "组件名称","value": "["普通"]","type":"优先级"} | 普通/紧急/非常紧急 | ||
处理人 | {"name": "组件名称","value": "处理人"} | {"name": "组件名称","value": "["处理人"]","type":"处理人"} | 请填写工单系统内成员昵称或保持空白 | ||
工单状态 | 作为ticket_status参数传入,”ticket_status“:"待分配" | 作为ticket_status参数传入,”ticket_status“:["待分配"] | 待分配/待受理/处理中/已完成/已关闭 审批驳回/待审批/待支付/支付中/已支付/支付失败/已关闭/已支付处理中 | ||
跟进时间 | {"name": "组件名称","value": "1667977603000"} | {"name": "组件名称","value": "[1667977603000,1667977603000]","type":"跟进时间"} | 填写13位的时间戳 | ||
智能组件 | |||||
群对接 | {"name": "组件名称","value": "中通对接群"} | 请填写工单系统中已配置的物流对接群 |
| ||
旺店通智能组件 | {"name": "组件名称","value": "[{"order_type":"plat","erp_order_id":"JY202209210235","warehouse_code":"001","logistics_company":"淘宝-中通","logistics_no":"","create_time":1663750499000,"got_time":0,"sign_time":0,"estimate_profit":0.0,"latest_logistics_info":"","goods":[{"goods_name":"床上四件套-床单","spec_name":"默认规格","spec_no":"CHUANG-CHUANGDAN","share_price":0.0011,"share_amount":0.0077,"num":7}]}]"} | order_type 订单类型 string erp_order_id erp单号 string warehouse_code 发货仓 string logistics_company 物流公司 string logistics_no 物流编号 string create_time 创建时间 int64 13位的时间戳 got_time 揽收时间 int64 13位的时间戳 sign_time 签收时间 int64 13位的时间戳 estimate_profit 预估毛利 float64 latest_logistics_info 最新物流信息 string goods.goods_name 商品名称 string goods.spec_name 规格名称 string goods.spec_no 规格编码 string goods.share_price 分摊价格 float64 goods.share_amount 分摊总价 float64 goods.num 数量 int64 | |||
6. 错误码
错误码分为两部分:
- http 响应体
- http body 返回的 json 中的 code
- 其中有一些错误会在 http body 返回的 json 中的 data 中,返回业务具体信息。如:创建工单时,存在必填项未填时,data中返回具体哪些组件未填
{
"code": 0,
"error_message": "",
"data": {
"code": 0,
"data": {
"data": {
"fields": [
"店铺"
],
"ticket_entry_id": 1
},
"err_code": 10204,
"message": "存在非必填组件,字段因不匹配被忽略"
}
}
}6.1. http 响应体
外层code值 | 含义 |
0 | 成功,可以使用data内的业务内容 |
非0 | 各种网关层错误,原因会填写error_message字段 |
6.2. 内层code
值 | 含义 | data |
10001 | 找不到对应的工单类型 | |
10002 | 存在多个工单类型 | |
10003 | 该工单类型不支持创建工单 | |
10101 | 工单ID不存在 | |
10102 | 工单状态不存在 | |
10103 | 该企业不存在 | |
10104 | 该操作人不存在 | |
10105 | 该版本不存在 | |
10201 | 缺少必填项组件 | {"fields":["组件名称1","组件名称2"]} |
10202 | 必填组件,但未定义规则,不支持传入 | {"fields":["组件名称1","组件名称2"]} |
10203 | 必填组件字段不匹配 | {"fields":["组件名称1","组件名称2"]} |
10204 | 存在非必填组件字段不匹配(其他能匹配到的字段仍然能成功更新) | {"fields":["组件名称1","组件名称2"]} |
10301 | 此工单当前状态下不允许编辑 | |
10302 | 当前账号无权限编辑工单 |



