API reference
All endpoints
Every endpoint is under https://monoes.me. Base path for all resources below: /api/community (except the file server, which is called out). See Authentication for what the auth badges mean.
Feed
The unified, cross-resource activity feed shown on the community homepage.
/api/community/feedPublicList recent activity across posts, bugs, features, and org uploads.
Request
Query params: sort ('latest' | 'popular'), page (number, 0-indexed), authorId (string, optional)Response
{ items: FeedItem[], hasMore: boolean }Session is read if present (to compute the viewer's own vote on each item) but not required. A Bearer token without community:read is silently treated as anonymous rather than rejected.
Bugs
Bug reports, voting, comments, and moderation labels.
/api/community/bugscommunity:writeFile a bug report.
Request
{ title: string, description: string, severity: 'low'|'medium'|'high'|'critical' }Response
201 { id, title, description, authorId, status: 'open', severity, createdAt, updatedAt }title 1-100 chars, description 1-1000 chars.
/api/community/bugs/{id}community:write · admin/moderatorUpdate a bug's status and/or severity.
Request
{ status?: 'open'|'in_progress'|'resolved'|'wontfix', severity?: 'low'|'medium'|'high'|'critical' }Response
{ status?, severity? }At least one field required.
/api/community/bugs/{id}community:write · admin/moderatorDelete a bug report.
Response
{ id }/api/community/bugs/{id}/votecommunity:writeUpvote, downvote, or clear your vote on a bug.
Request
{ value: 1 | -1 | 0 }Response
{ score: number, myVote: 1|-1|0 }0 removes an existing vote. Idempotent upsert per (bug, user).
/api/community/bugs/{id}/commentscommunity:writePost a comment on a bug.
Request
{ body: string }Response
201 { id, bugId, authorId, authorUsername, body, createdAt }body 1-1000 chars.
/api/community/bugs/{id}/comments/{commentId}community:writeDelete your own comment (or any comment, as a moderator/admin).
Response
{ id }/api/community/bugs/{id}/labelscommunity:write · admin/moderatorAttach a label to a bug.
Request
{ labelId: string }Response
{ bugId, labelId }Idempotent: attaching an already-attached label is a no-op.
/api/community/bugs/{id}/labels/{labelId}community:write · admin/moderatorDetach a label from a bug.
Response
{ bugId, labelId }Labels
Moderator-managed labels attachable to bug reports.
/api/community/labelscommunity:write · admin/moderatorCreate a new label.
Request
{ name: string, color: string }Response
201 { id, name, color }name 1-30 chars; color is a #rrggbb hex string; 409 if the name is already taken.
Feature requests
Community feature requests and voting.
/api/community/featurescommunity:writeSubmit a feature request.
Request
{ title: string, description: string }Response
201 { id, title, description, authorId, status: 'open', createdAt, updatedAt }title 1-100 chars, description 1-1000 chars.
/api/community/features/{id}community:write · admin/moderatorDelete a feature request.
Response
{ id }/api/community/features/{id}/statuscommunity:write · adminChange a feature request's status.
Request
{ status: 'open'|'planned'|'shipped'|'declined' }Response
{ status }/api/community/features/{id}/votecommunity:writeUpvote, downvote, or clear your vote on a feature request.
Request
{ value: 1 | -1 | 0 }Response
{ score: number, myVote: 1|-1|0 }Org gallery
Uploaded agent-org definitions, voting, comments, banner images, and run outputs.
/api/community/orgscommunity:writeUpload an org definition (JSON matching the org schema).
Request
{ orgJson: string }, a JSON-encoded org definition, max 500 KBResponse
201 { id, name, goal, topology, roleCount, createdAt }orgJson is validated against a Zod schema before insert. The request also needs a numeric Content-Length header under ~1 MB — a chunked-transfer client with no Content-Length is rejected outright, regardless of the actual body size.
/api/community/orgs/{id}community:writeUpdate an org's name, tagline, description, body, or banner image.
Request
{ name?, tagline?, description?, body?, bannerUrl? } (all optional, at least one required)Response
{ id, ...updatedFields }Requires ownership (or moderator/admin). Limits: name 1-100 chars, tagline ≤150, description ≤1000, body ≤20000. bannerUrl must start with /api/images/org/ or be null; empty strings on tagline/description/body are coerced to null.
/api/community/orgs/{id}community:writeDelete an org upload.
Response
{ id }/api/community/orgs/{id}/votecommunity:writeUpvote, downvote, or clear your vote on an org.
Request
{ value: 1 | -1 | 0 }Response
{ score: number, myVote: 1|-1|0 }/api/community/orgs/{id}/commentscommunity:writePost a comment on an org.
Request
{ body: string }Response
201 { id, orgUploadId, authorId, authorUsername, body, createdAt }body 1-1000 chars.
/api/community/orgs/{id}/comments/{commentId}community:writeDelete your own comment (or any comment, as a moderator/admin).
Response
{ id }/api/community/orgs/{id}/imagescommunity:writeUpload an image (used for the org body and/or banner).
Request
multipart/form-data, field 'image': PNG/JPEG/WebP, max 2 MBResponse
201 { url }/api/community/orgs/{id}/runscommunity:writeUpload a run's output files (Markdown and/or HTML).
Request
multipart/form-data: field 'label' (string, optional, ≤100 chars), field 'files' (up to 10 .md/.html files, 2 MB each)Response
201 { id, label: string | null, createdAt, files: [{ id, filename, fileType, sizeBytes }] }label is optional despite the field existing — a blank or missing label is stored as null.
/api/community/orgs/{id}/runs/{runId}community:writeDelete a run and its files.
Response
{ id }Requires ownership of the run (or moderator/admin).
/api/community/org-run-files/{fileId}PublicFetch a run output file's raw content.
Response
The raw file body, Content-Type text/markdown or text/html.Unauthenticated: anyone with the file id can view it, same as the linked org.
Posts
Free-form community posts and voting.
/api/community/postscommunity:writeCreate a post.
Request
{ title: string, body: string }Response
201 { id, title, body, authorId, createdAt }title 1-100 chars, body 1-2000 chars.
/api/community/posts/{id}/votecommunity:writeUpvote, downvote, or clear your vote on a post.
Request
{ value: 1 | -1 | 0 }Response
{ score: number, myVote: 1|-1|0 }Blog comments
Comments on monoes.me blog posts.
/api/community/blog/{slug}/commentscommunity:writePost a comment on a blog post.
Request
{ body: string }Response
201 { id, postSlug, authorId, authorUsername, body, createdAt }slug must match a real post; body 1-1000 chars.
/api/community/blog/{slug}/comments/{commentId}community:writeDelete your own comment (or any comment, as a moderator/admin).
Response
{ id }Profile & identity
The authenticated user's own profile: identity, avatar, and username.
/api/community/mecommunity:readGet the authenticated user's id, username, name, and avatar URL.
Response
{ id, username, name: string | null, avatarUrl: string | null }avatarUrl (when set) is /api/images/avatar/<key>?v=<updatedAt ms> — the query param is a cache-buster, not part of the key.
/api/community/profilecommunity:writeUpdate your profile: name, tagline, job, tags, and social links.
Request
{ name, tagline?, jobTitle?, company?, tags?: string[], githubUrl?, twitterUrl?, linkedinUrl?, websiteUrl? }Response
{ name, tagline, jobTitle, company, tags, githubUrl, twitterUrl, linkedinUrl, websiteUrl }name required, 1-100 chars. tagline ≤140, jobTitle ≤80, company ≤80. Up to 10 tags (1-24 chars, alnum/underscore/hyphen). githubUrl/linkedinUrl must be https and match their platform's domain; twitterUrl accepts twitter.com or x.com (or a subdomain of either); websiteUrl just needs to be https.
/api/community/profile/avatarcommunity:writeUpload a new avatar image.
Request
multipart/form-data, field 'avatar': PNG/JPEG/WebP, max 2 MBResponse
{ avatarKey, updatedAt }Stored at a fixed per-user key (avatars/<userId>) — re-uploading overwrites the previous image.
/api/community/usernamecommunity:writeSet or change your username (required once, during onboarding).
Request
{ username: string }Response
{ username }3-24 chars, letters/numbers/underscore/hyphen; must be unique — a taken username is a 400, not a 409 (see /docs/errors).
Admin
Admin-only user management (blocking is also available to moderators).
/api/community/admin/userscommunity:read · adminList all users.
Response
{ users: [{ id, email, username, role, blockedAt, createdAt }] }Admin-only — a moderator token gets 403 here.
/api/community/admin/users/{id}/blockcommunity:write · admin/moderatorBlock or unblock a user.
Request
{ blocked: boolean }Response
{ blockedAt: string | null }blockedAt is an ISO timestamp when blocking, null when unblocking.
/api/community/admin/users/{id}/rolecommunity:write · adminChange a user's role.
Request
{ role: 'member'|'moderator'|'admin' }Response
{ role }