TypeScript SDK
@kanera/sdk 一个类型化的客户端 Kanera 公共 API。它没有运行时依赖性,并且构建在全局之上 fetch,因此它在 Node 18+、Bun、Deno、Cloudflare Workers 和浏览器上运行不变。
npm install @kanera/sdk
import { Kanera } from "@kanera/sdk";
const kanera = new Kanera({ apiKey: process.env.KANERA_API_KEY! });
const session = await kanera.session();
if (session.scope === "read") throw new Error("this credential cannot write");
for await (const card of kanera.work.iterateCards()) {
console.log(card.key, card.title);
}
await kanera.cards.setCompletion("MKT-42", true);
await kanera.comments.create("MKT-42", { body: "Shipped in 1.4.0." });
该SDK是根据以下许可发布的: 麻省理工学院 许可证,故意比Kanera服务器(弹性许可证2.0)更宽松,因此将其嵌入到您自己的应用程序中不会引起许可证政策问题。
认证
创建一个 API 密钥 配置文件设置 -> API 密钥 或 工作区设置 -> API,并从环境中读取它而不是提交它。看 选择 API 密钥类型 哪种适合你的融合。
const kanera = new Kanera({ apiKey: process.env.KANERA_API_KEY! });
指向客户端您自己的部署:
const kanera = new Kanera({ apiKey, baseUrl: "https://api.your-kanera.example" });
首先检查范围
const { scope } = await kanera.session();
read 意味着每个突变都会被拒绝 FORBIDDEN 在启动时检查一次比在迁移过程中发现它要便宜。
客户端上有什么
| 命名空间 | 封面 |
|---|---|
kanera.workspaces | 工作区、其看板、成员和自动化 |
kanera.boards | 无障碍看板、打开看板、看看板和活动; watch 以及 unwatch |
kanera.lists | 列表级卡片操作:存档、移动、集成完成 |
kanera.separators | 创建、样式、位置、移动和删除板列表分隔符 |
kanera.cards | 卡片,加上 .attachments, .checklists, 和 .bulk;观看和提醒(watch, unwatch, setReminder, clearReminder, listReminders, followState); pollNotifications |
kanera.comments | 卡片评论和反应 |
kanera.runs | 代理运行:宣布、检测信号并完成卡上的飞行工作 |
kanera.notes | 笔记、反向链接和笔记附件 |
kanera.search | 跨无障碍内容搜索 |
kanera.work | 跨板工作、历史记录、时间表、累积摘要和下一步队列 |
kanera.automations | 工作区自动化规则和类型化执行历史记录 |
kanera.webhookEndpoints | 注册和管理出站 webhook,检查交付,并重试失败 |
kanera.http | 原始请求客户端,对于任何尚未包装的内容 |
卡片参考
任何地方有一张卡被命名,传递一个 UUID,一个人类密钥,例如 MKT-42,或规范的卡 URL。每个客户端都会解析一次密钥并缓存,因此在批量操作中重复一个密钥会花费一次查找。
密钥前缀仅在组织内部是唯一的,而个人凭证可以看到多个。多个可见的关键是 拒绝,不是猜测 — 传递规范 URL 或 UUID 来消除歧义。
时间表
读取与下面相同的工作日时间表 时间轴视图:
const schedule = await kanera.work.schedule({ boardId }); // or { workspaceId }, never both
console.log(schedule.summary.projectedFinish, schedule.summary.slipDays);
const critical = schedule.cards.filter(card => card.critical);
const conflicts = schedule.cards.filter(card => card.conflicts.length > 0);
// This changes the card's saved dates; it requires write access.
await kanera.cards.update("MKT-42", {
startDateLocalDate: "2026-11-02",
dueDateLocalDate: "2026-11-06",
});
const after = await kanera.work.schedule({ boardId });
期限 slackDays, 冲突 days, 和 summary.slipDays 计算周一至周五工作日。 plannedStart / plannedEnd 时间表遵循跨板可访问的连接链,并省略已完成和不可访问的卡片。 它不会自动将日期应用到继承者。
个人简报, kanera.work.scheduleAttention({ limit: 10 }) 返回分配的冲突、关键和迟到部分。 kanera.work.portfolio() 返回带有每个看板调度运行状况的受支持的跨看板累积。 MCP 活动 是与 SDK 的普通出站 webhooks 助手分开的托管协议。
卡牌关系
读取、添加和删除 依赖性 引用可以是密钥、UUID 或标准卡片 URL。
// MKT-42 blocks WEB-18. Both cards must belong to the same workspace.
const relation = await kanera.cards.addRelation("MKT-42", "WEB-18", "blocks");
await kanera.cards.addRelation("WEB-18", "MKT-43", "blocked_by");
await kanera.cards.addRelation("WEB-18", "MKT-44", "relates_to");
const { relations, hiddenBlockerCount } = await kanera.cards.listRelations("WEB-18");
const detail = await kanera.cards.get("WEB-18"); // also includes relations
const waiting = await kanera.work.cards({ filters: { blockedOnly: true } });
await kanera.cards.removeRelation("MKT-42", relation.id);
listRelations 返回的仅是凭据可以打开的卡片。 隐藏的活跃阻止器有助于 hiddenBlockerCount。添加是幂等的;循环和完成的卡片作为新的阻挡者被拒绝。工作查询过滤器还支持 blockedOnly: true 找到在可访问的看板上被封锁的开放卡片,以及 atRiskOnly: true 通过 SDK 完成卡片不会显示 Web App 的"工作被阻止"警告,因为您的可见阻止程序已经过期或到期时间晚于该卡片。
依赖图
cards.dependencyGraph 读取与 Web 视图相同的有界图形。 选择一个看板、工作区或一组卡片引用:
const aroundCard = await kanera.cards.dependencyGraph({ cards: ["MKT-42"] });
const boardGraph = await kanera.cards.dependencyGraph({ boardId });
const workspaceGraph = await kanera.cards.dependencyGraph({ workspaceId });
for (const edge of aroundCard.edges) {
console.log(edge.sourceCardId, edge.kind, edge.targetCardId);
}
结果包括卡片、边缘、查找、卡片面自定义字段、板 canEdit 旗帜,以及 truncated. cards 接受密钥、UUID 或规范URL 并将其解析为 API cardIds。棋盘图可以包括其他可读棋盘上连接的卡片; inScope 隐藏的活性阻滞剂仍然通过 blockedByCount,图表停在 400 张卡片处。参见 图形 API 详细信息请参阅
板隔板
分隔符是看板所拥有的项目,与卡片共享列表的排序轨道。 它们的类型锚点可以针对任何类型的项目,因此集成可以创建一个精确的部分,然后在其周围放置卡片,而无需计算数值位置。
const separator = await kanera.separators.create(boardId, listId, {
title: "This week",
color: "blue",
anchor: { side: "before", item: { type: "card", id: cardId } },
});
await kanera.cards.move("MKT-42", {
listId,
anchor: { side: "after", item: { type: "separator", id: separator.id } },
});
await kanera.separators.update(separator.id, { title: "Next up", color: "green" });
使用 kanera.separators.move 可将分隔线移动到某张卡片、另一条分隔线或列表边缘附近。删除分隔线不会移动任何卡片。
提醒和观看
观看和提醒属于连接的人员,因此他们需要个人密钥或 OAuth 连接;工作区密钥接收 403。提醒是私人的,每张卡最多一次,最多提前八周。
await kanera.cards.watch("MKT-42");
const state = await kanera.cards.setReminder("MKT-42", new Date("2026-10-16T15:00:00+01:00"));
state.reminder?.remindAt; // "2026-10-16T14:00:00.000Z"
const pending = await kanera.cards.listReminders(); // soonest first, with each card's key, title and url
await kanera.cards.clearReminder("MKT-42");
setReminder 替换卡片上的任何现有提醒,包括已经触发的提醒。 参见 提醒事项.
要在不接收 webhook 的情况下了解回复和提及信息,请投票您的通知收件箱。 cursor 并将其作为 since:
let cursor: string | undefined;
const page = await kanera.cards.pollNotifications({ since: cursor });
for (const item of page.items) console.log(item.summary, item.card?.url);
cursor = page.cursor; // repeat while page.hasMore
创建多张卡片
kanera.cards.bulk.create 一个请求最多可以在一个板上创建50张卡片,每张卡片都有自己的列表、标签、受让人和到期日期。 每张卡片在创建之前都会被验证,因此请先通过一个验证。 idempotencyKey 整个批次可以安全地重试。
const { cards } = await kanera.cards.bulk.create(boardId, [
{ listId, title: "Draft the brief", labelIds: [labelId] },
{ listId, title: "Review with legal", assigneeIds: [userId], dueDateLocalDate: "2026-11-02" },
], { idempotencyKey: crypto.randomUUID() });
kanera.cards.create 采取相同的 labelIds, assigneeIds, dueDateLocalDate, 和 dueDateSlot 一个单一的卡。
检查清单
通过一个请求创建一个包含其全部内容的清单:项目与描述、分配者和到期日期,以及顶级项目下的一个子清单级别。 服务器生成并原子化连接每个 ID,并返回它们,因此之后不需要查找。
const plan = await kanera.cards.checklists.create("MKT-42", {
title: "Launch",
items: [
{
text: "Prepare release",
description: "Coordinate the release.",
assigneeId: userId,
dueDateLocalDate: "2026-12-01",
subChecklists: [{ title: "Verification", items: [{ text: "Run smoke tests" }, { text: "Confirm rollback" }] }],
},
{ text: "Announce launch" },
],
});
// Add items in order at a position (omit the anchor to append).
await kanera.cards.checklists.addItems("MKT-42", plan.id, [{ text: "Kickoff" }], { afterItemId: null });
// Different changes for chosen items, applied atomically.
const [verification] = plan.subChecklists;
await kanera.cards.checklists.updateItems("MKT-42", verification.items.map((item) => ({ itemId: item.id, changes: { completed: true } })));
// The same assignee or due date on every item of one checklist.
await kanera.cards.checklists.updateAllItems("MKT-42", plan.id, { assigneeId: userId });
// Read the card's checklists without the rest of card detail.
const { checklists } = await kanera.cards.checklists.list("MKT-42");
分项清单项目只能采取 text 以及 completed,并且项目仅在其自己的组内移动:顶级清单之间,或一个父项目的子清单之间。在写入任何内容之前,都会对批次进行完整验证;被拒绝的请求 error.options.body.issues 确切的字段名称,如 items[0].subChecklists[0].items[1].text,未知字段会被拒绝而不是被忽略。
代理运行
当您的集成是代理人正在处理卡片时,请告诉 Kanera 该卡片已开始处理。 卡片图块随后会显示一个实时芯片,其中标明代理人和作业的名称,卡片详细信息显示在飞行中运行的情况,终端结果会移动到活动源中,无需任何人猜测作业是否仍在运行。
const run = await kanera.runs.start("MKT-42", {
title: "Drafting launch copy",
summary: "Reading the brief and the last two campaigns.",
externalUrl: "https://github.com/acme/site/pull/318", // optional; where a person can watch it
});
// Every update is a heartbeat. Send one at least every 10 minutes on a long job.
await kanera.runs.heartbeat(run.id);
// Waiting on a person? Say so, and the card's chip turns amber.
await kanera.runs.update(run.id, { status: "blocked", summary: "Need sign-off on the pricing line." });
await kanera.runs.finish(run.id, "succeeded", { summary: "Draft posted as a comment." });
finish 采取 succeeded, failed, 或 cancelled。终端状态结束运行并使其不可变,因此稍后 update 上面的图片 CONFLICT 错误 — 检查 error.status === 409
如果重试可以在你自己的后面着陆 finish.
标记为十五分钟没有心跳的实时运行 stalled 由 Kanera停顿运行并不会结束:代理仍然可以返回并关闭它,或者设置一个新的运行状态,以便它继续运行。 status: "running" 恢复。
使用 kanera.runs.list(card) 开始之前,这样两个代理不会运行相同的卡, { includeEnded: true }
为了完整的历史,和 kanera.runs.get(runId) 重读一次。
注册网钩
管理员管理工作区中的每个端点。非管理员可写入的凭据或交互式 OAuth 代理也可以注册端点,但只能看到其自己创建的连接。当代理需要事件回调而无需工作区管理员权限时,这非常有用。
const endpoint = await kanera.webhookEndpoints.create(workspaceId, {
name: "Release agent",
url: "https://agent.example.com/kanera",
eventTypes: ["card:updated", "comment:created"], // [] means every event
});
// The secret is returned only now (and after rotateSecret).
await saveSecret(endpoint.secret);
const deliveries = await kanera.webhookEndpoints.deliveries(workspaceId, endpoint.id);
连接范围端点返回 scope: "connection";管理员创建的端点返回
scope: "workspace"。该资源还提供 list, update, rotateSecret, retryDelivery, 和 delete。参见 Webhook 用于有效载荷验证和交付语义。
分页
每个列表端点都有边界;没有"给我整个看板"调用。 常见的集成错误是读取第一页并将其视为整个集合,因此迭代是默认形式:
for await (const board of kanera.boards.iterate()) { /* … */ }
const firstFifty = await kanera.comments.iterate("MKT-42").all(50);
// Or drive the pages yourself.
let cursor: string | undefined;
do {
const page = await kanera.comments.list("MKT-42", { cursor });
cursor = page.nextCursor ?? undefined;
} while (cursor);
错误
import { KaneraApiError } from "@kanera/sdk";
try {
await kanera.cards.update("MKT-42", { title: "New" });
} catch (error) {
if (error instanceof KaneraApiError && error.isForbidden) {
// A read-only credential, or no Editor access to this board. Retrying will not help.
}
}
KaneraApiError 带有 HTTP status,Kanera的稳定误差 code,以及值得分支的案例的守卫: isUnauthenticated, isForbidden, isNotFound, isRateLimited, isRetryable。运输故障增加 KaneraConnectionError.
重复和同等效力
读取和 DELETE/PUT 对速率限制和瞬态上游故障自动重试,具有指数回退、全抖动和 Retry-After 当 Kanera 是的,是的。
除非您提供等效密钥,否则不会重试突变,因为在模糊失败后重试会创建第二张卡片或发布第二条评论。提供一个,API 重放第一个结果而不是重复写入:
await kanera.comments.create("MKT-42", { body: "Deployed." }, {
idempotencyKey: crypto.randomUUID(),
});
验证 webhook
parseWebhook 验证签名并返回解析事件,或者 throws。 它会抛出而不是返回布尔值,这样处理器就不会忘记在使用有效载荷之前进行检查:
import { parseWebhook } from "@kanera/sdk";
app.post("/kanera", async (req, res) => {
const event = await parseWebhook({
secret: process.env.KANERA_WEBHOOK_SECRET!,
payload: req.rawBody, // the exact bytes; re-serialised JSON will not verify
headers: req.headers,
});
// event.type, event.workspaceId, event.data
res.sendStatus(204);
});
签名封面 {timestamp}.{body},并根据五分钟窗口(可配置)检查时间戳以绑定重播。它使用 Web Crypto,因此它可以在工作线程和边缘运行时以及 Node.js 中运行。参见 Webhook 用于端点设置和事件目录。
备选方案
new Kanera({
apiKey,
baseUrl: "https://api.kanera.app", // your own origin when self-hosting
timeoutMs: 30_000,
maxRetries: 2,
organisationId, // pin an identity-wide personal credential to one organisation
userAgent: "acme-sync/2.1", // appended to the SDK's own User-Agent
onRetry: ({ attempt, delayMs }) => log.warn({ attempt, delayMs }),
fetch: myFetch, // supply your own agent or proxy
});
逃生闸
API 的移动速度比类型表面快,并且没有任何东西被封装在封装器后面:
const result = await kanera.http.post<MyType>("/api/v1/some/new/endpoint", body);
关于浏览器的说明
该软件包在浏览器中运行,但在浏览器中使用它意味着向客户端发送 API 密钥,只能在受信任的第一方上下文中进行。 对于面向用户的任何内容,请从服务器调用 Kanera。