Skip to main content

API Reference

All endpoints are relative to the server base URL (e.g., http://localhost:54321). Responses use the standard {"ok": true, "data": {...}} envelope described in the overview.

Health​

GET /health​

Check server status. Always exempt from authentication.

curl http://localhost:54321/health
{
"ok": true,
"data": {
"status": "ok",
"session_id": "ses_a1b2c3",
"change_token": "1821"
}
}

The change_token is a monotonically increasing value derived from the action log. Use it with SSE to detect changes.


Project metadata​

GET /v1/project​

Local td serve project identity, title limits, and optional API capabilities. This endpoint follows the server's bearer-token authentication. It is not a browser-specific endpoint and does not serve HTML or assets.

{
"ok": true,
"data": {
"name": "my-project",
"path": "/path/to/my-project",
"session_id": "ses_a1b2c3",
"title_min_length": 3,
"title_max_length": 200,
"capabilities": ["issue_revisions", "board_move"]
}
}

Clients should check capabilities before relying on optional behavior. issue_revisions means issue reads return a revision and conditional issue writes reject stale snapshots. board_move enables moves anchored to issue IDs.


Monitor​

GET /v1/monitor​

Full board/list/activity snapshot -- the primary read endpoint for dashboard UIs.

Query parameters:

ParamDefaultDescription
include_closedfalseInclude closed issues
sortprioritySort mode: priority, created, updated
search(empty)Search query
search_modeautoSearch mode: auto, text, tdq
curl "http://localhost:54321/v1/monitor?sort=priority&search=auth"

Response data shape:

{
"monitor": {
"timestamp": "2026-02-27T04:20:00Z",
"focused_issue": null,
"in_progress": [],
"task_list": {
"reviewable": [],
"needs_rework": [],
"in_progress": [],
"ready": [],
"pending_review": [],
"blocked": [],
"closed": []
},
"activity": [],
"recent_handoffs": [],
"active_sessions": []
},
"session_id": "ses_a1b2c3",
"change_token": "1824"
}

Issues​

GET /v1/issues​

List issues with filtering, search, sorting, and pagination.

Query parameters:

ParamDefaultDescription
status(all open)Filter by status (repeatable)
type(all)Filter by type (repeatable)
priority(all)Filter by priority (repeatable)
search(empty)Search query
search_modeautoauto, text, or tdq
include_closedfalseInclude closed issues
sortprioritySort by: priority, created, updated, id
order(depends)asc or desc (default: asc for priority/id, desc for created/updated)
limit200Results per page (max 1000)
offset0Pagination offset
curl "http://localhost:54321/v1/issues?status=open&type=bug&sort=priority&limit=50"
{
"ok": true,
"data": {
"issues": [],
"total": 42,
"limit": 50,
"offset": 0,
"has_more": false
}
}

GET /v1/issues/{id}​

Get a single issue with its logs, comments, handoff, dependencies, and direct child issues.

Issue objects include an opaque revision string. Clients can send that value in an If-Match header when editing, deleting, or applying a workflow action. A stale revision returns 409 conflict without applying the change. Fetch the current task and compare it with the user's draft before resubmitting. Existing clients can continue to omit the header.

The detail response also includes children, an array of direct child issues.

curl http://localhost:54321/v1/issues/td-abc123
{
"ok": true,
"data": {
"issue": { "id": "td-abc123", "title": "Fix auth", "status": "open", "..." : "..." },
"logs": [],
"comments": [],
"latest_handoff": null,
"children": [],
"dependencies": [
{
"dep_id": "dep_a1b2c3d4",
"issue_id": "td-abc123",
"depends_on_id": "td-111",
"relation_type": "depends_on"
}
],
"blocked_by": []
}
}
  • dependencies -- outgoing edges: issues that {id} depends on.
  • blocked_by -- incoming edges: issues that depend on {id}.

POST /v1/issues​

Create a new issue.

curl -X POST http://localhost:54321/v1/issues \
-H "Content-Type: application/json" \
-d '{
"title": "Fix auth timeout",
"type": "bug",
"priority": "P1",
"description": "Token expires too early",
"points": 3,
"labels": ["auth", "backend"]
}'

Request body fields:

FieldTypeRequiredDescription
titlestringyes3-200 characters
typestringnobug, feature, task, epic, chore (default: task)
prioritystringnoP0-P4 (default: P2)
descriptionstringnoIssue description
acceptancestringnoAcceptance criteria
pointsintnoFibonacci: 1,2,3,5,8,13,21
labelsstring[]noLabel tags
parent_idstringnoParent issue ID (must exist)
sprintstringnoSprint name
minorboolnoMark as minor
defer_untilstringnoYYYY-MM-DD or null
due_datestringnoYYYY-MM-DD or null

Response:

{ "ok": true, "data": { "issue": { "..." : "..." } } }

PATCH /v1/issues/{id}​

Partial update -- only include fields you want to change.

curl -X PATCH http://localhost:54321/v1/issues/td-abc123 \
-H "Content-Type: application/json" \
-d '{"priority": "P0", "labels": ["auth", "urgent"]}'

DELETE /v1/issues/{id}​

Soft-delete an issue (can be restored via CLI).

curl -X DELETE http://localhost:54321/v1/issues/td-abc123
{ "ok": true, "data": { "deleted": true } }

Status Transitions​

All transition endpoints accept POST and an optional JSON body with a reason field. The reason is logged as a progress entry.

curl -X POST http://localhost:54321/v1/issues/td-abc123/start \
-H "Content-Type: application/json" \
-d '{"reason": "Starting auth work"}'

Transition Matrix​

EndpointValid FromTarget Status
POST /v1/issues/{id}/startopenin_progress
POST /v1/issues/{id}/reviewopen, in_progressin_review
POST /v1/issues/{id}/approvein_reviewclosed
POST /v1/issues/{id}/rejectin_reviewopen
POST /v1/issues/{id}/blockopen, in_progressblocked
POST /v1/issues/{id}/unblockblockedopen
POST /v1/issues/{id}/closeopen, in_progress, blocked, in_reviewclosed
POST /v1/issues/{id}/reopenclosedopen

Invalid transitions return 409 conflict.

Cascade Behavior​

Some transitions trigger cascades:

  • review -- if all siblings of a parent are reviewable, the parent cascades to in_review.
  • approve/close -- parent cascades to closed when all children qualify, and blocked dependents are automatically unblocked.

The response includes cascade details:

{
"ok": true,
"data": {
"issue": { "..." : "..." },
"cascades": {
"parent_status_updates": [],
"auto_unblocked": []
}
}
}

Comments​

POST /v1/issues/{id}/comments​

Add a comment to an issue.

curl -X POST http://localhost:54321/v1/issues/td-abc123/comments \
-H "Content-Type: application/json" \
-d '{"text": "Needs a test for the token refresh edge case."}'
{
"ok": true,
"data": {
"comment": {
"id": "cmt_123",
"issue_id": "td-abc123",
"session_id": "ses_a1b2c3",
"text": "Needs a test for the token refresh edge case.",
"created_at": "2026-02-27T04:30:00Z"
}
}
}

DELETE /v1/issues/{id}/comments/{comment_id}​

Permanently delete a comment. Both the issue ID and comment ID must match.

curl -X DELETE http://localhost:54321/v1/issues/td-abc123/comments/cmt_123
{ "ok": true, "data": { "deleted": true } }

Dependencies​

POST /v1/issues/{id}/dependencies​

Declare that {id} depends on another issue.

curl -X POST http://localhost:54321/v1/issues/td-abc123/dependencies \
-H "Content-Type: application/json" \
-d '{"depends_on": "td-222"}'
{
"ok": true,
"data": {
"dependency": {
"dep_id": "dep_a1b2c3d4",
"issue_id": "td-abc123",
"depends_on_id": "td-222",
"relation_type": "depends_on"
}
}
}

DELETE /v1/issues/{id}/dependencies/{dep_id}​

Remove a dependency using its dep_id. The dependency must belong to {id}.

curl -X DELETE http://localhost:54321/v1/issues/td-abc123/dependencies/dep_a1b2c3d4
{ "ok": true, "data": { "removed": true } }

Focus​

PUT /v1/focus​

Set or clear the focused issue. This is stored in the project config, not the database.

# Set focus
curl -X PUT http://localhost:54321/v1/focus \
-H "Content-Type: application/json" \
-d '{"issue_id": "td-abc123"}'

# Clear focus
curl -X PUT http://localhost:54321/v1/focus \
-H "Content-Type: application/json" \
-d '{"issue_id": null}'
{ "ok": true, "data": { "focused_issue_id": "td-abc123" } }

Boards​

GET /v1/boards​

List all boards.

curl http://localhost:54321/v1/boards
{ "ok": true, "data": { "boards": [] } }

GET /v1/boards/{id}​

Get a board with its resolved issues. Accepts include_closed=true query param.

curl "http://localhost:54321/v1/boards/sprint-12?include_closed=true"
{
"ok": true,
"data": {
"board": {
"id": "brd_abc",
"name": "Sprint 12",
"query": "sprint:sprint-12",
"is_builtin": false,
"view_mode": "list",
"last_viewed_at": null,
"created_at": "...",
"updated_at": "..."
},
"issues": [
{
"issue": { "..." : "..." },
"board_id": "brd_abc",
"position": 0,
"has_position": true,
"category": "open"
}
]
}
}

Board issues are resolved by executing the board's TDQ query first, then applying position overlays for custom ordering.

POST /v1/boards​

Create a new board. The query must be valid TDQ syntax.

curl -X POST http://localhost:54321/v1/boards \
-H "Content-Type: application/json" \
-d '{"name": "Sprint 12", "query": "sprint:sprint-12"}'

PATCH /v1/boards/{id}​

Update a board's name and/or query.

curl -X PATCH http://localhost:54321/v1/boards/brd_abc \
-H "Content-Type: application/json" \
-d '{"name": "Sprint 13", "query": "sprint:sprint-13"}'

DELETE /v1/boards/{id}​

Delete a board.

curl -X DELETE http://localhost:54321/v1/boards/brd_abc

POST /v1/boards/{id}/issues​

Set an explicit position for an issue within a board.

curl -X POST http://localhost:54321/v1/boards/brd_abc/issues \
-H "Content-Type: application/json" \
-d '{"issue_id": "td-abc123", "position": 0}'

POST /v1/boards/{id}/move​

Move a board issue before another issue. Omit before_id or send an empty string to move it to the end. This also works when tasks have no explicit board positions yet. The changes are recorded in the action log.

{ "issue_id": "td-abc123", "before_id": "td-def456", "include_closed": false }

The move uses the same task list as GET /v1/boards/{id}: closed tasks are left out unless include_closed is true. Send the same value your board view used. Only the moved task and any unpositioned tasks above it get explicit positions; tasks below the drop point keep following the board query, so new tasks still sort by it.

Returns { "ok": true, "data": { "positioned": true } }. Returns 409 if the issue or the task it should be placed before is no longer on the board. Refresh the board before retrying.

DELETE /v1/boards/{id}/issues/{issue_id}​

Remove an issue's explicit position overlay from a board.

curl -X DELETE http://localhost:54321/v1/boards/brd_abc/issues/td-abc123

Sessions​

GET /v1/sessions​

List all sessions with the current server session highlighted.

curl http://localhost:54321/v1/sessions
{
"ok": true,
"data": {
"sessions": [
{
"id": "ses_a1b2c3",
"name": "td-serve-web",
"branch": "default",
"agent_type": "web",
"started_at": "2026-02-27T03:00:00Z",
"last_activity": "2026-02-27T04:10:00Z"
}
],
"current_session_id": "ses_a1b2c3"
}
}

Stats​

GET /v1/stats​

Project-wide statistics.

curl http://localhost:54321/v1/stats
{
"ok": true,
"data": {
"total": 142,
"by_status": { "open": 34, "in_progress": 5, "blocked": 3, "in_review": 2, "closed": 98 },
"by_type": { "bug": 12, "feature": 45, "task": 60, "epic": 8, "chore": 17 },
"by_priority": { "P0": 2, "P1": 8, "P2": 55, "P3": 40, "P4": 37 },
"created_today": 3,
"created_this_week": 11,
"total_points": 287,
"completion_rate": 0.69,
"total_logs": 534,
"total_handoffs": 89
}
}

Real-Time Events (SSE)​

GET /v1/events​

Server-Sent Events stream for real-time change notifications.

curl -N http://localhost:54321/v1/events

Event Types​

refresh -- emitted when data changes (after writes or when the poll detects a new change token):

id: 1824
event: refresh
data: {"change_token":"1824","timestamp":"2026-02-27T04:20:07Z"}

ping -- emitted every 30 seconds as a keepalive:

id: 1824
event: ping
data: {"change_token":"1824"}

Reconnect Behavior​

The server supports the Last-Event-ID header. When a client reconnects with a stale event ID, the server sends an immediate refresh event so the client can re-fetch current data.

Recommended client reconnect strategy: exponential backoff starting at 1 second, capped at 10 seconds.

Usage Pattern​

  1. Connect to GET /v1/events.
  2. On refresh events, re-fetch data from GET /v1/monitor or the relevant endpoint.
  3. Use change_token from the health or monitor endpoints to track whether your local state is current.