Appearance
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
| Action | Required Permission | Scope |
|---|---|---|
| List / search / view | Issues: Read | Issues within accessible projects |
| Create | Issues: Create | In accessible projects |
| Update | Issues: Update (Own/All) | Own: only issues you created. All: any issue in accessible projects |
| Delete | Issues: 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
| Method | Endpoint | Description |
|---|---|---|
GET | /api/projects/{projectId}/issues | List all issues |
POST | /api/projects/{projectId}/issues | Create 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/search?q= | Search issues (within project) |
GET | /api/issues/search | Search issues (cross-project) |
GET | /api/issues/my | List issues assigned to the authenticated user |
Create Issue
POST /api/projects/{projectId}/issues
Request Fields
| Field | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Issue title, max 255 characters |
description | string | Yes | Markdown description, max 65,535 characters |
lane_id | integer | Yes | Board lane ID (must belong to the project) |
priority | integer | Yes | 0 Highest, 1 High, 2 Medium, 3 Low, 4 Lowest |
type | integer | Yes | 0 Feature, 1 Bug |
order | integer | Yes | Position within the lane |
assignee_id | integer | No | User ID (must be a project member) |
sprint_id | integer | No | Sprint ID (must belong to the project) |
epic_id | integer | No | Epic ID (must belong to the project) |
estimated_minutes | integer | No | Time estimate in minutes, min 0 |
blocked_by_ids | integer[] | No | Issue IDs that block this issue |
blocks_ids | integer[] | No | Issue IDs that this issue blocks |
prompt | string | No | AI 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,
"order": 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,
"order": 0,
"estimated_minutes": 240,
"blocked_by_ids": [],
"blocks_ids": [],
"branch_link_statuses": [],
"created_at": "2026-03-13T10:30:00.000000Z"
}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,
"order": 0,
"sprint_id": 5,
"blocked_by_ids": [38, 40]
}'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,
"order": 0,
"estimated_minutes": 240,
"blocked_by_ids": [38, 40],
"blocks_ids": [],
"branch_link_statuses": [],
"created_at": "2026-03-13T10:30:00.000000Z"
}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,
"order": 0,
"estimated_minutes": 240,
"blocked_by_ids": [38, 40],
"blocks_ids": [],
"branch_link_statuses": [1],
"created_at": "2026-03-13T10:30: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"List Issues
GET /api/projects/{projectId}/issues
Returns an array of all issues in the project.
bash
curl https://{tenant}.kendo.dev/api/projects/1/issues \
-H "Authorization: Bearer your-token"json
[
{
"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,
"...": "..."
}
]Global Search
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
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | No | Text search across title, description, and key (max 200 chars) |
project_id | integer | No | Filter by project ID |
lane_id | integer | No | Filter by lane ID |
assignee_id | integer | No | Filter by assignee user ID |
sprint_id | integer | No | Filter by sprint ID |
epic_id | integer | No | Filter by epic ID |
priority | integer | No | Filter by priority (0-4, see enums below) |
type | integer | No | Filter by type (0-1, see enums below) |
exclude_final_lane | boolean | No | Exclude issues in each project's highest-order lane (typically Done) |
limit | integer | No | Max 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.
| Field | Type | Description |
|---|---|---|
data | IssueSearchResource[] | Matching issues (never more than meta.limit) |
meta.truncated | boolean | true when the result set was capped at meta.limit; refine filters to see more matches |
meta.count | integer | Number of issues actually returned in data |
meta.limit | integer | Server-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
| Field | Type | Required | Description |
|---|---|---|---|
description | string | Yes | The input text describing the desired issue |
context | string | No | Additional context prepended to the description |
Response Fields
| Field | Type | Description |
|---|---|---|
title | string | AI-generated issue title |
description | string | AI-generated issue description (Markdown) |
type | integer | Suggested issue type (0 Feature, 1 Bug) |
priority | integer | Suggested priority (0-4) |
intent | string | Pipeline result intent: create, update, or respond |
reasoning | string|null | Explanation when intent is update or respond |
issue_id | integer|null | Existing issue ID when intent is update |
pipeline | array|null | Pipeline step summary (see below), null when empty |
Pipeline Step Object
Each entry in the pipeline array describes the result of one agent step:
| Field | Type | Description |
|---|---|---|
step | string | Agent name: validate, duplicate_check, research, classify, write |
status | integer | 0 Success, 1 Warning, 2 Skipped |
message | string | Human-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:
| Field | Type | Description |
|---|---|---|
step | string | Agent name (same as pipeline step names) |
phase | integer | 0 Pending, 1 Started, 2 Completed |
message | string | Human-readable progress message (English) |
stepIndex | integer | Zero-based step position (0-4) |
totalSteps | integer | Total 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
| Value | Label |
|---|---|
0 | Highest |
1 | High |
2 | Medium |
3 | Low |
4 | Lowest |
Type
| Value | Label |
|---|---|
0 | Feature |
1 | Bug |
See Also
- Comments API — Add comments to issues
- Time Entries API — Log time against issues
- Sprints API — Assign issues to sprints
- Issues & Board guide — Working with issues in the UI