Passer au contenu principal

SDK TypeScript

@kanera/sdk est un client typé pour le API publique Kanera. Il n'a aucune dépendance d'exécution et est construit sur une base globale fetch, il fonctionne donc inchangé sur Node 18+, Bun, Deno, Cloudflare Workers et les navigateurs.

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

Le SDK est publié sous le MIT licence, délibérément plus permissive que le serveur Kanera (Elastic License 2.0), donc l'intégrer dans votre propre application ne soulève aucune question de politique de licence.

Authentifier​

Créez une clé API dans Paramètres du profil -> Clés API ou Paramètres de l'espace de travail -> API, et lisez-le à partir de l'environnement plutôt que de le valider. Voir choisissez un type de clé API pour quel type correspond à votre intégration.

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

Auto-hébergement ? Dirigez le client vers votre propre déploiement :

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

Vérifiez d'abord la portée​

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

read signifie que chaque mutation sera refusée avec un FORBIDDEN erreur. Vérifier une fois au démarrage coûte moins cher que de le découvrir à mi-chemin d'une migration.

Qu'y a-t-il sur le client​

Espace de nomsCouvertures
kanera.workspacesEspaces de travail, leurs tableaux, membres et automatisations
kanera.boardsTableaux accessibles, ouverture d'un tableau, fiches de tableau et activité ; watch et unwatch
kanera.listsOpérations sur les cartes au niveau de la liste : archiver, déplacer, terminer la définition
kanera.separatorsCréer, styliser, positionner, déplacer et supprimer des séparateurs de liste de tableaux
kanera.cardsCartes, plus .attachments, .checklists, et .bulk; visionnage et rappels (watch, unwatch, setReminder, clearReminder, listReminders, followState); pollNotifications
kanera.commentsCommentaires et réactions sur la carte
kanera.runsCourses d'agent : annoncer, battre et terminer le travail en vol sur une carte
kanera.notesNotes, backlinks et pièces jointes aux notes
kanera.searchRechercher dans le contenu accessible
kanera.workTravail transversal, historique, planifications, résumés de cumul et files d'attente suivantes
kanera.automationsRègles d'automatisation de l'espace de travail et historique d'exécution tapé
kanera.webhookEndpointsEnregistrez et gérez les webhooks sortants, inspectez les livraisons et réessayez les échecs.
kanera.httpLe client de requête brute, pour tout ce qui n'est pas encore enveloppé

Références des cartes​

Partout où une carte est nommée, transmettez un UUID, une clé humaine telle que MKT-42, ou une URL canonique de carte. Les clés sont résolues une fois par client et mises en cache, donc en répéter une lors d'une opération groupée coûte une seule recherche.

Un préfixe de clé est unique uniquement au sein d’une organisation, et un identifiant personnel peut en voir plusieurs. Une clé visible dans plusieurs est rejeté, pas deviné - transmettez l'URL canonique ou l'UUID pour lever l'ambiguïté.

Calendriers​

Lisez le même horaire de travail que le Vue chronologique:

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

Durées, slackDays, conflit days, et summary.slipDays comptez les jours ouvrables du lundi au vendredi. plannedStart / plannedEnd décrire le plan ; les dates au plus tôt/au plus tard décrivent le calcul de la dépendance. Le calendrier suit les chaînes connectées accessibles à tous les niveaux et omet les cartes complétées et inaccessibles. Il n’applique pas automatiquement les dates aux successeurs.

Pour un briefing personnel, kanera.work.scheduleAttention({ limit: 10 }) renvoie les sections affectées en cas de conflit, critiques et de démarrage tardif. kanera.work.portfolio() renvoie le cumul inter-cartes pris en charge avec l’état de la planification par carte. Événements MCP sont un protocole hébergé distinct des assistants de webhook sortants ordinaires du SDK.

Relations avec les cartes​

Lire, ajouter et supprimer dépendances avec les méthodes de cartes dactylographiées. Les références peuvent être des clés, des UUID ou des URL de cartes canoniques.

// 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 renvoie uniquement les cartes que l'identifiant peut ouvrir. Les bloqueurs actifs cachés contribuent à hiddenBlockerCount. Les ajouts sont idempotents ; les cycles et les cartes terminées à mesure que les nouveaux bloqueurs sont rejetés. Les filtres de requête de travail prennent également en charge blockedOnly: true pour trouver les cartes ouvertes bloquées sur les tableaux accessibles, et atRiskOnly: true pour les cartes ouvertes dont le bloqueur visible est en retard ou dû après elles. Le fait de remplir une carte via le SDK n'affiche pas l'avertissement de blocage de travail de l'application Web.

Graphiques de dépendance​

cards.dependencyGraph lit le même graphique borné que la vue Web. Choisissez un tableau, un espace de travail ou un ensemble de références de cartes :

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

Le résultat comprend des cartes, des bords, des recherches, des champs personnalisés de face de carte, un tableau canEdit des drapeaux, et truncated. cards accepte les clés, les UUID ou les URL canoniques et les résout en API cardIds. Un graphique de carte peut inclure des cartes connectées sur d'autres cartes lisibles ; inScope les distingue. Les bloqueurs actifs masqués restent comptés blockedByCount, et le graphique s'arrête à 400 cartes. Voir l'API graphique pour les détails du parcours.

Séparateurs de tableaux​

Les séparateurs sont des éléments appartenant au tableau qui partagent la voie de classement d'une liste avec les cartes. Leurs ancres tapées peuvent cibler n'importe quel type d'élément, de sorte qu'une intégration peut créer une section exacte, puis placer des cartes autour d'elle sans calculer de positions numériques.

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

Utiliser kanera.separators.move pour repositionner un séparateur par rapport à une carte, un autre séparateur ou un bord de liste. La suppression d'un séparateur laisse chaque carte en place.

Rappels et surveillance​

La surveillance et les rappels appartiennent à la personne connectée, elle a donc besoin d'une clé personnelle ou d'une connexion OAuth ; les clés de l'espace de travail reçoivent 403. Un rappel est privé, au maximum un par carte, et jusqu'à huit semaines à l'avance.

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 remplace tout rappel existant sur la carte, y compris celui qui a déjà été déclenché. Voir Rappels.

Pour entendre les réponses et les mentions sans recevoir de webhooks, interrogez votre boîte de réception de notification. Conservez le retour cursor et renvoyez-le comme 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

Créer plusieurs cartes​

kanera.cards.bulk.create crée jusqu'à 50 cartes sur un tableau en une seule demande, chacune avec sa propre liste, ses propres étiquettes, ses destinataires et sa propre date d'échéance. Chaque carte est validée avant qu'elle soit créée, alors passez un idempotencyKey et l'ensemble du lot peut être réessayé en toute sécurité.

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 prend la même chose labelIds, assigneeIds, dueDateLocalDate, et dueDateSlot pour une seule carte.

Listes de contrôle​

Créez une liste de contrôle avec tout son contenu dans une seule requête : éléments avec descriptions, destinataires et dates d'échéance, ainsi qu'un niveau de sous-listes de contrôle sous les éléments de niveau supérieur. Le serveur génère et connecte chaque identifiant de manière atomique et les renvoie, il n'y a donc rien à rechercher par la suite.

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

Les éléments de la sous-liste de contrôle prennent uniquement text et completed, et les éléments se déplacent uniquement au sein de leur propre groupe : entre les listes de contrôle de niveau supérieur ou entre les sous-listes de contrôle d'un élément parent. Un lot est entièrement validé avant que quoi que ce soit soit écrit ; une demande rejetée error.options.body.issues nomme le champ exact, tel que items[0].subChecklists[0].items[1].text, et les champs inconnus sont rejetés plutôt qu'ignorés.

L'agent s'exécute​

Lorsque votre intégration est un agent travaillant sur une carte, dites à Kanera qu'elle a commencé. La vignette de la carte affiche ensuite une puce en direct nommant l'agent et le travail, les détails de la carte montrent l'exécution en vol et le résultat du terminal est transféré dans le flux d'activité. Personne n’a besoin de deviner si un travail est toujours en cours.

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 prend succeeded, failed, ou cancelled. Un statut de terminal met fin à l'exécution et la rend immuable, donc un statut ultérieur update là-dessus, il échoue avec un CONFLICT erreur - vérifier error.status === 409 si une nouvelle tentative pouvait atterrir après la vôtre finish.

Une course en direct sans battement de coeur pendant quinze minutes est marquée stalled par Kanera, donc un agent en panne ne peut pas laisser une carte indiquant "travailler" pour toujours. Une exécution bloquée n'est pas terminée : l'agent peut toujours revenir et la fermer, ou définir status: "running" pour reprendre.

Utiliser kanera.runs.list(card) avant de commencer pour que deux agents n'exécutent pas la même carte, { includeEnded: true } pour l'histoire complète, et kanera.runs.get(runId) pour relire une course.

Enregistrer des webhooks​

Un administrateur gère chaque point de terminaison dans un espace de travail. Un identifiant non administrateur capable d'écrire ou un agent OAuth interactif peut également enregistrer des points de terminaison, mais ne voit que ceux créés par sa propre connexion. Ceci est utile lorsqu'un agent a besoin de rappels d'événements sans autorisation d'administrateur d'espace de travail.

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

Retour des points de terminaison au niveau de la connexion scope: "connection"; retour des points de terminaison créés par l'administrateur scope: "workspace". La ressource fournit également list, update, rotateSecret, retryDelivery, et delete. Voir Webhooks pour la vérification de la charge utile et la sémantique de livraison.

Pagination​

Chaque point de terminaison de liste est délimité ; il n'y a pas d'appel "donnez-moi tout le tableau". Le bug d'intégration courant consiste à lire la première page et à la traiter comme l'ensemble, donc l'itération est la forme par défaut :

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

Erreurs​

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 porte le HTTP status, l'erreur stable de Kanera code, et des gardes pour les cas qui méritent d'être abordés : isUnauthenticated, isForbidden, isNotFound, isRateLimited, isRetryable. Les pannes de transport augmentent KaneraConnectionError.

Nouvelles tentatives et idempotence​

Lit et DELETE/PUT sont réessayées automatiquement en cas de limites de débit et de défaillances transitoires en amont, avec une interruption exponentielle, une gigue totale et Retry-After honoré lorsque Kanera l'envoie.

Les mutations ne sont pas réessayées sauf si vous fournissez une clé d'idempotence, car une nouvelle tentative après un échec ambigu créerait une deuxième carte ou publierait un deuxième commentaire. Fournissez-en un et l'API rejoue le premier résultat au lieu de répéter l'écriture :

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

Vérifier les webhooks​

parseWebhook vérifie la signature et renvoie l'événement analysé, ou lance. Il lance plutôt que de renvoyer un booléen afin qu'un gestionnaire ne puisse pas oublier de vérifier avant d'utiliser la charge utile :

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

La signature couvre {timestamp}.{body}, et l'horodatage est vérifié par rapport à une fenêtre de cinq minutes - configurable - pour une relecture limitée. Il utilise Web Crypto, il fonctionne donc dans les environnements d'exécution Workers et Edge ainsi que dans Node. Voir Webhooks pour la configuration du point de terminaison et le catalogue d’événements.

Possibilités​

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

Trappe de secours​

L'API se déplace plus rapidement que la surface saisie, et rien n'est protégé par un wrapper :

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

Une note sur les navigateurs​

Le package s'exécute dans un navigateur, mais son utilisation signifie envoyer une clé API au client. Ne faites cela que dans un contexte de première partie fiable. Pour tout ce qui concerne l'utilisateur, appelez Kanera depuis votre serveur.