SDK de mecanografiado
@kanera/sdk es un cliente escrito para el API pública de Kanera. No tiene dependencias de tiempo de ejecución y está basado en global. fetch, por lo que se ejecuta sin cambios en Node 18+, Bun, Deno, Cloudflare Workers y 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." });
El SDK se publica bajo el MIT licencia, deliberadamente más permisiva que el servidor Kanera (Elastic License 2.0), por lo que incorporarla en su propia aplicación no plantea preguntas sobre la política de licencia.
autenticar
Cree una clave API en Configuración de perfil -> Claves API o Configuración del espacio de trabajo -> APIy leerlo del entorno en lugar de confirmarlo. Ver elija un tipo de clave API para qué tipo se adapta a tu integración.
const kanera = new Kanera({ apiKey: process.env.KANERA_API_KEY! });
¿Autohospedaje? Apunte al cliente a su propia implementación:
const kanera = new Kanera({ apiKey, baseUrl: "https://api.your-kanera.example" });
Primero verifique el alcance
const { scope } = await kanera.session();
read significa que cada mutación será rechazada con un FORBIDDEN error. Verificarlo una vez al inicio es más barato que descubrirlo a mitad de una migración.
¿Qué hay en el cliente?
| Espacio de nombres | Cubiertas |
|---|---|
kanera.workspaces | Espacios de trabajo, sus tableros, miembros y automatizaciones |
kanera.boards | Tableros accesibles, apertura de tablero, tarjetas de tablero y actividad; watch y unwatch |
kanera.lists | Operaciones de tarjeta a nivel de lista: archivar, mover, configurar finalización |
kanera.separators | Crear, diseñar, posicionar, mover y eliminar separadores de listas de tableros |
kanera.cards | Tarjetas, más .attachments, .checklists, y .bulk; observación y recordatorios (watch, unwatch, setReminder, clearReminder, listReminders, followState); pollNotifications |
kanera.comments | Comentarios y reacciones de la tarjeta. |
kanera.runs | Ejecuciones del agente: anuncio, latido y finalización del trabajo en vuelo en una tarjeta |
kanera.notes | Notas, vínculos de retroceso y archivos adjuntos de notas |
kanera.search | Buscar en contenido accesible |
kanera.work | Trabajo cruzado, historial, cronogramas, resúmenes y colas de Siguiente |
kanera.automations | Reglas de automatización del espacio de trabajo e historial de ejecución escrito |
kanera.webhookEndpoints | Registre y administre webhooks salientes, inspeccione entregas y reintente fallas |
kanera.http | El cliente de solicitud sin procesar, para cualquier cosa que aún no esté envuelta |
Referencias de tarjetas
En cualquier lugar donde se nombre una tarjeta, pase un UUID, una clave humana como MKT-42, o una URL de tarjeta canónica. Las claves se resuelven una vez por cliente y se almacenan en caché, por lo que repetir una en una operación masiva cuesta una única búsqueda.
Un prefijo clave es único sólo dentro de una organización, y una credencial personal puede ver varios. Una clave visible en más de uno es rechazado, no adivinado: pase la URL canónica o el UUID para eliminar la ambigüedad.
Horarios de la línea de tiempo
Leer el mismo horario de jornada laboral que el Vista de línea de tiempo:
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 });
Duraciones, slackDays, conflicto days, y summary.slipDays cuente los días laborables de lunes a viernes. plannedStart / plannedEnd describir el plan; Las fechas más tempranas/últimas describen el cálculo de dependencia. El cronograma sigue cadenas conectadas accesibles en todos los ámbitos y omite tarjetas completas e inaccesibles. No aplica automáticamente fechas a los sucesores.
Para una sesión informativa personal, kanera.work.scheduleAttention({ limit: 10 }) devuelve las secciones asignadas de conflicto, críticas y de inicio tardío. kanera.work.portfolio() devuelve el resumen cruzado admitido con el estado del cronograma por tablero. Eventos MCP son un protocolo alojado independiente de los ayudantes de webhook salientes habituales del SDK.
Relaciones de tarjetas
Leer, agregar y eliminar dependencias con los métodos de tarjeta mecanografiada. Las referencias pueden ser claves, UUID o URL de tarjetas canónicas.
// 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 devuelve sólo tarjetas que la credencial puede abrir. Los bloqueadores activos ocultos contribuyen a hiddenBlockerCount. Los agregados son idempotentes; Se rechazan ciclos y tarjetas completadas a medida que se rechazan nuevos bloqueadores. Los filtros de consulta de trabajo también admiten blockedOnly: true para encontrar tarjetas abiertas bloqueadas en tableros accesibles, y atRiskOnly: true para tarjetas abiertas cuyo bloqueador visible está vencido o vencido después de ellas. Al completar una tarjeta a través del SDK no se muestra la advertencia de trabajo bloqueado de la aplicación web.
Gráficos de dependencia
cards.dependencyGraph lee el mismo gráfico delimitado que la vista web. Elija un tablero, espacio de trabajo o conjunto de referencias de tarjetas:
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);
}
El resultado incluye tarjetas, bordes, búsquedas, campos personalizados de caras de tarjetas, tableros. canEdit banderas, y truncated. cards acepta claves, UUID o URL canónicas y las resuelve en las API cardIds. Un gráfico de tablero puede incluir tarjetas conectadas en otros tableros legibles; inScope los distingue. Los bloqueadores activos ocultos permanecen contados blockedByCount, y el gráfico se detiene en 400 cartas. Ver la API gráfica para detalles transversales.
Separadores de tableros
Los separadores son elementos propiedad de el tablero que comparten el carril de pedidos de una lista con las tarjetas. Sus anclajes escritos pueden apuntar a cualquier tipo de elemento, por lo que una integración puede crear una sección exacta y luego colocar tarjetas alrededor de ella sin calcular posiciones 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" });
uso kanera.separators.move para reposicionar un separador en relación con una tarjeta, otro separador o un borde de lista. Al eliminar un separador, todas las tarjetas permanecen en su lugar.
Recordatorios y observación
La visualización y los recordatorios son propiedad de la persona conectada, por lo que necesita una clave personal o una conexión OAuth; recibir claves del espacio de trabajo 403. Un recordatorio es privado, como máximo uno por tarjeta y con hasta ocho semanas de antelación.
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 reemplaza cualquier recordatorio existente en la tarjeta, incluido uno que ya se haya activado. Ver Recordatorios.
Para recibir respuestas y menciones sin recibir webhooks, sondee su bandeja de entrada de notificaciones. Mantener lo devuelto cursor y pasarlo de vuelta 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
Crea varias tarjetas
kanera.cards.bulk.create crea hasta 50 tarjetas en un tablero en una sola solicitud, cada una con su propia lista, etiquetas, asignados y fecha de vencimiento. Cada tarjeta se valida antes de crearla, así que pase una idempotencyKey y es seguro volver a intentar todo el lote.
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 toma lo mismo labelIds, assigneeIds, dueDateLocalDate, y dueDateSlot para una sola tarjeta.
Listas de verificación
Cree una lista de verificación con todo su contenido en una sola solicitud: elementos con descripciones, responsables y fechas de vencimiento, y un nivel de sublistas de verificación debajo de los elementos de nivel superior. El servidor genera y conecta cada ID de forma atómica y los devuelve, por lo que no hay nada que buscar después.
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");
Los elementos de la sublista de verificación solo toman text y completed, y los elementos se mueven solo dentro de su propio grupo: entre listas de verificación de nivel superior o entre las sublistas de verificación de un elemento principal. Un lote se valida en su totalidad antes de escribir algo; una solicitud rechazada error.options.body.issues nombra el campo exacto, como items[0].subChecklists[0].items[1].texty los campos desconocidos se rechazan en lugar de ignorarse.
El agente se ejecuta
Cuando su integración sea un agente trabajando en una tarjeta, informe a Kanera que comenzó. Luego, el mosaico de la tarjeta muestra un chip en vivo que nombra al agente y el trabajo, los detalles de la tarjeta muestran la ejecución en vuelo y el resultado del terminal pasa a la fuente de actividad. Nadie tiene que adivinar si un trabajo todavía existe.
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 toma succeeded, failed, o cancelled. El estado de un terminal finaliza la ejecución y la hace inmutable, por lo que un estado posterior update falla con un CONFLICT error - comprobar error.status === 409
si un reintento pudiera aterrizar después del tuyo finish.
Se marca una carrera en vivo sin latidos durante quince minutos. stalled por Kanera, por lo que un agente accidentado no puede dejar una tarjeta que muestre "funcionando" para siempre. Una ejecución detenida no finaliza: el agente aún puede regresar y cerrarla, o configurar status: "running" para reanudar.
uso kanera.runs.list(card) antes de comenzar para que dos agentes no ejecuten la misma tarjeta, { includeEnded: true }
para la historia completa, y kanera.runs.get(runId) para volver a leer una ejecución.
Registrar webhooks
Un administrador gestiona todos los puntos finales de un espacio de trabajo. Una credencial con capacidad de escritura que no sea de administrador o un agente OAuth interactivo también puede registrar puntos finales, pero solo ve los que creó su propia conexión. Esto es útil cuando un agente necesita devoluciones de llamadas de eventos sin autoridad de administrador del espacio de trabajo.
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);
Devolución de puntos finales con ámbito de conexión scope: "connection"; retorno de puntos finales creados por el administrador
scope: "workspace". El recurso también proporciona list, update, rotateSecret, retryDelivery, y delete. Ver Ganchos web para verificación de carga útil y semántica de entrega.
Paginación
Cada punto final de la lista está limitado; no hay ninguna llamada de "dame toda el tablero". El error de integración común es leer la página uno y tratarla como el conjunto completo, por lo que la iteración es la forma predeterminada:
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);
Errores
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 lleva el HTTP status, error estable de Kanera codey guardias para los casos en los que vale la pena ramificarse: isUnauthenticated, isForbidden, isNotFound, isRateLimited, isRetryable. Aumentan las averías en el transporte KaneraConnectionError.
Reintentos e idempotencia
Lee y DELETE/PUT se reintentan automáticamente en límites de velocidad y fallas transitorias en sentido ascendente, con retroceso exponencial, fluctuación total y Retry-After Es un honor que Kanera lo envíe.
Las mutaciones no se reintentan a menos que proporcione una clave de idempotencia, porque un reintento después de un error ambiguo crearía una segunda tarjeta o publicaría un segundo comentario. Proporcione uno y la API reproducirá el primer resultado en lugar de repetir la escritura:
await kanera.comments.create("MKT-42", { body: "Deployed." }, {
idempotencyKey: crypto.randomUUID(),
});
Verificar webhooks
parseWebhook verifica la firma y devuelve el evento analizado o lo lanza. Lanza en lugar de devolver un valor booleano para que el controlador no pueda olvidarse de comprobarlo antes de usar la 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);
});
La firma cubre {timestamp}.{body}, y la marca de tiempo se compara con una ventana de cinco minutos (configurable) para limitar la reproducción. Utiliza Web Crypto, por lo que funciona en tiempos de ejecución de trabajadores y de borde, así como en Node. Ver Ganchos web para la configuración de endpoints y el catálogo de eventos.
Opciones
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
});
Escotilla de escape
La API se mueve más rápido que la superficie escrita y nada está cerrado detrás de un contenedor:
const result = await kanera.http.post<MyType>("/api/v1/some/new/endpoint", body);
Una nota sobre los navegadores
El paquete se ejecuta en un navegador, pero usarlo allí significa enviar una clave API al cliente. Hágalo solo en un contexto propio y confiable. Para cualquier cosa relacionada con el usuario, llame a Kanera desde su servidor.