Skip to content

客服系统 API 文档(draft)

猫猫 edited this page Oct 9, 2022 · 19 revisions

总览

所有 API 以 /api/{version} 开头,目前的 version 为 2,使用的 Base URL 是在 LeanCloud 控制台绑定的云引擎域名。

API 详细参数与返回值请参考对应部署的 /docs/{version} 页面。本文重点在描述 API 的用法。

鉴权

除了公开的资源(分类、知识库),所有接口都需要登录,通过 X-LC-Session Header 鉴权。登录方式(session 的获取)参见下文用户部分。

多语言

目前仅「自定义表单」功能支持多语言。计划将来分类、知识库等所有可配置的内容都支持多语言。 在客户端视角,在请求的 Accept-Language header 中指定语言的优先级即可。目前支持的语言有 zh-CN, zh-TW, zh-HK, en, ja, ko, id, th, de, fr, ru, es, pt, tr

用户

POST /users 登录

使用 JWT HMAC RS256 算法与给定的私钥对用户登录信息进行签名。登录信息 payload 包含两个内置字段 sub 与 name。其中 sub 为用户唯一标识为必选(用户 ID),如果同一个厂商在不同产品中使用同一个 ID 即可打通用户。name 仅用于展示为可选,每次有效登录时,name 会覆盖当前值。

Request body:

{
  "jwt": "JWT token"
}

其中 JWT token 的 payload 形如:

{
  "sub": "1234567890",
  "name": "John Doe"
}

返回的 session 既在鉴权部分提到的 X-LC-Session header 值:

{
  "sessionToken": "random session token"
}

为了防止重放攻击,服务端在签发 JWT token 时应带上 jtiexp 等保留字段。

匿名登录(游客账号登录)

随机生成一个 UUID 并构造以下结构的 authData 即可实现游客登录,该方式适用于用户还没有 / 无法登录您应用的情况下提交与追踪工单的进展:

{
  "anonymousId": "random UUID"
}

Response:

{
  "sessionToken": "random session token"
}

客户端应在本地持久化该匿名 ID,如果该 ID 丢失,用户将无法追踪之前提交的工单。

分类

分类(Category)是工单系统的核心,是用户接触到的第一个概念,其他所有的概念都是围绕分类组织的。

产品

分类是一个树形结构,可以在后台分类设置中直观的看到一颗或多颗分类树。树的根节点是一种特殊的分类,称为产品(Product)。不同的产品之间从用户视角看是独立的,一个用户(哪怕在产品之间通过 ID 对应为一个用户)在某个产品的用户端只会看到某个产品下的分类,查到某个产品下的工单列表,收到某个产品下的未读提醒。同时从客服的视角不同的产品之间是互通的,不同产品的分类可以共享客服、知识库、自动化流程等各种资源。

「产品」是一个很具体的名词,但其实这是一个虚拟的概念,用「产品」这个名字命名仅仅是为了方便理解。实际上除了真正的产品,其他维度,只要它们需要在客户端有不同的分类方式,都可以抽象为「产品」来区分。比如不同的平台可以是不同的「产品」,不同的地区也可以是不同的「产品」。

获取分类

我们可以通过 GET /api/2/products/{pid}/categories 获取某个产品下的分类列表。分类的 schema 请参照 API 文档。需要指出的是,这个 API 返回的是一个拍平的数组,需要通过分类的 parentIdposition 来构造树。

因为需要构建树,这个 API 不支持分页。在不加参数的情况下默认会返回所有的分类,包括已禁用的,如果只需要启用状态的分类可以加上 active=true 参数。但是考虑到用户的历史工单中可能存在已被禁用的分类,建议用户端获取所有的分类作为全局状态存储,在展示分类列表时再过滤掉禁用的分类。

工单

生命周期

工单的 schema 请参见 API 文档。其中最重要的属性是工单状态。工单状态有以下可选值。

ENUM 描述
NEW 50 新工单,还没有客服回复
WAITING_CUSTOMER_SERVICE 120 等待客服回复
WAITING_CUSTOMER 160 等待用户回复
TO_BE_CONFIRMED 220 待用户确认解决(客服点击「认为已解决」时会设置该状态)
FULFILLED 250 已解决
CLOSED 280 已关闭

工单的完整状态机描述如下。其中「用户确认解决」(TO_BE_CONFIRMED / FULFILLED 两个状态)是可选的,我们推荐让用户来确认问题解决并关闭工单,但如果确实不需要该功能可以无视这两个状态,简化为只用 CLOSED 状态。

https://sketchviz.com/@leeyeh/bf19ae9be26aa29c8b16b3d4f6721556/6c3065a73860a2b2da2b726807706597cd5b4a4f.sketchy.png

虽然状态众多,但在用户端从用户的视角这些状态可以分为三类:

  • [0, 150): 等待客服跟进,无需关注
  • [150, 240): 需要用户关注,提供更多信息或确认解决
  • >=240 : 流程结束,无需关注

建议给不同类型的状态使用不同的视觉设计以区分。

创建工单

创建工单的 API POST /api/2/tickets 请参见 API 文档。

自定义字段与表单

创建工单时支持不同的分类展示不同的表单。我们可以通过 GET api/2/categories/{id}/fields 获取到需要后台配置的自定义表单字段。

字段的数据结构参见 API 文档中的 TicketField schema。

表单字段有以下 6 种类型:

Type 描述
text 单行文本
multi-line 多行文本
dropdown 下拉选择
multi-select 多选 Radio
radios 单选 Radia
file 文件上传

不支持类型扩展,如果想要支持特殊类型控件,可以在在字段名称中约定特殊标记,客户端根据此标记渲染特殊控件。

除了需要用户提交的字段,有时候我们会想要上报一些用户不可见的数据,比如设备相关的信息。可以在后台创建「客户不可见」的自定义字段,然后在创建工单时自动填充带上。

展示工单

一个工单的详情页面的内容主要由以下几部分信息组成。

其中基本必须要展示的有:

  • 工单基本信息 GET /api/2/tickets/{id},包括了 ID、标题、详细描述、状态、分类、评价等
  • 回复

可选展示的信息有:

  • 操作记录
  • 用户提交的字段 GET /api/1/ticket-fields?ids={ids}&includeVariant=true&locale={locale} (这个 API 待迁移到 v2)

以下待更新

展示工单列表

GET /api/2/tickets

分页

支持通过页码分页:通过 page (默认值: 1) 和 page_size (默认值: 10) 控制页数和每页的数量。

也支持通过工单的创建时间作为条件向后翻页(即加载更多模式)。

过滤

GET /api/1/tickets/{id}/ops-logs 获取操作日志列表

分页

通过 q.created_at 分页, 一次最多返回 500 条数据

Example

获取创建时间晚于 2021-05-06T05:00:00.000Z 的操作日志:

GET /api/1/tickets/{id}/ops-log?q=created_id:>2021-05-06T05:00:00.000Z

Action

描述
selectAssignee 系统分配负责人
changeCategory 修改分类
changeAssignee 修改负责人
replyWithNoContent 客服认为当前工单无需回复
replySoon 客服认为处理工单需要时间, 稍后再回复
resolve 用户或客服认为工单已解决
close 用户或客服关闭了工单
reject 已弃用, 用户或客服关闭了工单

Response

[
  {
    "id": "日志ID",
    "action": "selectAssignee",
    "operator_id": "xxx",
    "assignee_id": "xxx",
    "category_id": "xxx",
    "created_at": "2021-05-06T05:35:54.441Z"
  }
]

PATCH /api/1/tickets/{id} 修改工单

可更新的字段

字段 类型 描述
assignee_id string 更新工单负责人, 仅客服可用
category_id string 更新工单分类, 仅客服可用
organization_id string 更新工单所属组织, 设置为空字符串来取消关联。仅客服可用
tags { key: string; value: string } 仅客服可用
private_tags { key: string; value: string } 仅客服可用
evaluation { star: 0 | 1; content: string, selections: string[] } selections 是评价的选项,仅工单作者可用
subscribed boolean 仅客服可用
unread_count 0 将未读操作数量置空

Response

{}

POST /api/1/tickets/{id}/operate 操作工单

Action

描述
replyWithNoContent 无需回复
replySoon 稍后回复
resolve 设置为已解决
close 关闭工单
reopen 重新打开工单

Request

{
  "action": "replyWithNoContent"
}

Response

{}

工单更新通知(小红点)

工单更新通知追踪用户创建或关注的工单是否有更新,因此以下所有 API 都需要登录状态。

GET /api/2/unread 是否有未读通知

返回 boolean 类型的 true / false,主要用于显示小红点。

GET /api/2/notifications 获取更新通知列表

Params

{
  unread: 1 | undefined; // 是否只返回未读的通知
  before: string; // ISO 时间戳,配合返回值中的 latestActionAt 翻页
  limit: number; // 每页条数,默认 25
  includeTicketMetaKeys: string[]; // 需要在返回值中携带的工单 metaData 字段
}

Response

{
  id: string; // 通知 id
  ticket: Ticket;
  unreadCount: number;
  latestAction: "reply" |
    "changeStatus" |
    "changeAssignee" |
    "ticketEvaluation" |
    "newTicket"; // 最近一次更新操作的类型,对于用户侧只需要关心前两个
  latestActionAt: string; // 最后一次更新的时间,配合 before 参数可实现翻页
}

在用户点击进入工单详情页后,请求工单详情 API 会自动将该工单通知的已读状态改为已读,也可以通过下面这个 API 批量将所有未读通知标为已读:

POST /api/2/notifications/read-all

回复

Schema

{
  objectId: string,

  ticket: Pointer<Ticket>, // 所回复的工单
  content: string,
  content_HTML: string
  author: Pointer<User>, // 提交该回复的用户
  isCustomerService: boolean, // 提交该回复的用户是否为客服

  createdAt: string, // UTC timestamp
  updatedAt: string, // UTC timestamp
}

POST /api/1/tickets/{id}/replies 回复工单

Request

{
  "content": "回复内容",
  "file_ids": ["回复附件 ID"]
}

Response:

{
  "id": "回复 ID",
  "created_at": "2021-05-06T05:38:26.126Z"
}

GET /api/1/tickets/{id}/replies 获取回复列表

分页

通过 q.created_at 分页, 一次最多返回 500 条数据

Example

获取创建时间晚于 2021-05-06T05:00:00.000Z 的回复:

GET /api/1/tickets/{id}/replies?q=created_at:>2021-05-06T05:00:00.000Z

Response

[
  {
    "id": "回复 ID",
    "nid": "回复 Number ID",
    "author_id": "回复作者 ID",
    "author": {
      // 回复作者的详细信息
    },
    "content": "回复内容",
    "content_HTML": "回复内容 (HTML 格式)",
    "file_ids": ["回复附件 ID"],
    "files": [
      // 回复附件
    ],
    "is_customer_service": true, // 该回复作者是否是由客服
    "created_at": "2021-05-06T05:36:22.415Z"
  }
]

常见问题(FAQ)

这部分已经重新设计,请参考: https://demo.leanticket.cn/docs/2/#/Knowladge%20base

文件上传

上传到工单文件服务

文件需要通过对应平台的存储 SDK 上传(推荐直接使用客户端 SDK,也提供服务端 SDK 上传)。SDK 的安装与用法参见 LeanCloud 文档:

上传到第三方文件服务