External API

A developer guide for building integrations with the AgentKanban external API.


Getting started

  1. Create an API key in Settings > API Keys
  2. Choose All boards or grant the key access to specific boards with Read or Edit permission
  3. 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:

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:

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:

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"]