Developers
Issues, projects, boards, cycles, and automations — every resource the app uses, exposed as a typed, versioned REST API with scoped API keys and webhooks.
Every resource — issues, projects, boards, cycles, comments — maps to a stable REST endpoint with the same shape the app uses. Responses are JSON; errors are structured with a stable `code`, a human `message`, and field-level `details`.
The product runs on tRPC v11; the public REST surface is generated from the same routers, so both speak an identical schema. Use REST from any language, or import the tRPC types in TypeScript for end-to-end inference with zero drift.
A machine-readable spec lives at `/api/v1/openapi.json` — generated from the routers on every deploy, never written by hand. Point your codegen at it for typed clients in any language, or load it into Postman, Insomnia, or an MCP-aware agent.
Authenticate with a static API key — `Authorization: Bearer ttm_<prefix>_<secret>` — minted and revoked in Settings → API keys. Keys are scoped (`issues:read`, `issues:write`, …), on the same Clerk-backed identity that protects the product UI. The REST API does not support OAuth; the separate MCP server supports OAuth 2.1 for agent clients.
Subscribe to issue, comment, and automation events. Payloads are HMAC-SHA-256 signed so you can verify authenticity before acting.
`@planoda/cli` wraps this same REST API for scripting. `npx @planoda/cli triage "…"` AI-triages a backlog with no account — it calls the public triage endpoint directly, no key required. Authenticated commands (`issue create`, `issue list`, `whoami`) use the identical bearer-token API key as the REST calls above.
curl https://planoda.com/api/v1/issues \
-H "Authorization: Bearer $PLANODA_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 9f1c-create-auth-bug" \
-d '{
"teamId": "<team-uuid>",
"title": "Auth fails on Safari 17",
"priority": 2
}'Full reference in the docs.
Start building
Free tier includes API access. Build, automate, and integrate.
API in short
A slice of the surface. The full, always-current catalog is in the OpenAPI spec at /api/v1/openapi.json.
/api/v1/issuesList issues (cursor-paged)/api/v1/issuesCreate an issue/api/v1/issues/{id}Fetch one issue/api/v1/issues/{id}Update an issue/api/v1/projectsList projects/api/v1/webhooksRegister a webhookGET /api/v1/issues?teamId=<team-uuid>&limit=2
Authorization: Bearer ttm_<prefix>_<secret>{
"data": [
{
"id": "019f8b13-6c44-7a7f-aa3f-a466b099eaca",
"number": 142,
"title": "Auth fails on Safari 17",
"teamId": "019f8611-375b-793f-9ea5-eef5a0a2bf44",
"workflowStateId": "019f8611-3a02-7c31-9f0e-2b7c4d1e5a90",
"priority": 2,
"assigneeId": "019f8611-38dd-7932-bf4d-74796dc113ed",
"createdAt": "2026-06-17T09:24:00.000Z"
}
],
"nextCursor": "eyJyYW5rIjoiMTQyIn0"
}{
"id": "iss_9b71e4",
"identifier": "ENG-143",
"title": "Auth fails on Safari 17",
"priority": "high",
"url": "https://planoda.com/acme/issue/ENG-143"
}PATCH /api/v1/issues/iss_9b71e4
Authorization: Bearer $PLANODA_TOKEN
Content-Type: application/json
{ "workflowStateId": "<workflow-state-uuid>" }{
"error": {
"code": "validation_failed",
"message": "priority must be one of none|low|medium|high|urgent",
"details": [{ "path": "priority", "issue": "invalid_enum" }],
"requestId": "req_4c2f…"
}
}Enforced per token and per route. Inspect the headers on every response and back off on Retry-After when you see a 429.
RateLimit-Limit: 600
RateLimit-Remaining: 581
RateLimit-Reset: 41
Retry-After: 41 # only on 429Cursor-based. Read nextCursor from the response and pass it back as ?cursor= until it is absent — or let the SDK iterate for you.
for await (const issue of planoda.issues.listAll({
teamId: "<team-uuid>",
})) {
console.log(issue.number);
}@planoda/cli wraps this same REST API for scripting. Its triage command needs no account at all — the fastest way to feel the product before you ever create a workspace.
$ npx @planoda/cli triage "Fix the flaky login test" "Add dark mode" "Typo in footer"
urgent Bug 3pt Fix the flaky login test
medium Feature 5pt Add dark mode
low Chore 1pt Typo in footerPipe a backlog file in instead: cat backlog.txt | npx @planoda/cli triage -
npm i -g @planoda/cli
# or run ad-hoc: npx @planoda/cli <command>export PLANODA_API_KEY=<your_api_key> # Settings → API keys
planoda whoami
planoda issue create --team <teamId> --title "Ship the CLI" --priority 2
planoda issue list --limit 10 --jsontriage [tasks...]AI-triage a backlog — no account, reads stdin when piped with -issue create--team <id> --title <t> [--description <md>] [--priority 0-4] [--project <id>]issue list[--team <id>] [--limit <n>]whoamiShow the authenticated userhelp · versionHelp / version--api-key <key>API key (or env PLANODA_API_KEY)--base-url <origin>API origin (or env PLANODA_BASE_URL, default https://planoda.com)--jsonEmit raw JSON for scripting / CINeed more than the CLI covers? @planoda/sdk is the same API as a typed TypeScript client.
FAQ
The public REST surface is generated from the same tRPC v11 routers the product runs on — when the product gets faster, your scripts get faster too.
Everything you need to build on Planoda.