External API
A developer guide for building integrations with the AgentKanban external API.
Getting started
- Create an API key in Settings > API Keys
- Choose All boards or grant the key access to specific boards with Read or Edit permission
- Copy the key -- it starts with
ak_and can be revealed again later from the key list
All requests go to /api/ext/v1/ with the key in the Authorization header:
Authorization: Bearer ak_YourApiKeyHere
Response format
Every response is JSON:
- Success:
{ "data": { ... } }or{ "data": [...] } - Error:
{ "error": { "code": "...", "message": "..." } }
Common HTTP status codes:
| Status | Meaning |
|---|---|
| 200 | OK |
| 201 | Created |
| 204 | Deleted (no body) |
| 400 | Bad request (validation error) |
| 401 | Missing or invalid API key |
| 403 | Key does not have access to that board |
| 404 | Resource not found |
| 429 | Rate limited |
Boards
List boards
GET /api/ext/v1/boards
Returns all boards the API key has access to.
Response:
{
"data": [
{ "id": "01abc...", "name": "Sprint Board", "permission": "edit" }
]
}
Create a board
POST /api/ext/v1/boards
Content-Type: application/json
{ "name": "New Board" }
The key automatically gets edit access to the new board.
Response (201):
{ "data": { "id": "01abc...", "name": "New Board" } }
Get board detail
GET /api/ext/v1/boards/:boardId
Returns board info with a summary of its lanes.
Response:
{
"data": {
"id": "01abc...",
"name": "Sprint Board",
"lanes": [
{ "id": "...", "name": "To Do", "taskCount": 5 },
{ "id": "...", "name": "Doing", "taskCount": 2 },
{ "id": "...", "name": "Done", "taskCount": 8 }
]
}
}
Delete a board
DELETE /api/ext/v1/boards/:boardId
Requires edit permission. Returns 204 with no body.
Lanes
List lanes
GET /api/ext/v1/boards/:boardId/lanes
Response:
{
"data": [
{ "id": "...", "name": "To Do", "position": 0 },
{ "id": "...", "name": "Doing", "position": 1 },
{ "id": "...", "name": "Done", "position": 2 }
]
}
Create a lane
POST /api/ext/v1/boards/:boardId/lanes
Content-Type: application/json
{ "name": "In Review" }
Response (201):
{ "data": { "id": "...", "name": "In Review" } }
Rename or reorder a lane
PATCH /api/ext/v1/boards/:boardId/lanes/:laneId
Content-Type: application/json
{ "name": "Code Review" }
To reorder, send { "position": 2 }. Both fields are optional.
Response:
{ "data": { "id": "...", "name": "Code Review" } }
Delete a lane
DELETE /api/ext/v1/boards/:boardId/lanes/:laneId
Returns 204 with no body. Requires edit permission.
Tasks
List tasks
GET /api/ext/v1/boards/:boardId/tasks
Optional query parameters:
| Parameter | Description |
|---|---|
laneId |
Filter tasks to a specific lane |
includeArchived |
Set to true to include archived tasks |
shortIdPrefix |
Filter tasks by the copied short ID prefix |
Response:
{
"data": [
{
"id": "...",
"shortId": "a1b2c3d4",
"title": "Fix login bug",
"laneId": "...",
"priority": 3,
"tags": ["bug"],
"archived": false,
"createdAt": "2026-04-27T12:00:00.000Z",
"updatedAt": "2026-04-27T12:00:00.000Z"
}
]
}
Create a task
POST /api/ext/v1/boards/:boardId/tasks
Content-Type: application/json
{ "title": "New task", "laneId": "..." }
Response (201):
{ "data": { "id": "...", "title": "New task", "laneId": "..." } }
Get task detail
GET /api/ext/v1/boards/:boardId/tasks/:taskId
Response:
{
"data": {
"id": "...",
"shortId": "a1b2c3d4",
"title": "Fix login bug",
"description": "The login form crashes when...",
"laneId": "...",
"priority": 3,
"tags": ["bug"],
"archived": false,
"createdAt": "2026-04-27T12:00:00.000Z",
"updatedAt": "2026-04-27T12:05:00.000Z",
"lastTurnAt": null
}
}
Update a task
PATCH /api/ext/v1/boards/:boardId/tasks/:taskId
Content-Type: application/json
{ "title": "Updated title", "priority": 2 }
Updatable fields: title, description, priority, tags, archived.
Priority is an integer from 0 to 4:
0= none1= low2= medium3= high4= urgent
Response:
{ "data": { "id": "...", "title": "Updated title", "priority": 2 } }
Move a task
POST /api/ext/v1/boards/:boardId/tasks/:taskId/move
Content-Type: application/json
{ "laneId": "target-lane-id", "position": 0 }
position is optional. Without it, the task is appended to the end of the target lane.
Response:
{ "data": { "id": "...", "laneId": "target-lane-id", "position": 0 } }
Delete a task
DELETE /api/ext/v1/boards/:boardId/tasks/:taskId
Returns 204 with no body.
Comments
List comments
GET /api/ext/v1/boards/:boardId/tasks/:taskId/comments
Returns the task's conversation thread, including authorSource so integrations can distinguish user, API, and agent messages.
Add a comment
POST /api/ext/v1/boards/:boardId/tasks/:taskId/comments
Content-Type: application/json
{
"body": "Work completed.",
"authorName": "CI Bot",
"source": "api"
}
source is optional and may be api or agent. It defaults to api for ordinary external API callers. MCP-based agent flows use agent.
Delete a comment
DELETE /api/ext/v1/boards/:boardId/tasks/:taskId/comments/:commentId
Returns 204 with no body.
Todos
List todos
GET /api/ext/v1/boards/:boardId/tasks/:taskId/todos
Returns todos ordered by iteration, then by task-specific todo order.
Create a todo
POST /api/ext/v1/boards/:boardId/tasks/:taskId/todos
Content-Type: application/json
{
"text": "Update release notes",
"iteration": 2,
"source": "agent"
}
source is optional and may be api or agent.
Get, update, or delete a todo
GET /api/ext/v1/boards/:boardId/tasks/:taskId/todos/:todoId
PATCH /api/ext/v1/boards/:boardId/tasks/:taskId/todos/:todoId
DELETE /api/ext/v1/boards/:boardId/tasks/:taskId/todos/:todoId
Reorder todos
POST /api/ext/v1/boards/:boardId/tasks/:taskId/todos/reorder
Content-Type: application/json
{ "orderedIds": ["todo-1", "todo-2", "todo-3"] }
Turns
Captured conversation turns from IDE extensions (Copilot, Claude Code, Codex). Turns are typically pushed automatically by IDE extensions and do not need to be created manually.
Post turns (batch upsert)
Requires edit permission. Up to 100 turns per batch.
POST /api/ext/v1/boards/:boardId/tasks/:taskId/turns
Content-Type: application/json
{
"turns": [
{
"source": "copilot",
"sourceId": "abc123",
"role": "user",
"content": "How do I fix the login bug?",
"capturedAt": "2025-06-20T10:30:00Z"
},
{
"source": "copilot",
"sourceId": "def456",
"role": "assistant",
"content": "The issue is in the auth middleware...",
"capturedAt": "2025-06-20T10:30:05Z"
}
]
}
Response (201):
{
"data": { "inserted": 2 },
"watermarks": {
"copilot": { "sourceId": "def456", "capturedAt": "2025-06-20T10:30:05Z" }
}
}
Duplicate turns (same source + sourceId) are silently ignored.
List turns
Requires read permission.
GET /api/ext/v1/boards/:boardId/tasks/:taskId/turns
GET /api/ext/v1/boards/:boardId/tasks/:taskId/turns?source=copilot
GET /api/ext/v1/boards/:boardId/tasks/:taskId/turns?limit=50&after=2025-06-20T10:30:00Z
Response:
{
"data": [
{
"id": "...",
"source": "copilot",
"sourceId": "abc123",
"role": "user",
"content": "How do I fix the login bug?",
"toolUseOnly": false,
"capturedAt": "2025-06-20T10:30:00Z"
}
],
"watermarks": { "copilot": { "sourceId": "...", "capturedAt": "..." } },
"cursor": null
}
Get watermarks
Returns per-source watermarks without turn content. Useful for checking what has already been synced.
GET /api/ext/v1/boards/:boardId/tasks/:taskId/turns/watermark
Response:
{
"watermarks": {
"copilot": { "sourceId": "def456", "capturedAt": "2025-06-20T10:30:05Z" }
}
}
Webhooks
See the Webhooks guide for full documentation on setting up and using webhooks via the API.
Quick reference
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/ext/v1/webhooks |
List webhooks for this key |
| POST | /api/ext/v1/webhooks |
Create a webhook |
| GET | /api/ext/v1/webhooks/:id |
Webhook detail with recent deliveries |
| PATCH | /api/ext/v1/webhooks/:id |
Update URL, events, or active state |
| DELETE | /api/ext/v1/webhooks/:id |
Delete a webhook |
Rate limits
Each API key is rate limited:
- 60 requests per minute
- 1,000 requests per hour
When limited, you receive a 429 response with a Retry-After header (in seconds):
HTTP/1.1 429 Too Many Requests
Retry-After: 12
{ "error": { "code": "rate_limited", "message": "Rate limit exceeded. Try again later." } }
Error handling
All errors follow the same shape:
{ "error": { "code": "validation_error", "message": "Board name is required" } }
Common error codes:
| Code | Meaning |
|---|---|
missing_auth |
No Authorization header |
invalid_key |
Key not found or revoked |
forbidden |
Key lacks permission for this board |
not_found |
Resource does not exist |
validation_error |
Invalid request body |
invalid_body |
Request body is not valid JSON |
rate_limited |
Too many requests |
Examples
curl
# List boards
curl -H "Authorization: Bearer ak_YourKey" \
https://www.agentkanban.io/api/ext/v1/boards
# Create a task
curl -X POST \
-H "Authorization: Bearer ak_YourKey" \
-H "Content-Type: application/json" \
-d '{"title": "Deploy v2", "laneId": "lane-id-here"}' \
https://www.agentkanban.io/api/ext/v1/boards/board-id/tasks
# Move a task to Done
curl -X POST \
-H "Authorization: Bearer ak_YourKey" \
-H "Content-Type: application/json" \
-d '{"laneId": "done-lane-id"}' \
https://www.agentkanban.io/api/ext/v1/boards/board-id/tasks/task-id/move
JavaScript (fetch)
const API_KEY = process.env.AGENTKANBAN_API_KEY;
const BASE = "https://www.agentkanban.io/api/ext/v1";
async function listBoards() {
const res = await fetch(`${BASE}/boards`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const { data } = await res.json();
return data;
}
async function createTask(boardId, laneId, title) {
const res = await fetch(`${BASE}/boards/${boardId}/tasks`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ title, laneId }),
});
const { data } = await res.json();
return data;
}
Python (requests)
import os, requests
API_KEY = os.environ["AGENTKANBAN_API_KEY"]
BASE = "https://www.agentkanban.io/api/ext/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# List boards
boards = requests.get(f"{BASE}/boards", headers=HEADERS).json()["data"]
# Create a task
task = requests.post(
f"{BASE}/boards/{board_id}/tasks",
headers=HEADERS,
json={"title": "Deploy v2", "laneId": lane_id},
).json()["data"]