SupDesk SDK 正式发布
任何成熟的产品最终都会编写一层集成逻辑——从收件箱读取数据的脚本、同步看板的 cron 任务、或是从自定义 UI 提交反馈的接口。当你坐在键盘前时,SupDesk 控制台非常高效;但只要你打算实现自动化,就必须依赖 API,而 API 的使用体验完全取决于与其交互的客户端 SDK。
今天,我们正式发布了四款 SDK。
SDK 阵容
- JavaScript / TypeScript — npm 上的
supdesk。无需修改即可运行在 Node 18+、Deno、Bun 和 Cloudflare Workers 上,零运行时依赖。 - Python — PyPI 上的
supdesk。包含同步客户端SupDesk与异步客户端AsyncSupDesk,两者共享同一个 httpx 核心。 - Go —
github.com/rabinapps/supdesk-go。仅依赖标准库,因此只要能运行 Go 1.23+ 的地方都可以编译——包括通过GOOS=wasip1运行在 Cloudflare Workers 上。 - Dart — pub.dev 上的
supdesk。基于 dio 构建,拦截器、CancelToken和代理适配器均能如预期般工作。
安装
npm install supdeskpip install supdeskgo get github.com/rabinapps/supdesk-godependencies:
supdesk: ^0.1.0一个 API,四种语言
这些 SDK 并非四个独立的包装器,而是跨所有语言具备统一语义的同一套 API。只要学会了一种语言中的读取提交操作,就能在全部四种语言中熟练运用。
自动分页。 list() 返回一个同时支持异步迭代的页面对象——可以使用 for await 遍历所有页面,或者在只需首页数据时仅拉取第一页。
类型化资源。 提交、反馈、更新日志条目、消息、候补名单注册、Beta 计划与测试者、以及帮助中心文章和分类——每个资源均包含符合预期的 API 方法与类型化参数,拼写错误会在编译阶段直接报错,而非拖到线上环境。
类型化异常。 所有错误均继承自同一个基类——JS 和 Python 中的 SupDeskError、Go 中的 APIError、Dart 中的 SupDeskException——因此一个 catch 块即可捕获全部异常,同时依然支持通过 instanceof 或 errors.As 精确识别具体错误。
Webhook 签名校验。 constructEventFromRequest、construct_event_from_headers、ConstructEvent 和 constructEventFromHeaders 均能在常数时间内校验 SupDesk 签名,确保接收端处理的数据真实可靠。
内置容错与重试。 所有客户端均支持指数退避与抖动重试,遵循 Retry-After 响应头,且绝不会重复重试可能已被接收的计费 POST 请求——不稳定的网络绝不会导致用户的工单被重复提交。
专为服务端设计
SupDesk API 密钥拥有整个项目的操作权限,因此这些 SDK 拒绝在浏览器环境中运行——JS 和 Dart 的构造函数在检测到 DOM 或客户端构建环境时会直接抛出异常,README 中也明确解释了原因。请务必将密钥保存在服务端环境变量中,隐藏在你自己控制的接口之后。
还有两点需要说明:所有方案均支持读取操作,而写入操作(POST/PATCH/DELETE)则需要付费方案;此外,密钥作用域限定在项目级别,建议为每个环境配置独立的密钥,以便在撤销其中一个时不会影响其他环境。
SupDesk 的应用场景
这些 SDK 是 SupDesk SaaS 集成指南 的后端支撑部分——为希望在自家系统中嵌入支持功能的产品提供多租户支持、API 访问及 Webhook 机制。完整参考请查阅 API 文档。
开始使用
前往 工作区设置 → API 密钥 获取 API 密钥,安装适配你技术栈的客户端,并运行本页面的快速入门。随后可以克隆仓库并查阅 README。
用于后端集成的服务端SDK
从每个SDK的GitHub存储库动态加载的实时代码示例。
import { SupDesk } from "supdesk";
const supdesk = new SupDesk({ apiKey: process.env.SUPDESK_API_KEY! });
// Auto-pages: iterating walks every page for you.
for await (const submission of await supdesk.submissions.list({
status: "open",
})) {
console.log(submission.title);
}
await supdesk.submissions.create({
type: "bug",
title: "Export button does nothing",
email: "user@example.com",
body: "Clicking Export on the reports page has no effect.",
});from supdesk import SupDesk
supdesk = SupDesk() # api_key=... or $SUPDESK_API_KEY
# Auto-pages: iterating walks every page for you.
for submission in supdesk.submissions.list(status="open"):
print(submission.title)
supdesk.submissions.create(
type="bug",
title="Export button does nothing",
email="user@example.com",
body="Clicking Export on the reports page has no effect.",
)import "github.com/rabinapps/supdesk-go/supdesk"
client, err := supdesk.New(os.Getenv("SUPDESK_API_KEY"))
if err != nil {
log.Fatal(err)
}
page, err := client.Submissions.List(ctx, supdesk.SubmissionsListParams{
Status: ptr("open"),
})
if err != nil {
return err
}
// Auto-pages: All walks every page for you.
for sub, err := range page.All(ctx) {
if err != nil {
return err
}
fmt.Println(sub.Title)
}
_, err = client.Submissions.Create(ctx, supdesk.SubmissionsCreateParams{
Type: supdesk.SubmissionTypeBug,
Title: "Export button does nothing",
Email: "user@example.com",
Body: "Clicking Export on the reports page has no effect.",
})import 'dart:io';
import 'package:supdesk/supdesk.dart';
final supdesk = SupDesk(apiKey: Platform.environment['SUPDESK_API_KEY']!);
// Auto-pages: the stream walks every page for you.
final page = await supdesk.submissions.list(status: PostStatus.open);
await for (final submission in page.autoPaging()) {
print(submission.title);
}
await supdesk.submissions.create(
type: SubmissionType.bug,
title: 'Export button does nothing',
email: 'user@example.com',
body: 'Clicking Export on the reports page has no effect.',
);