Skip to content

Suggestions API

All endpoints are mounted under /api/suggestions and require an authenticated session via Cloudflare Access. Each route runs requirePermission("suggestions", "view" | "update" | "manage") before the handler.

GET /api/suggestions

Permission: view

Query params:

NameTypeNotes
statusnew | planned | in_progress | shipped | declinedOptional filter
sorttop | newDefaults to top (by upvote count)
qstringTitle/description substring search

Response:

{
"suggestions": [
{
"id": "...",
"title": "...",
"description": "...",
"status": "new",
"github_issue_url": null,
"github_issue_number": null,
"orchestrator_promoted_at": null,
"author_id": "...",
"author_name": "Joel Krause",
"author_picture": "...",
"created_at": "2026-05-24T10:00:00",
"updated_at": "2026-05-24T10:00:00",
"upvote_count": 4,
"comment_count": 2,
"has_upvoted": 1
}
]
}

GET /api/suggestions/:id

Permission: view

Returns the suggestion plus its comments (chronological).

{
"suggestion": { /* same shape as list item */ },
"comments": [
{ "id": "...", "body": "...", "author_name": "...", "created_at": "..." }
]
}

POST /api/suggestions/similar

Permission: view

Used by the submit form to surface likely duplicates while typing. Embeds the draft text (Workers AI, @cf/baai/bge-small-en-v1.5, 384-dim) and returns the top 5 existing suggestions with cosine similarity ≥ 0.78.

{ "title": "Dark mode", "description": "...", "excludeId": "optional-id" }

Response:

{
"similar": [
{
"id": "...",
"title": "Add dark mode toggle",
"description": "...",
"status": "planned",
"upvote_count": 4,
"comment_count": 2,
"similarity": 0.91
}
]
}

Returns { "similar": [] } if the AI binding is unavailable or fewer than 4 chars of title are provided — never throws.

POST /api/suggestions

Permission: update

{ "title": "Dark mode toggle", "description": "Would love..." }

Returns the created suggestion with 201.

PUT /api/suggestions/:id

Permission: update (must be the original author OR have manage)

{ "title": "...", "description": "..." }

DELETE /api/suggestions/:id

Permission: manage — admin only. Cascades to upvotes and comments.

PATCH /api/suggestions/:id/status

Permission: manage

{ "status": "planned" }

POST /api/suggestions/:id/upvote — idempotent insert DELETE /api/suggestions/:id/upvote — remove the current user’s upvote

Permission: update

POST /api/suggestions/:id/comments

Permission: update

{ "body": "Great idea — would also need..." }

DELETE /api/suggestions/:id/comments/:commentId

Permission: update. Author of the comment OR manage can delete.

POST /api/suggestions/:id/promote/github

Permission: manage

{ "withOrchestrator": true }

Creates an issue in <github-org>/nucleus. If withOrchestrator is true, the issue is created with the nucleus:ready label so the orchestrator’s groomer picks it up as a build candidate.

Response:

{
"ok": true,
"github_issue_url": "https://github.com/.../issues/1234",
"github_issue_number": 1234,
"orchestrator_promoted_at": "2026-05-24T11:00:00.000Z"
}

Errors:

  • 409 — suggestion has already been promoted (the existing github_issue_url is returned in the body)
  • 500 — GitHub connection missing or API call failed