Zum Hauptinhalt springen

TypeScript-SDK

@kanera/sdk ist ein typisierter Client für die Öffentliche Kanera-API. Es hat keine Laufzeitabhängigkeiten und ist global aufgebaut fetch, läuft also unverändert auf Node 18+, Bun, Deno, Cloudflare Workers und Browsern.

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." });

Das SDK wird unter veröffentlicht MIT Lizenz, die bewusst freizügiger ist als der Kanera-Server (Elastic License 2.0), sodass die Einbettung in Ihre eigene Anwendung keine lizenzrechtlichen Fragen aufwirft.

Authentifizieren​

Erstellen Sie einen API-Schlüssel Profileinstellungen -> API-Schlüssel oder Arbeitsbereichseinstellungen -> API, und lesen Sie es aus der Umgebung, anstatt es festzuschreiben. Sehen Wählen Sie einen API-Schlüsseltyp welche Art zu Ihrer Integration passt.

const kanera = new Kanera({ apiKey: process.env.KANERA_API_KEY! });

Selbsthosting? Weisen Sie den Client auf Ihre eigene Bereitstellung hin:

const kanera = new Kanera({ apiKey, baseUrl: "https://api.your-kanera.example" });

Überprüfen Sie zunächst den Umfang​

const { scope } = await kanera.session();

read bedeutet, dass jede Mutation mit a abgelehnt wird FORBIDDEN Fehler. Eine einmalige Überprüfung beim Start ist günstiger als die Entdeckung mitten in der Migration.

Was steht auf dem Client?​

NamensraumAbdeckungen
kanera.workspacesArbeitsbereiche, ihre Boards, Mitglieder und Automatisierungen
kanera.boardsZugängliche Tafeln, Öffnen einer Tafel, Tafelkarten und Aktivitäten; watch und unwatch
kanera.listsKartenoperationen auf Listenebene: Archivieren, Verschieben, Satzvervollständigung
kanera.separatorsErstellen, formatieren, positionieren, verschieben und löschen Sie Trennzeichen für die Board-Liste
kanera.cardsKarten, plus .attachments, .checklists, und .bulk; Zuschauen und Erinnerungen (watch, unwatch, setReminder, clearReminder, listReminders, followState); pollNotifications
kanera.commentsKartenkommentare und Reaktionen
kanera.runsAgentenausführungen: Ankündigung, Heartbeat und Abschluss der Arbeit an einer Karte während des Flugs
kanera.notesNotizen, Backlinks und Notizanhänge
kanera.searchDurchsuchen Sie barrierefreie Inhalte
kanera.workBoardübergreifende Arbeit, Verlauf, Zeitpläne, Rollup-Zusammenfassungen und Up-Next-Warteschlangen
kanera.automationsAutomatisierungsregeln für den Arbeitsbereich und typisierter Ausführungsverlauf
kanera.webhookEndpointsRegistrieren und verwalten Sie ausgehende Webhooks, überprüfen Sie Zustellungen und wiederholen Sie Fehler
kanera.httpDer Rohanforderungsclient für alles, was noch nicht verpackt ist

Kartenreferenzen​

Überall dort, wo eine Karte benannt ist, übergeben Sie eine UUID, einen menschlichen Schlüssel wie z MKT-42oder eine kanonische Karten-URL. Schlüssel werden einmal pro Client aufgelöst und zwischengespeichert, sodass die Wiederholung eines Schlüssels in einem Massenvorgang eine einzige Suche kostet.

Ein Schlüsselpräfix ist nur innerhalb einer Organisation eindeutig, und ein persönlicher Berechtigungsnachweis kann mehrere sehen. Ein Schlüssel, der in mehr als einem sichtbar ist, ist abgelehnt, nicht erraten – übergeben Sie zur Eindeutigkeit die kanonische URL oder die UUID.

Zeitpläne​

Lesen Sie den gleichen Arbeitstagsplan wie der Zeitleistenansicht:

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 });

Dauer, slackDays, Konflikt days, und summary.slipDays zählen die Arbeitstage Montag–Freitag. plannedStart / plannedEnd Beschreiben Sie den Plan. früheste/späteste Daten beschreiben die Abhängigkeitsberechnung. Der Zeitplan folgt den zugänglichen verbundenen Ketten über alle Boards hinweg und lässt ausgefüllte und unzugängliche Karten aus. Termine werden nicht automatisch auf Nachfolger angewendet.

Für ein persönliches Briefing, kanera.work.scheduleAttention({ limit: 10 }) gibt zugewiesene Konflikt-, kritische und verspätete Abschnitte zurück. kanera.work.portfolio() gibt das unterstützte brettübergreifende Rollup mit dem Planzustand pro Board zurück. MCP-Veranstaltungen sind ein separates gehostetes Protokoll von den gewöhnlichen Outgoing-Webhook-Helfern des SDK.

Kartenbeziehungen​

Lesen, hinzufügen und entfernen Abhängigkeiten mit den getippten Kartenmethoden. Referenzen können Schlüssel, UUIDs oder kanonische Karten-URLs sein.

// 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 Gibt nur Karten zurück, die mit dem Berechtigungsnachweis geöffnet werden können. Versteckte aktive Blocker tragen dazu bei hiddenBlockerCount. Adds sind idempotent; Zyklen und abgeschlossene Karten, da neue Blocker abgelehnt werden. Die Arbeitsabfragefilter unterstützen ebenfalls blockedOnly: true um gesperrte offene Karten auf zugänglichen Brettern zu finden, und atRiskOnly: true für offene Karten, deren sichtbarer Blocker überfällig ist oder nach ihnen fällig ist. Beim Ausfüllen einer Karte über das SDK wird die Warnung der Web-App wegen blockierter Arbeit nicht angezeigt.

Abhängigkeitsdiagramme​

cards.dependencyGraph liest den gleichen begrenzten Graphen wie die Webansicht. Wählen Sie ein Board, einen Arbeitsbereich oder einen Satz Kartenreferenzen:

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);
}

Das Ergebnis umfasst Karten, Kanten, Suchvorgänge, benutzerdefinierte Kartenflächen und Tafeln canEdit Fahnen und truncated. cards akzeptiert Schlüssel, UUIDs oder kanonische URLs und löst sie in die APIs auf cardIds. Ein Board-Diagramm kann verbundene Karten auf anderen lesbaren Boards umfassen; inScope zeichnet sie aus. Versteckte aktive Blocker bleiben durchgezählt blockedByCount, und die Grafik stoppt bei 400 Karten. Siehe die Graph-API für Details zur Durchquerung.

Bretttrenner​

Trennzeichen sind Board-eigene Elemente, die die Bestellzeile einer Liste mit Karten teilen. Ihre typisierten Anker können auf beide Arten von Elementen abzielen, sodass eine Integration einen genauen Abschnitt erstellen und dann Karten darum herum platzieren kann, ohne numerische Positionen zu berechnen.

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" });

Benutzen kanera.separators.move um ein Trennzeichen relativ zu einer Karte, einem anderen Trennzeichen oder einem Listenrand neu zu positionieren. Durch das Löschen eines Trennzeichens bleibt jede Karte an Ort und Stelle.

Erinnerungen und Zuschauen​

Überwachung und Erinnerungen sind Sache der verbundenen Person, daher benötigt sie einen persönlichen Schlüssel oder eine OAuth-Verbindung; Arbeitsbereichsschlüssel erhalten 403. Eine Erinnerung ist privat, höchstens eine pro Karte und bis zu acht Wochen im Voraus.

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 ersetzt alle vorhandenen Erinnerungen auf der Karte, einschließlich einer bereits ausgelösten. Siehe Erinnerungen.

Um Antworten und Erwähnungen zu erfahren, ohne Webhooks zu erhalten, rufen Sie Ihren Benachrichtigungseingang ab. Bewahren Sie die zurückgegebene Ware auf cursor und gib es zurück als 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

Erstellen Sie mehrere Karten​

kanera.cards.bulk.create erstellt in einer Anfrage bis zu 50 Karten auf einem Board, jede mit eigener Liste, Beschriftungen, Beauftragten und Fälligkeitsdatum. Jede Karte wird validiert, bevor eine erstellt wird. Geben Sie also eine weiter idempotencyKey und der gesamte Stapel kann sicher erneut versucht werden.

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 nimmt das gleiche labelIds, assigneeIds, dueDateLocalDate, und dueDateSlot für eine einzelne Karte.

Checklisten​

Erstellen Sie eine Checkliste mit ihrem gesamten Inhalt in einer Anfrage: Elemente mit Beschreibungen, Beauftragten und Fälligkeitsterminen sowie eine Ebene von Unterchecklisten unter Elementen der obersten Ebene. Der Server generiert und verknüpft alle IDs atomar und gibt sie zurück, sodass es danach nichts mehr nachzuschlagen gibt.

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");

Es werden nur Unterpunkte der Checkliste übernommen text und completed, und Elemente werden nur innerhalb ihrer eigenen Gruppe verschoben: zwischen Checklisten der obersten Ebene oder zwischen den Unterchecklisten eines übergeordneten Elements. Ein Stapel wird vollständig validiert, bevor etwas geschrieben wird. eine abgelehnte Anfrage error.options.body.issues benennt das genaue Feld, z items[0].subChecklists[0].items[1].text, und unbekannte Felder werden eher abgelehnt als ignoriert.

Agent läuft​

Wenn es sich bei Ihrer Integration um einen Agenten handelt, der an einer Karte arbeitet, teilen Sie Kanera mit, dass der Vorgang begonnen hat. Auf der Kartenkachel wird dann ein Live-Chip angezeigt, der den Agenten und den Auftrag benennt, im Kartendetail wird der Fluglauf angezeigt und das Endergebnis wird in den Aktivitäts-Feed verschoben. Niemand muss raten, ob ein Job noch läuft.

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 dauert succeeded, failed, oder cancelled. Ein Endstatus beendet den Lauf und macht ihn unveränderlich, also später update darauf scheitert es mit a CONFLICT Fehler – prüfen error.status === 409 ob ein Wiederholungsversuch nach Ihrem eigenen landen könnte finish.

Ein Live-Lauf ohne Herzschlag für fünfzehn Minuten wird markiert stalled von Kanera, sodass ein abgestürzter Agent nicht für immer eine Karte hinterlassen kann, auf der „funktioniert“ steht. Eine blockierte Ausführung wird nicht beendet: Der Agent kann immer noch zurückkommen und sie schließen oder festlegen status: "running" fortfahren.

Benutzen kanera.runs.list(card) vor dem Start, damit nicht zwei Agenten dieselbe Karte ausführen, { includeEnded: true } für die vollständige Geschichte und kanera.runs.get(runId) einen Lauf noch einmal lesen.

Registrieren Sie Webhooks​

Ein Administrator verwaltet jeden Endpunkt in einem Arbeitsbereich. Ein Nicht-Administrator-Schreibzugriffsberechtigungsnachweis oder ein interaktiver OAuth-Agent kann ebenfalls Endpunkte registrieren, sieht aber nur diejenigen, die seine eigene Verbindung erstellt hat. Dies ist nützlich, wenn ein Agent Ereignisrückrufe ohne Workspace-Administratorberechtigung benötigt.

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);

Verbindungsbezogene Endpunkte werden zurückgegeben scope: "connection"; Vom Administrator erstellte Endpunkte werden zurückgegeben scope: "workspace". Die Ressource bietet auch list, update, rotateSecret, retryDelivery, und delete. Siehe Webhooks zur Nutzlastüberprüfung und Liefersemantik.

Paginierung​

Jeder Listenendpunkt ist begrenzt; Es gibt keinen „Gib mir das ganze Board“. Der häufigste Integrationsfehler besteht darin, Seite eins zu lesen und sie als den gesamten Satz zu behandeln, sodass die Iteration die Standardform ist:

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);

Fehler​

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 trägt das HTTP status, Kaneras stabiler Fehler code, und Wachen für die Fälle, auf die es sich zu verzweigen lohnt: isUnauthenticated, isForbidden, isNotFound, isRateLimited, isRetryable. Transportausfälle nehmen zu KaneraConnectionError.

Wiederholungsversuche und Idempotenz​

Liest und DELETE/PUT werden bei Ratenbegrenzungen und vorübergehenden Upstream-Ausfällen automatisch erneut versucht, mit exponentiellem Backoff, vollständigem Jitter usw Retry-After Ich fühle mich geehrt, wenn Kanera es schickt.

Mutationen werden nicht wiederholt, es sei denn, Sie geben einen Idempotenzschlüssel an, da ein erneuter Versuch nach einem mehrdeutigen Fehler eine zweite Karte erstellen oder einen zweiten Kommentar veröffentlichen würde. Geben Sie eins ein und die API spielt das erste Ergebnis ab, anstatt den Schreibvorgang zu wiederholen:

await kanera.comments.create("MKT-42", { body: "Deployed." }, {
idempotencyKey: crypto.randomUUID(),
});

Webhooks überprüfen​

parseWebhook überprüft die Signatur und gibt das analysierte Ereignis zurück oder löst es aus. Es wird ein boolescher Wert ausgegeben, anstatt einen booleschen Wert zurückzugeben, sodass ein Handler nicht vergessen kann, dies zu überprüfen, bevor er die Nutzlast verwendet:

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);
});

Die Signatur deckt ab {timestamp}.{body}, und der Zeitstempel wird anhand eines konfigurierbaren Fünf-Minuten-Fensters für die gebundene Wiedergabe überprüft. Es nutzt Web Crypto und funktioniert daher sowohl in Worker- und Edge-Laufzeitumgebungen als auch in Node. Siehe Webhooks für die Endpunkteinrichtung und den Ereigniskatalog.

Optionen​

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
});

Notluke​

Die API bewegt sich schneller als die typisierte Oberfläche und nichts ist hinter einem Wrapper geschützt:

const result = await kanera.http.post<MyType>("/api/v1/some/new/endpoint", body);

Ein Hinweis zu Browsern​

Das Paket läuft in einem Browser, aber wenn man es dort verwendet, muss man einen API-Schlüssel an den Client senden. Tun Sie dies nur in einem vertrauenswürdigen First-Party-Kontext. Für alles, was mit dem Benutzer zu tun hat, rufen Sie Kanera von Ihrem Server aus an.