# retoor <retoor@molodetz.nl>
from .._shared import endpoint, field
GROUP = {
"slug": "issues",
"title": "Issue Reports",
"intro": """
# Issue Reports
The issue tracker is a full integration with a Gitea repository. The listing and detail views read
issues straight from Gitea with their live status, and a report you file is first rewritten by the
internal AI service into a consistent ticket, then posted to Gitea as an issue. The original
reporter is notified when a developer replies or the status changes, and a comment posted here is
pushed to Gitea and attributed to your account.
Every endpoint follows the shared [Conventions & Errors](/docs/conventions.html) (auth, content
negotiation, pagination, status codes); see [Authentication](/docs/authentication.html) for the
four ways to sign requests.
""",
"endpoints": [
endpoint(
id="issues-list",
method="GET",
path="/issues",
title="List issue tickets",
summary="Render the issue board from Gitea, paginated and filterable by state.",
auth="public",
interactive=True,
params=[
field(
"state",
"query",
"string",
False,
"open",
"Filter: open (default), closed, or all.",
),
field("page", "query", "integer", False, "1", "1-based page."),
],
),
endpoint(
id="issues-create",
method="POST",
path="/issues/create",
title="Report an issue",
summary="Enqueue an issue report. It is enhanced by AI and filed on the tracker.",
auth="user",
encoding="form",
ajax=True,
destructive=True,
params=[
field(
"title",
"form",
"string",
True,
"Login button misaligned",
"Title, 1-200 characters.",
),
field(
"description",
"form",
"textarea",
True,
"Steps to reproduce...",
"Description, 1-5000 characters.",
),
],
notes=["Returns a job uid and status_url. Poll the status_url until status is done to get the issue number."],
sample_response={
"uid": "JOB_UID",
"status_url": "/issues/jobs/JOB_UID",
},
),
endpoint(
id="issues-job",
method="GET",
path="/issues/jobs/{uid}",
title="Issue filing job status",
summary="Poll the filing job; the result carries the new issue number and url.",
auth="public",
ajax=True,
params=[field("uid", "path", "string", True, "JOB_UID", "Job uid.")],
sample_response={
"uid": "JOB_UID",
"kind": "issue_create",
"status": "done",
"number": 42,
"issue_url": "/issues/42",
"enhanced": True,
"error": None,
"created_at": "2026-06-12T09:00:00+00:00",
"completed_at": "2026-06-12T09:00:03+00:00",
},
),
endpoint(
id="issues-planning-queue",
method="POST",
path="/issues/planning",
title="Generate a tickets planning report",
summary="Enqueue a phased markdown implementation document for open tickets, with each ticket's full description reproduced verbatim (inline plus a Source Tickets appendix) alongside implementation steps and acceptance criteria. Admin only.",
auth="admin",
encoding="form",
ajax=True,
params=[field("numbers", "form", "string", False, "1,4,7", "Comma-separated issue numbers to include. Omit to plan every open ticket.")],
notes=["Returns a job uid and status_url. Poll the status_url until status is done to read the markdown and download it.", "Provide 'numbers' to plan only a chosen subset of open tickets; omitting it plans all open tickets."],
sample_response={
"uid": "PLANNING_JOB_UID",
"status_url": "/issues/planning/PLANNING_JOB_UID",
},
),
endpoint(
id="issues-planning-status",
method="GET",
path="/issues/planning/{uid}",
title="Planning report job status",
summary="Poll the planning job; the result carries the rendered markdown and the download URL. Admin only.",
auth="admin",
ajax=True,
params=[field("uid", "path", "string", True, "PLANNING_JOB_UID", "Planning job uid.")],
sample_response={
"uid": "PLANNING_JOB_UID",
"kind": "planning",
"status": "done",
"download_url": "/issues/planning/PLANNING_JOB_UID/download",
"markdown": "# Open Tickets Implementation Plan\n\n...",
"ai_used": True,
"issue_count": 12,
"bytes_out": 4096,
"error": None,
"created_at": "2026-06-15T09:00:00+00:00",
"completed_at": "2026-06-15T09:00:05+00:00",
},
),
endpoint(
id="issues-planning-download",
method="GET",
path="/issues/planning/{uid}/download",
title="Download the planning report",
summary="Download the generated planning report as a markdown file. Admin only.",
auth="admin",
interactive=True,
params=[field("uid", "path", "string", True, "PLANNING_JOB_UID", "Planning job uid.")],
),
endpoint(
id="issues-detail",
method="GET",
path="/issues/{number}",
title="View an issue ticket",
summary="Render a Gitea issue and its comments. Returns an HTML page.",
auth="public",
interactive=True,
params=[
field("number", "path", "integer", True, "12", "Issue number.")
],
),
endpoint(
id="issues-comment",
method="POST",
path="/issues/{number}/comment",
title="Comment on an issue",
summary="Post a comment to the Gitea issue, attributed to the current user.",
auth="user",
encoding="form",
destructive=True,
params=[
field("number", "path", "integer", True, "12", "Issue number."),
field(
"body",
"form",
"textarea",
True,
"I can reproduce this on mobile.",
"Comment, 1-5000 characters.",
),
],
),
endpoint(
id="issues-status",
method="POST",
path="/issues/{number}/status",
title="Change an issue status",
summary="Open or close the Gitea issue. Admin only.",
auth="admin",
encoding="form",
destructive=True,
params=[
field("number", "path", "integer", True, "12", "Issue number."),
field(
"status",
"form",
"string",
True,
"closed",
"New status: open or closed.",
),
],
),
endpoint(
id="issues-attachments-list",
method="GET",
path="/issues/{number}/attachments",
title="List issue attachments",
summary="Return the files attached to an issue ticket.",
auth="public",
params=[
field("number", "path", "integer", True, "12", "Issue number."),
],
),
endpoint(
id="issues-attachments-add",
method="POST",
path="/issues/{number}/attachments",
title="Attach files to an issue",
summary=(
"Link already-uploaded files (from /uploads/upload) to an open issue. The "
"files are also mirrored to the Gitea tracker. Allowed only while the issue is open."
),
auth="user",
encoding="form",
destructive=True,
params=[
field("number", "path", "integer", True, "12", "Issue number."),
field(
"attachment_uids",
"form",
"string",
True,
"a1b2c3d4,e5f6g7h8",
"Comma separated attachment uids returned by /uploads/upload.",
),
],
notes=[
"Only the open issue accepts changes; a closed issue returns 409.",
"You can only link your own uploads unless you are an administrator.",
],
),
endpoint(
id="issues-attachments-delete",
method="DELETE",
path="/issues/{number}/attachments/{uid}",
title="Delete an issue attachment",
summary=(
"Soft-delete a file from an open issue (owner or administrator) and remove it "
"from the tracker."
),
auth="user",
destructive=True,
params=[
field("number", "path", "integer", True, "12", "Issue number."),
field("uid", "path", "string", True, "a1b2c3d4", "Attachment uid."),
],
),
endpoint(
id="issues-comment-attachments-add",
method="POST",
path="/issues/{number}/comments/{cid}/attachments",
title="Attach files to an issue comment",
summary=(
"Link already-uploaded files to an issue comment (issue must be open). Mirrored "
"to the Gitea comment."
),
auth="user",
encoding="form",
destructive=True,
params=[
field("number", "path", "integer", True, "12", "Issue number."),
field("cid", "path", "integer", True, "1", "Gitea comment id."),
field(
"attachment_uids",
"form",
"string",
True,
"a1b2c3d4",
"Comma separated attachment uids returned by /uploads/upload.",
),
],
),
endpoint(
id="issues-comment-attachments-delete",
method="DELETE",
path="/issues/{number}/comments/{cid}/attachments/{uid}",
title="Delete an issue comment attachment",
summary=(
"Soft-delete a file from an issue comment (owner or administrator) and remove it "
"from the tracker."
),
auth="user",
destructive=True,
params=[
field("number", "path", "integer", True, "12", "Issue number."),
field("cid", "path", "integer", True, "1", "Gitea comment id."),
field("uid", "path", "string", True, "a1b2c3d4", "Attachment uid."),
],
),
],
}