Issues API
Issues are the unit of work agents execute. The issues API lives under /api/projects/* and covers the full lifecycle — create, update, comment, attach files, link dependencies, pause, resume, and close.
Authentication
All endpoints require internal API auth (dashboard session cookie or personal API key). Minimum role: Viewer or above for reads, Editor or above for writes.
Issues
GET /api/projects/{projectId}/issues
List all issues in a project.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status (Open, InProgress, WaitingOnDependency, PendingReview, Resolved, Closed) |
assigneeId | string | Filter by assignee |
Response: a JSON array of issue objects with id, title, description, status, assignee, links, and timestamps.
POST /api/projects/{projectId}/issues
Create a new issue inside a project.
Request body: an issue create request with at least title. Common fields:
| Field | Type | Notes |
|---|---|---|
title | string | Required. Short, imperative. |
description | string | The detailed brief the agent reads. |
assignedAgentId | string | Optional. Specialist agent id to assign to. |
assigneeUserId | string | Optional. Human user id to assign to. |
goalId | string | Optional. Link to a parent goal. |
blockedByIssueId | string | Optional. Create in WaitingOnDependency status with an auto-link to the blocker. |
Response: 201 Created with the new issue object. If assigned to an agent, the agent picks it up on its next heartbeat.
GET /api/projects/issues/{issueId}
Get a single issue by id.
Note the route: issue id is global, so the path is /api/projects/issues/{id} — you don’t need to provide the project id when looking up a specific issue.
Response: the issue object, or 404 Not Found.
PUT /api/projects/issues/{issueId}
Update an issue. Any field can be updated: title, description, status, assignee, goal id, etc.
Response: the updated issue object.
POST /api/projects/issues/{issueId}/close
Move an issue to Closed status. This is the final close — different from marking it resolved, which happens via PUT with status: "Resolved".
Query parameters:
| Parameter | Type | Description |
|---|---|---|
force | bool | Close even if dependents are still open (normally blocks the close) |
Response: 204 No Content.
POST /api/projects/issues/{issueId}/pause
Pause an issue mid-execution. The agent stops at the next turn boundary and the issue status goes back to Open. Useful when you want to interrupt an agent that’s going in a bad direction.
Response: 204 No Content.
POST /api/projects/issues/{issueId}/resume
Resume a paused issue. The status goes back to InProgress and the assigned agent picks it up on its next heartbeat.
Response: 204 No Content.
DELETE /api/projects/issues/{issueId}
Delete an issue. Destructive.
Query parameters:
| Parameter | Type | Description |
|---|---|---|
confirm | bool | Must be true — safety guard |
Response: 204 No Content.
Comments
POST /api/projects/issues/{issueId}/comments
Add a comment to an issue. Comments are visible to everyone with access to the project and become part of the issue activity timeline.
Request body:
{
"content": "The draft looks good. Fix the pricing tier names and ship it.",
"authorType": "User"
}
Response: the new comment object.
Attachments
POST /api/projects/issues/{issueId}/attachments
Upload a file and attach it to an issue. Expects a multipart/form-data request with a file field.
curl -X POST https://your-exolvra.example/api/projects/issues/issue_123/attachments \
-H "Authorization: Bearer exou_..." \
-F "file=@./screenshot.png"
Response: the new attachment object with its download URL.
GET /api/projects/issues/{issueId}/attachments
List all attachments on an issue.
Response: an array of attachment metadata (no file bytes).
GET /api/projects/attachments/{attachmentId}/download
Download an attachment’s file bytes.
Response: the raw file content with the appropriate Content-Type header.
DELETE /api/projects/attachments/{attachmentId}
Delete an attachment.
Response: 204 No Content.
Links (dependencies)
POST /api/projects/issues/{issueId}/links
Create a link between this issue and another. Used for dependency tracking.
Request body:
{
"targetIssueId": "issue_456",
"linkType": "BlockedBy"
}
Link types: Blocks, BlockedBy, RelatedTo, DuplicateOf. Creating a BlockedBy link transitions this issue to WaitingOnDependency automatically.
Response: the new link object.
GET /api/projects/issues/{issueId}/links
List all links attached to an issue — both outgoing (this issue blocks X) and incoming (this issue is blocked by Y).
Response: an array of link objects.
DELETE /api/projects/links/{linkId}
Remove a link. If removing a BlockedBy link leaves this issue with no more blockers, it auto-transitions from WaitingOnDependency back to Open.
Response: 204 No Content.
Example — create a dependency chain
# Create issue A (the blocker)
curl -X POST https://your-exolvra.example/api/projects/proj_123/issues \
-H "Authorization: Bearer exou_..." \
-H "Content-Type: application/json" \
-d '{
"title": "Draft pricing page copy",
"description": "Write the headline, value props, and FAQ for the new pricing page.",
"assignedAgentId": "ux-writer"
}'
# Response contains issue A id, e.g. "issue_a1"
# Create issue B, already blocked by A
curl -X POST https://your-exolvra.example/api/projects/proj_123/issues \
-H "Authorization: Bearer exou_..." \
-H "Content-Type: application/json" \
-d '{
"title": "Implement pricing page",
"description": "Build the pricing page using the approved copy from the previous issue.",
"assignedAgentId": "frontend-dev",
"blockedByIssueId": "issue_a1"
}'
Issue B is created in WaitingOnDependency status immediately. When the ux-writer agent marks issue A resolved, B auto-transitions to Open and the frontend-dev agent picks it up on the next heartbeat.
Where to go next
- Projects API — the parent container endpoints
- Issues — the dashboard counterpart
- Projects, goals & issues — the conceptual model