SDK TypeScript
@kanera/sdk é um cliente digitado para o API pública Kanera. Não tem dependências de tempo de execução e é construído em base global fetch, portanto, ele funciona inalterado no Node 18+, Bun, Deno, Cloudflare Workers e navegadores.
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." });
O SDK é publicado sob o MIT licença, deliberadamente mais permissiva que o servidor Kanera (Elastic License 2.0), portanto, incorporá-lo em seu próprio aplicativo não levanta questões de política de licença.
Autenticar
Crie uma chave de API em Configurações de perfil -> Chaves de API ou Configurações do espaço de trabalho -> APIe leia-o do ambiente em vez de confirmá-lo. Ver escolha um tipo de chave de API para qual tipo se adapta à sua integração.
const kanera = new Kanera({ apiKey: process.env.KANERA_API_KEY! });
Auto-hospedagem? Aponte o cliente para sua própria implantação:
const kanera = new Kanera({ apiKey, baseUrl: "https://api.your-kanera.example" });
Verifique o escopo primeiro
const { scope } = await kanera.session();
read significa que cada mutação será recusada com um FORBIDDEN erro. Verificar uma vez na inicialização é mais barato do que descobrir no meio de uma migração.
O que está no cliente
| Espaço para nome | Capas |
|---|---|
kanera.workspaces | Espaços de trabalho, seus quadros, membros e automações |
kanera.boards | Quadros acessíveis, abertura de quadro, cartões de tabuleiro e atividades; watch e unwatch |
kanera.lists | Operações de cartão em nível de lista: arquivar, mover, definir conclusão |
kanera.separators | Criar, estilizar, posicionar, mover e excluir separadores de lista de quadros |
kanera.cards | Cartões, mais .attachments, .checklistse .bulk; observação e lembretes (watch, unwatch, setReminder, clearReminder, listReminders, followState); pollNotifications |
kanera.comments | Comentários e reações do cartão |
kanera.runs | Execuções do agente: anunciar, pulsar e concluir o trabalho em andamento em um cartão |
kanera.notes | Notas, backlinks e anexos de notas |
kanera.search | Pesquise em conteúdo acessível |
kanera.work | Trabalho cruzado, histórico, programações, resumos cumulativos e próximas filas |
kanera.automations | Regras de automação do espaço de trabalho e histórico de execução digitado |
kanera.webhookEndpoints | Registre e gerencie webhooks de saída, inspecione entregas e tente novamente falhas |
kanera.http | O cliente de solicitação bruta, para qualquer coisa ainda não empacotada |
Referências de cartão
Em qualquer lugar que um cartão seja nomeado, passe um UUID, uma chave humana como MKT-42ou um URL de cartão canônico. As chaves são resolvidas uma vez por cliente e armazenadas em cache, portanto, repetir uma em uma operação em massa custa uma única pesquisa.
Um prefixo de chave é exclusivo apenas dentro de uma organização, e uma credencial pessoal pode ser vista por vários. Uma chave visível em mais de uma é rejeitado, não adivinhado - passe o URL canônico ou o UUID para desambiguar.
Cronogramas
Leia o mesmo horário de trabalho do Visualização da linha do tempo:
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 });
Durações, slackDays, conflito dayse summary.slipDays contar os dias úteis de segunda a sexta-feira. plannedStart / plannedEnd descreva o plano; as datas mais antigas/mais recentes descrevem o cálculo da dependência. A programação segue cadeias conectadas acessíveis em todos os tabuleiros e omite cartões concluídos e inacessíveis. Não aplica datas automaticamente aos sucessores.
Para um briefing pessoal, kanera.work.scheduleAttention({ limit: 10 }) retorna seções atribuídas de conflito, críticas e de início tardio. kanera.work.portfolio() retorna o rollup cruzado compatível com a integridade da programação por quadro. Eventos MCP são um protocolo hospedado separado dos auxiliares de webhook de saída comuns do SDK.
Relacionamentos de cartão
Ler, adicionar e remover dependências com os métodos de cartão digitado. As referências podem ser chaves, UUIDs ou URLs de cartões canônicos.
// 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 retorna apenas cartões que a credencial pode abrir. Bloqueadores ativos ocultos contribuem para hiddenBlockerCount. As adições são idempotentes; ciclos e cartões concluídos à medida que novos bloqueadores são rejeitados. Os filtros de consulta de trabalho também suportam blockedOnly: true para encontrar cartões abertos bloqueados em quadros acessíveis, e atRiskOnly: true para cartões abertos cujo bloqueador visível está vencido ou vencido depois deles. O preenchimento de um cartão por meio do SDK não exibe o aviso de trabalho bloqueado do aplicativo Web.
Gráficos de dependência
cards.dependencyGraph lê o mesmo gráfico limitado que a visualização da web. Escolha um quadro, área de trabalho ou conjunto de referências de cartão:
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);
}
O resultado inclui cartões, bordas, pesquisas, campos personalizados de face de cartão, tabuleiro canEdit bandeiras, e truncated. cards aceita chaves, UUIDs ou URLs canônicos e os resolve para a API cardIds. Um gráfico de quadro pode incluir quadros conectadas em outras quadros legíveis; inScope os distingue. Bloqueadores ativos ocultos permanecem contados blockedByCount, e o gráfico para em 400 cartões. Veja a API gráfica para detalhes de travessia.
Separadores de quadro
Separadores são itens de propriedade do quadro que compartilham a linha de ordenação de uma lista com os cartões. Suas âncoras digitadas podem ter como alvo qualquer tipo de item, portanto, uma integração pode criar uma seção exata e, em seguida, colocar cartões ao redor dela sem calcular posições numéricas.
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" });
Usar kanera.separators.move para reposicionar um separador em relação a um cartão, outro separador ou borda de lista. A exclusão de um separador deixa todos os cartões no lugar.
Lembretes e observação
A observação e os lembretes são de responsabilidade da pessoa conectada, portanto ela precisa de uma chave pessoal ou de uma conexão OAuth; chaves do espaço de trabalho recebem 403. Um lembrete é privado, no máximo um por cartão e com até oito semanas de antecedência.
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 substitui qualquer lembrete existente no cartão, incluindo aquele que já foi acionado. Veja Lembretes.
Para ouvir respostas e menções sem receber webhooks, pesquise sua caixa de entrada de notificação. Guarde o devolvido cursor e devolvê-lo como 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
Crie vários cartões
kanera.cards.bulk.create cria até 50 cartões em um quadro em uma solicitação, cada um com sua própria lista, rótulos, responsáveis e data de vencimento. Cada cartão é validado antes de qualquer ser criado, então passe um idempotencyKey e todo o lote pode ser tentado novamente com segurança.
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 leva o mesmo labelIds, assigneeIds, dueDateLocalDatee dueDateSlot para um único cartão.
Listas de verificação
Crie uma lista de verificação com todo o seu conteúdo em uma solicitação: itens com descrições, responsáveis e datas de vencimento, e um nível de sublistas de verificação em itens de nível superior. O servidor gera e conecta cada ID atomicamente e os retorna, então não há nada para procurar depois.
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");
Os itens da sub-lista de verificação levam apenas text e completede os itens se movem somente dentro de seu próprio grupo: entre listas de verificação de nível superior ou entre sublistas de verificação de um item pai. Um lote é totalmente validado antes que qualquer coisa seja escrita; um pedido rejeitado error.options.body.issues nomeia o campo exato, como items[0].subChecklists[0].items[1].texte os campos desconhecidos são rejeitados em vez de ignorados.
Execuções do agente
Quando sua integração for um agente trabalhando em um cartão, informe ao Kanera que ela foi iniciada. O bloco do cartão mostra um chip ativo nomeando o agente e o trabalho, os detalhes do cartão mostram a execução em andamento e o resultado do terminal passa para o feed de atividades. Ninguém precisa adivinhar se um trabalho ainda está em andamento.
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 leva succeeded, failed, ou cancelled. Um status de terminal encerra a execução e a torna imutável, portanto, update nele falha com um CONFLICT erro - verifique error.status === 409
se uma nova tentativa pudesse ocorrer após a sua finish.
Uma corrida ao vivo sem batimentos cardíacos por quinze minutos é marcada stalled por Kanera, então um agente travado não pode deixar um cartão mostrando “funcionando” para sempre. Uma execução paralisada não é finalizada: o agente ainda pode voltar e fechá-la ou definir status: "running" para retomar.
Usar kanera.runs.list(card) antes de começar para que dois agentes não executem o mesmo cartão, { includeEnded: true }
para a história completa e kanera.runs.get(runId) para reler uma execução.
Registrar webhooks
Um administrador gerencia cada endpoint em um workspace. Uma credencial com capacidade de gravação não administrativa ou um agente OAuth interativo também pode registrar endpoints, mas vê apenas aqueles que sua própria conexão criou. Isso é útil quando um agente precisa de retornos de chamada de evento sem autoridade de administrador do espaço de trabalho.
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);
Retorno de endpoints com escopo de conexão scope: "connection"; Endpoints criados pelo administrador retornam
scope: "workspace". O recurso também fornece list, update, rotateSecret, retryDeliverye delete. Veja Webhooks para verificação de carga útil e semântica de entrega.
Paginação
Cada ponto final da lista é limitado; não existe uma chamada do tipo "dê-me o quadro inteiro". O bug comum de integração é ler a página um e tratá-la como um conjunto completo, então a iteração é a forma padrão:
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);
Erros
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 carrega o HTTP status, erro estável de Kanera code, e guardas para os casos que valem a pena ramificar: isUnauthenticated, isForbidden, isNotFound, isRateLimited, isRetryable. Falhas de transporte aumentam KaneraConnectionError.
Novas tentativas e idempotência
Lê e DELETE/PUT são repetidas automaticamente em limites de taxa e falhas transitórias de upstream, com espera exponencial, jitter total e Retry-After honrado quando Kanera o envia.
As mutações não são repetidas, a menos que você forneça uma chave de idempotência, porque uma nova tentativa após uma falha ambígua criaria um segundo cartão ou postaria um segundo comentário. Forneça um e a API reproduzirá o primeiro resultado em vez de repetir a gravação:
await kanera.comments.create("MKT-42", { body: "Deployed." }, {
idempotencyKey: crypto.randomUUID(),
});
Verifique webhooks
parseWebhook verifica a assinatura e retorna o evento analisado ou lança. Ele lança em vez de retornar um booleano para que um manipulador não se esqueça de verificar antes de usar a carga útil:
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);
});
A assinatura cobre {timestamp}.{body}, e o carimbo de data/hora é verificado em uma janela de cinco minutos — configurável — para a reprodução vinculada. Ele usa Web Crypto, por isso funciona em ambientes de execução de trabalho e de borda, bem como em Node. Veja Webhooks para configuração do endpoint e catálogo de eventos.
Opções
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
});
Escotilha de fuga
A API se move mais rápido que a superfície digitada e nada fica protegido por um wrapper:
const result = await kanera.http.post<MyType>("/api/v1/some/new/endpoint", body);
Uma nota sobre navegadores
O pacote é executado em um navegador, mas usá-lo significa enviar uma chave de API ao cliente. Faça isso apenas em um contexto primário confiável. Para qualquer coisa voltada ao usuário, ligue para Kanera do seu servidor.