Developers
Issues, projects, boards, cycles, and automations — every resource the app uses, exposed as a typed, versioned REST API with OAuth 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 personal API token for scripts, or OAuth 2.0 with PKCE for third-party apps acting on a user's behalf. Tokens are scoped (`issues:read`, `boards:write`, …) and revocable, on the same Clerk-backed identity that protects the product UI.
Subscribe to issue, comment, and automation events. Payloads are HMAC-SHA-256 signed so you can verify authenticity before acting.
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 '{
"team": "ENG",
"title": "Auth fails on Safari 17",
"priority": "high"
}'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?team=ENG&state=started&limit=2
Authorization: Bearer $PLANODA_TOKEN{
"data": [
{
"id": "iss_8f3a2c",
"identifier": "ENG-142",
"title": "Auth fails on Safari 17",
"state": { "name": "In Progress", "type": "started" },
"priority": "high",
"assignee": { "id": "usr_31a", "name": "Ada L." },
"createdAt": "2026-06-17T09:24:00Z"
}
],
"pageInfo": {
"hasNextPage": true,
"endCursor": "eyJjIjoiMTQyIn0"
}
}{
"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
{ "stateId": "wfs_done", "assigneeId": "usr_31a" }{
"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. Follow pageInfo.endCursor until hasNextPage is false — or let the SDK iterate for you.
for await (const issue of planoda.issues.listAll({
team: "ENG",
})) {
console.log(issue.identifier);
}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.