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.
List suggestions
Section titled “List suggestions”GET /api/suggestions
Permission: view
Query params:
| Name | Type | Notes |
|---|---|---|
status | new | planned | in_progress | shipped | declined | Optional filter |
sort | top | new | Defaults to top (by upvote count) |
q | string | Title/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 suggestion detail
Section titled “Get suggestion detail”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": "..." } ]}Find similar suggestions (AI)
Section titled “Find similar suggestions (AI)”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.
Submit a suggestion
Section titled “Submit a suggestion”POST /api/suggestions
Permission: update
{ "title": "Dark mode toggle", "description": "Would love..." }Returns the created suggestion with 201.
Edit a suggestion
Section titled “Edit a suggestion”PUT /api/suggestions/:id
Permission: update (must be the original author OR have manage)
{ "title": "...", "description": "..." }Delete a suggestion
Section titled “Delete a suggestion”DELETE /api/suggestions/:id
Permission: manage — admin only. Cascades to upvotes and comments.
Change status
Section titled “Change status”PATCH /api/suggestions/:id/status
Permission: manage
{ "status": "planned" }Upvote
Section titled “Upvote”POST /api/suggestions/:id/upvote — idempotent insert
DELETE /api/suggestions/:id/upvote — remove the current user’s upvote
Permission: update
Comments
Section titled “Comments”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.
Promote to GitHub
Section titled “Promote to GitHub”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 existinggithub_issue_urlis returned in the body)500— GitHub connection missing or API call failed