Skip to content

Issues API

Issues are the core work items in kendo. Each issue belongs to a project and has a unique key (e.g., KD-42).

Permissions

ActionRequired PermissionScope
List / search / viewIssues: ReadIssues within accessible projects
CreateIssues: CreateIn accessible projects
UpdateIssues: Update (Own/All)Own: only issues you created. All: any issue in accessible projects
DeleteIssues: Delete (Own/All)Own: only issues you created. All: any issue in accessible projects

Admins and project owners bypass all permission checks for project-scoped resources.

Endpoints

MethodEndpointDescription
GET/api/projects/{projectId}/issuesList issues (capped at 500)
POST/api/projects/{projectId}/issuesCreate an issue
GET/api/projects/{projectId}/issues/{issueId}Get an issue
PUT/api/projects/{projectId}/issues/{issueId}Update an issue
DELETE/api/projects/{projectId}/issues/{issueId}Delete an issue
GET/api/projects/{projectId}/issues/{issueId}/blockingGet an issue's blocking dependencies
GET/api/projects/{projectId}/issues/search?q=Search issues (within project)
GET/api/issues/searchSearch issues (cross-project)
GET/api/issues/myList issues assigned to the authenticated user

Create Issue

POST /api/projects/{projectId}/issues

Request Fields

FieldTypeRequiredDescription
titlestringYesIssue title, max 255 characters
descriptionstringYesMarkdown description, max 65,535 characters
lane_idintegerYesBoard lane ID (must belong to the project)
priorityintegerYes0 Highest, 1 High, 2 Medium, 3 Low, 4 Lowest
typeintegerYes0 Feature, 1 Bug
assignee_idintegerNoUser ID (must be a project member)
sprint_idintegerNoSprint ID (must belong to the project)
epic_idintegerNoEpic ID (must belong to the project)
estimated_minutesintegerNoTime estimate in minutes, min 0
blocked_by_idsinteger[]NoIssue IDs that block this issue
blocks_idsinteger[]NoIssue IDs that this issue blocks
label_idsinteger[]NoLabel IDs to attach (must belong to the project)
promptstringNoAI prompt context, max 10,000 characters
bash
curl -X POST https://{tenant}.kendo.dev/api/projects/1/issues \
  -H "Authorization: Bearer your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Add pagination to issues list",
    "description": "The issues overview needs cursor-based pagination for large projects.",
    "lane_id": 1,
    "priority": 2,
    "type": 0,
    "assignee_id": 3,
    "estimated_minutes": 240
  }'
json
{
  "id": 42,
  "key": "KD-42",
  "title": "Add pagination to issues list",
  "description": "The issues overview needs cursor-based pagination for large projects.",
  "prompt": null,
  "user_id": 1,
  "assignee_id": 3,
  "project_id": 1,
  "lane_id": 1,
  "sprint_id": null,
  "epic_id": null,
  "comment_ids": [],
  "priority": 2,
  "type": 0,
  "estimated_minutes": 240,
  "blocked_by_ids": [],
  "blocks_ids": [],
  "branch_links": [],
  "label_ids": [],
  "created_at": "2026-03-13T10:30:00.000000Z",
  "token_total": 0,
  "token_capture_complete": true,
  "handed_to_claude_at": null
}

AI token fields

Two read-only fields report the AI token usage recorded against an issue. Both are derived at read time — there is no stored column, and neither is accepted on create or update.

FieldTypeMeaning
token_totalintegerSum of every recorded token category across the issue's token events. 0 when none — zero means no AI work was logged, not that the value is unknown.
token_capture_completebooleanfalse when at least one Claude session on the issue was not fully captured, meaning token_total understates the real spend. Treat the number as a lower bound and present it as such.

token_total is a floor even when token_capture_complete is true: some platform-side usage is not reported to us at all, so no issue's total includes it. Don't present the figure as a complete record of AI spend.

Not every issue-returning endpoint carries them. The search-shaped responses use a leaner resource that omits both fields, and only the capped endpoints report a meta.truncated flag:

EndpointToken fieldsmeta.truncated
Get Issueyesn/a — single object
List Issuesyesyes
Search Issues, and the board / backlog / recent / epic-issues / blocking tabsyesyes
Global Searchnoyes
My Issuesnoyes

There is no sprint- or epic-level rollup endpoint. Sprint and epic totals are not fields on those resources — kendo's own UI sums token_total across the issues it has already loaded. If you aggregate the same way, mind the two things the UI accounts for:

  • Capped collections. The capped endpoints stop at 500 issues and set meta.truncated; a sum over a truncated page is a floor, not a total.
  • Incomplete capture. Any issue with token_capture_complete: false makes the whole sum a floor too. The UI renders both cases identically, as at least N tokens.

Update Issue

PUT /api/projects/{projectId}/issues/{issueId}

Accepts the same fields as create. All required fields must be included in every update.

bash
curl -X PUT https://{tenant}.kendo.dev/api/projects/1/issues/42 \
  -H "Authorization: Bearer your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Add pagination to issues list",
    "description": "The issues overview needs cursor-based pagination for large projects.",
    "lane_id": 2,
    "priority": 1,
    "type": 0,
    "sprint_id": 5,
    "blocked_by_ids": [38, 40],
    "label_ids": [1, 2]
  }'
json
{
  "id": 42,
  "key": "KD-42",
  "title": "Add pagination to issues list",
  "description": "The issues overview needs cursor-based pagination for large projects.",
  "prompt": null,
  "user_id": 1,
  "assignee_id": 3,
  "project_id": 1,
  "lane_id": 2,
  "sprint_id": 5,
  "epic_id": null,
  "comment_ids": [],
  "priority": 1,
  "type": 0,
  "estimated_minutes": 240,
  "blocked_by_ids": [38, 40],
  "blocks_ids": [],
  "branch_links": [],
  "label_ids": [],
  "created_at": "2026-03-13T10:30:00.000000Z",
  "token_total": 0,
  "token_capture_complete": true,
  "handed_to_claude_at": null
}

Get Issue

GET /api/projects/{projectId}/issues/{issueId}

bash
curl https://{tenant}.kendo.dev/api/projects/1/issues/42 \
  -H "Authorization: Bearer your-token"
json
{
  "id": 42,
  "key": "KD-42",
  "title": "Add pagination to issues list",
  "description": "The issues overview needs cursor-based pagination for large projects.",
  "prompt": null,
  "user_id": 1,
  "assignee_id": 3,
  "project_id": 1,
  "lane_id": 2,
  "sprint_id": 5,
  "epic_id": null,
  "comment_ids": [10, 11],
  "priority": 1,
  "type": 0,
  "estimated_minutes": 240,
  "blocked_by_ids": [38, 40],
  "blocks_ids": [],
  "branch_links": [
    {
      "id": 7,
      "issue_id": 42,
      "branch_name": "feat/pagination",
      "branch_url": "https://github.com/acme/widgets/tree/feat/pagination",
      "status": 1,
      "created_by": 3,
      "created_at": "2026-03-13T10:45:00.000000Z",
      "creator": {"id": 3, "first_name": "Sam", "last_name": "Smith"},
      "project_github_repo": {
        "id": 4,
        "project_id": 1,
        "repo_full_name": "acme/widgets",
        "repo_url": "https://github.com/acme/widgets",
        "default_branch": "main",
        "is_primary": true,
        "owner": "acme",
        "repo_name": "widgets",
        "branch_links_count": 3
      }
    }
  ],
  "label_ids": [1, 4],
  "created_at": "2026-03-13T10:30:00.000000Z",
  "token_total": 0,
  "token_capture_complete": true,
  "handed_to_claude_at": "2026-03-13T11:00:00.000000Z"
}

Delete Issue

DELETE /api/projects/{projectId}/issues/{issueId}

Returns 204 No Content on success.

bash
curl -X DELETE https://{tenant}.kendo.dev/api/projects/1/issues/42 \
  -H "Authorization: Bearer your-token"

Blocking Dependencies

GET /api/projects/{projectId}/issues/{issueId}/blocking

Returns the issue's dependency graph neighbours — the union of the issues that block it (blocked_by) and the issues it blocks — as a lean, capped list. The dependencies are resolved directly, with no sprint or lane filter, so a blocker that lives in a completed sprint is still returned (unlike the sprint-scoped board/backlog lists). The endpoint issues no request shaping beyond the cap; it is intended for resolving the small set of dependency cards shown on an issue's detail and edit screens.

{issueId} accepts the numeric id or the issue key (e.g. KD-0042).

Each item is the lean issue shape (id, key, title, lane_id, sprint_id, plus the other list fields) — not the full issue resource. The response uses the shared capped-list envelope:

json
{
  "data": [
    {"id": 7, "key": "PA-0007", "title": "Migrate auth tables", "lane_id": 3, "sprint_id": 12},
    {"id": 9, "key": "PA-0009", "title": "Document rollout", "lane_id": 1, "sprint_id": null}
  ],
  "meta": {"truncated": false, "count": 2, "limit": 500}
}
bash
curl https://{tenant}.kendo.dev/api/projects/1/issues/42/blocking \
  -H "Authorization: Bearer your-token"

List Issues

GET /api/projects/{projectId}/issues

Returns the project's issues, most recently updated first, in the same {data, meta} envelope as /api/issues/search. Capped at 500 issues: when more match, meta.truncated is true and the remainder is not reachable through this endpoint — narrow the set with GET /api/projects/{projectId}/issues/search instead.

bash
curl https://{tenant}.kendo.dev/api/projects/1/issues \
  -H "Authorization: Bearer your-token"
json
{
  "data": [
    {
      "id": 42,
      "key": "KD-42",
      "title": "Add pagination to issues list",
      "priority": 1,
      "type": 0,
      "lane_id": 2,
      "assignee_id": 3,
      "sprint_id": 5,
      "...": "..."
    },
    {
      "id": 43,
      "key": "KD-43",
      "title": "Fix email validation on login form",
      "priority": 0,
      "type": 1,
      "lane_id": 1,
      "assignee_id": null,
      "sprint_id": null,
      "...": "..."
    }
  ],
  "meta": {
    "truncated": false,
    "count": 2,
    "limit": 500
  }
}

GET /api/issues/search

Search issues across all accessible projects. Admins can search all projects; non-admin users only see issues from projects they have access to.

Query Parameters

ParameterTypeRequiredDescription
querystringNoText search across title, description, and key (max 200 chars)
project_idintegerNoFilter by project ID
lane_idintegerNoFilter by lane ID
assignee_idintegerNoFilter by assignee user ID
sprint_idintegerNoFilter by sprint ID
epic_idintegerNoFilter by epic ID
priorityintegerNoFilter by priority (0-4, see enums below)
typeintegerNoFilter by type (0-1, see enums below)
exclude_final_lanebooleanNoExclude issues in each project's highest-order lane (typically Done)
limitintegerNoMax results to return (1-500, default 25)

Response Shape

Search responses use a {data, meta} envelope. The server caps result sets at 500 issues; when a query matches more, meta.truncated is true and callers should refine the filter set to see additional rows.

FieldTypeDescription
dataIssueSearchResource[]Matching issues (never more than meta.limit)
meta.truncatedbooleantrue when the result set was capped at meta.limit; refine filters to see more matches
meta.countintegerNumber of issues actually returned in data
meta.limitintegerServer-side cap applied to this request
bash
curl "https://{tenant}.kendo.dev/api/issues/search?query=login&priority=1&limit=10" \
  -H "Authorization: Bearer your-token"
json
{
  "data": [
    {
      "id": 42,
      "key": "KD-42",
      "title": "Fix login timeout bug",
      "project_id": 1,
      "project_name": "Backend",
      "lane_id": 2,
      "lane_title": "In Progress",
      "lane_color": 1,
      "epic_id": 7,
      "epic_title": "Authentication Overhaul",
      "user_id": 2,
      "assignee_id": 3,
      "priority": 1,
      "type": 1,
      "updated_at": "2026-03-13T10:30:00+00:00"
    }
  ],
  "meta": {
    "truncated": false,
    "count": 1,
    "limit": 10
  }
}

My Issues

GET /api/issues/my

List all issues assigned to the authenticated user across every project they can access, excluding issues in each project's final lane (typically Done). Returns up to 500 issues in the same {data, meta} envelope as /api/issues/search.

Query Parameters

None. Filtering is client-side.

bash
curl "https://{tenant}.kendo.dev/api/issues/my" \
  -H "Authorization: Bearer your-token"
json
{
  "data": [
    {
      "id": 42,
      "key": "KD-42",
      "title": "Fix login timeout bug",
      "project_id": 1,
      "project_name": "Backend",
      "lane_id": 2,
      "lane_title": "In Progress",
      "lane_color": 1,
      "epic_id": 7,
      "epic_title": "Authentication Overhaul",
      "user_id": 2,
      "assignee_id": 3,
      "priority": 1,
      "type": 1,
      "updated_at": "2026-03-13T10:30:00+00:00"
    }
  ],
  "meta": {
    "truncated": false,
    "count": 1,
    "limit": 500
  }
}

AI Story Generation

POST /api/projects/{projectId}/issues/agent-generate-story

Generates an AI-powered issue story using a multi-agent pipeline (Validate → Duplicate Check → Research → Classify → Write). Requires the generateStory permission. Rate-limited via throttle:story.

Real-time progress is broadcast via WebSocket on the user's private channel (Tenant.{tenantId}.App.Models.User.{userId}) as agent-progress events during processing.

Request Fields

FieldTypeRequiredDescription
descriptionstringYesThe input text describing the desired issue
contextstringNoAdditional context prepended to the description

Response Fields

FieldTypeDescription
titlestringAI-generated issue title
descriptionstringAI-generated issue description (Markdown)
typeintegerSuggested issue type (0 Feature, 1 Bug)
priorityintegerSuggested priority (0-4)
intentstringPipeline result intent: create, update, or respond
reasoningstring|nullExplanation when intent is update or respond
issue_idinteger|nullExisting issue ID when intent is update
pipelinearray|nullPipeline step summary (see below), null when empty

Pipeline Step Object

Each entry in the pipeline array describes the result of one agent step:

FieldTypeDescription
stepstringAgent name: validate, duplicate_check, research, classify, write
statusinteger0 Success, 1 Warning, 2 Skipped
messagestringHuman-readable result message (English)
bash
curl -X POST https://{tenant}.kendo.dev/api/projects/1/issues/agent-generate-story \
  -H "Authorization: Bearer your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Users can not reset their password when 2FA is enabled"
  }'
json
{
  "title": "Fix password reset flow when 2FA is enabled",
  "description": "## Problem\n\nUsers with two-factor authentication enabled cannot complete the password reset flow...",
  "type": 1,
  "priority": 1,
  "intent": "create",
  "reasoning": null,
  "issue_id": null,
  "pipeline": [
    {"step": "validate", "status": 0, "message": "Input is actionable"},
    {"step": "duplicate_check", "status": 0, "message": "No duplicates found"},
    {"step": "research", "status": 0, "message": "Found 3 relevant issues"},
    {"step": "classify", "status": 0, "message": "Classified as new bug report"},
    {"step": "write", "status": 0, "message": "Story generated successfully"}
  ]
}

WebSocket Progress Events

During generation, agent-progress events are broadcast on the user's private channel:

FieldTypeDescription
stepstringAgent name (same as pipeline step names)
phaseinteger0 Pending, 1 Started, 2 Completed
messagestringHuman-readable progress message (English)
stepIndexintegerZero-based step position (0-4)
totalStepsintegerTotal steps in the pipeline (5)

Events are best-effort — the pipeline works correctly even when WebSocket (Reverb) is unavailable. The HTTP response pipeline field is the authoritative result.

Enums

Priority

ValueLabel
0Highest
1High
2Medium
3Low
4Lowest

Type

ValueLabel
0Feature
1Bug

Response Field Notes

The label_ids field is a flat list of label IDs currently attached to the issue. Fetch the project's labels via GET /api/projects/{projectId}/labels once and join client-side — labels are stable per-project taxonomy. Manage assignments via PUT /api/projects/{projectId}/issues/{issueId}/labels.

See Also