documentation
06 api reference

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:

ParameterTypeDescription
statusstringFilter by status (Open, InProgress, WaitingOnDependency, PendingReview, Resolved, Closed)
assigneeIdstringFilter 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:

FieldTypeNotes
titlestringRequired. Short, imperative.
descriptionstringThe detailed brief the agent reads.
assignedAgentIdstringOptional. Specialist agent id to assign to.
assigneeUserIdstringOptional. Human user id to assign to.
goalIdstringOptional. Link to a parent goal.
blockedByIssueIdstringOptional. 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:

ParameterTypeDescription
forceboolClose 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:

ParameterTypeDescription
confirmboolMust 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.

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.

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