Developers
Install once, autocomplete forever. The SDK is generated from the same schema as the API, so every endpoint is fully typed.
`@planoda/sdk` is typed against the same API the OpenAPI 3.1 spec at `/api/v1/openapi.json` describes, so every method, argument, and response is typed. Your editor knows the shape of an issue before you do.
Writes carry idempotency keys, transient failures retry with exponential backoff and jitter, and rate-limit responses surface `RateLimit-*` headers so you can back off precisely. The defaults are production-safe; every knob is overridable.
Node, Bun, Deno, edge runtimes, and the browser (with a scoped token). The SDK targets the platform `fetch`, so there is no runtime-specific build — the same API surface across every environment.
Import `@planoda/sdk/webhooks` to verify inbound webhook signatures in one call — the same HMAC-SHA-256 scheme the dashboard uses — so your endpoint can trust a payload before it acts on it.
Structured error classes map one-to-one with API failures, so you can branch on rate limits, validation, and auth distinctly — no string matching. Each error carries the API `code`, the request id, and field-level `details`.
import { createPlanodaClient } from "@planoda/sdk";
const planoda = createPlanodaClient({
auth: { kind: "bearer", token: process.env.PLANODA_TOKEN! },
});
const issue = await planoda.issues.create({
teamId: "team_8f3a…",
title: "Ship the SDK",
priority: 2, // high
});
console.log(issue.number); // 142Full reference in the docs.
Start building
Free tier includes API access. Build, automate, and integrate.
SDK in short
Install, initialize once with a scoped token, and the whole API is typed under planoda.*.
npm i @planoda/sdk
# or: pnpm add @planoda/sdk
# or: bun add @planoda/sdkimport { createPlanodaClient } from "@planoda/sdk";
export const planoda = createPlanodaClient({
auth: { kind: "bearer", token: process.env.PLANODA_TOKEN! },
// optional knobs:
retries: 3,
timeoutMs: 15_000,
});const issue = await planoda.issues.create({
teamId: "team_8f3a…",
title: "Auth fails on Safari 17",
priority: 2, // high
labels: ["bug", "auth"],
}, {
idempotencyKey: "create-auth-bug-9f1c",
});
console.log(issue.identifier); // ENG-143// One page, with the typed cursor:
const page = await planoda.issues.list({
team: "ENG",
state: "started",
limit: 50,
});
// Or stream every page via an async iterator:
for await (const issue of planoda.issues.listAll({
team: "ENG",
})) {
console.log(issue.identifier, issue.title);
}Register a webhook, then verify inbound deliveries with the bundled verifier — the same HMAC scheme the dashboard uses.
import { verifyWebhook } from "@planoda/sdk/webhooks";
await planoda.webhooks.create({
url: "https://app.example.com/hooks/planoda",
events: ["issue.created", "issue.updated", "comment.added"],
});
// In your route handler:
const ok = await verifyWebhook({
payload: await req.text(),
header: req.headers.get("x-planoda-signature")!,
secret: process.env.PLANODA_WEBHOOK_SECRET!,
});import {
PlanodaRateLimitError,
PlanodaValidationError,
} from "@planoda/sdk";
try {
await planoda.issues.create(input);
} catch (err) {
if (err instanceof PlanodaRateLimitError) {
await sleep(err.retryAfterMs);
} else if (err instanceof PlanodaValidationError) {
console.error(err.code, err.details);
} else throw err;
}FAQ
Everything you need to build on Planoda.