GitHub
Reference

Issues API

REST endpoints for issues — CRUD, dispatch to an agent, comments, activity timeline, time entries, custom field values, batch delete.

The Issues API is the canonical surface for the Plan → Issues features. Mounted at /api/v1/issues. Every endpoint follows the common conventions — JSON in/out, cursor pagination, RBAC-gated by issues.* permissions.

Endpoints

Method · PathPurposePermission
GET /api/v1/issuesList with filtersissues.read
POST /api/v1/issuesCreateissues.create
DELETE /api/v1/issuesBulk delete (body: {ids: []})issues.delete
GET /api/v1/issues/{id}Read with embedded relationsissues.read
PATCH /api/v1/issues/{id}Update any fieldissues.edit
DELETE /api/v1/issues/{id}Single deleteissues.delete
POST /api/v1/issues/{id}/dispatchDispatch to an agentissues.dispatch
GET /api/v1/issues/{id}/commentsList commentsissues.read
POST /api/v1/issues/{id}/commentsAdd a commentissues.edit
GET /api/v1/issues/{id}/activitiesActivity timelineissues.read
GET /api/v1/issues/{id}/time-entriesTime entriesissues.read
GET /api/v1/issues/{id}/custom-fieldsCustom field valuesissues.read
PUT /api/v1/issues/{id}/custom-fieldsSet custom field valuesissues.edit

Routed in internal/server/router/router.go under r.Route("/issues", ...).

Filters

GET /api/v1/issues accepts these query params (per handlers/issues.go ListIssues):

ParamTypeNotes
statusbacklog | todo | in_progress | in_review | done | cancelledSingle value (one status at a time)
teamIdstring
projectIdstring
assigneeIdstringFilter by human assignee
parentIdstringSub-issues of a parent
sprintIdstring
initiativeIdstring
limit, cursor, updatedSince, includeCountStandard pagination

The handler accepts seven domain filters today. Filtering by priority, type, assigneeAgentId, label, or full-text search happens client-side after fetching — bring them up if you need them as first-class server-side filters.

Read shape

GET /api/v1/issues/{id} returns the full graph:

{
  "id": "iss_a1b2c3",
  "number": 42,
  "title": "Fix login redirect loop",
  "description": "Steps to reproduce…",
  "status": "in_progress",
  "priority": "high",
  "type": "bug",
  "teamId": "team_back",
  "orgId": "org_default",
  "projectId": "proj_auth",
  "assigneeId": "user_alice",
  "assigneeAgentId": null,
  "parentId": null,
  "sprintId": "sprint_q2_w3",
  "initiativeId": "init_oidc_ga",
  "startDate": "2026-05-01",
  "dueDate": "2026-05-12",
  "estimate": 5,
  "summary": "AI-generated synopsis…",
  "workflowId": "wf_…",
  "createdAt": "2026-05-04T22:14:00Z",
  "updatedAt": "2026-05-05T09:31:08Z",
  "labels":      [{"id":"lbl_a","name":"auth","color":"#ef4444"}],
  "customFields":[{"fieldId":"cf_severity","fieldType":"select","textValue":"sev-1"}],
  "team":     { "id":"team_back","name":"Backend","identifier":"BACK" },
  "project":  { "id":"proj_auth","name":"Auth Refresh" },
  "assignee": { "id":"user_alice","name":"Alice","email":"alice@…" },
  "creator":  { "id":"user_pm","name":"Pat" }
}

The list endpoint returns the same shape but with embedded relations summarized (no full team/project objects — just IDs).

Create

curl -X POST http://localhost:3000/api/v1/issues \
  -H "Authorization: Bearer $PFAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Migrate admin login to OIDC",
    "type": "bug",
    "priority": "high",
    "teamId": "team_back",
    "projectId": "proj_auth",
    "labels": ["auth", "production"],
    "assigneeId": "user_alice"
  }'

Required: title, teamId. Defaults: status=backlog, priority=none, type=task. The response is the freshly-created issue including the auto-assigned number.

Update

# Move to in_review, change priority, add a label
curl -X PATCH http://localhost:3000/api/v1/issues/iss_a1b2c3 \
  -H "Authorization: Bearer $PFAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "in_review",
    "priority": "urgent",
    "labels": ["auth", "production", "needs-review"]
  }'

labels is replace-style (the array you send is the new full label set). To add without replacing, fetch first and merge. Comments, activity, and custom fields use their own dedicated endpoints rather than fields on the main resource.

Dispatch to an agent

curl -X POST http://localhost:3000/api/v1/issues/iss_a1b2c3/dispatch \
  -H "Authorization: Bearer $PFAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "claude-code",
    "branch": "main",
    "prompt": "Investigate the redirect loop and fix it"
  }'

Response includes the agentExecutionId. The dispatch path:

  1. Sets assigneeAgentId on the issue
  2. Creates an AgentExecution row
  3. Provisions a per_execution container
  4. Mirrors the run into the unified workflow_execution view

Stream progress via GET /api/v1/agents/executions/{execId}/events (SSE) or pfai wait agent-execution/{execId}.

Bulk delete

curl -X DELETE http://localhost:3000/api/v1/issues \
  -H "Authorization: Bearer $PFAI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ids": ["iss_a1b2c3","iss_d4e5f6","iss_g7h8i9"]}'

Returns 204 on full success. Partial failures (some IDs missing or unauthorized) come back as 200 with {"deleted":[...], "errors":[...]}.

Activity timeline

curl http://localhost:3000/api/v1/issues/iss_a1b2c3/activities \
  -H "Authorization: Bearer $PFAI_TOKEN"

Returns the chronological feed of activity events. Twelve types tracked — issue_created, status_changed, priority_changed, assignee_changed, title_changed, description_changed, labels_changed, comment_added, comment_deleted, time_logged, agent_dispatched, workflow_assigned. Each entry carries oldValue/newValue strings for diffing.

See also