{"openapi":"3.0.3","info":{"title":"Monoes Community API","version":"1.0.0","description":"REST API backing the monoes.me community: features, bugs, orgs, posts, and voting. Generated from the same registry that powers https://monoes.me/docs/reference."},"servers":[{"url":"https://monoes.me/api/community"}],"security":[{"oauth2":["community:read","community:write"]}],"components":{"securitySchemes":{"oauth2":{"type":"oauth2","flows":{"authorizationCode":{"authorizationUrl":"https://monoes.me/api/auth/oauth2/authorize","tokenUrl":"https://monoes.me/api/auth/oauth2/token","scopes":{"community:read":"Read feed, bugs, orgs, posts, and votes","community:write":"Post, comment, vote, and upload"}}}}}},"paths":{"/feed":{"get":{"summary":"List recent activity across posts, bugs, features, and org uploads.","tags":["Feed"],"description":"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.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"Query params: sort ('latest' | 'popular'), page (number, 0-indexed), authorId (string, optional)"}}},"responses":{"default":{"description":"{ items: FeedItem[], hasMore: boolean }"}}}},"/bugs":{"post":{"summary":"File a bug report.","tags":["Bugs"],"description":"title 1-100 chars, description 1-1000 chars.","security":[{"oauth2":["community:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ title: string, description: string, severity: 'low'|'medium'|'high'|'critical' }"}}},"responses":{"default":{"description":"201 { id, title, description, authorId, status: 'open', severity, createdAt, updatedAt }"}}}},"/bugs/{id}":{"patch":{"summary":"Update a bug's status and/or severity.","tags":["Bugs"],"description":"At least one field required. Additionally requires the acting user to have the admin or moderator role.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ status?: 'open'|'in_progress'|'resolved'|'wontfix', severity?: 'low'|'medium'|'high'|'critical' }"}}},"responses":{"default":{"description":"{ status?, severity? }"}}},"delete":{"summary":"Delete a bug report.","tags":["Bugs"],"description":"Additionally requires the acting user to have the admin or moderator role.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"default":{"description":"{ id }"}}}},"/bugs/{id}/vote":{"post":{"summary":"Upvote, downvote, or clear your vote on a bug.","tags":["Bugs"],"description":"0 removes an existing vote. Idempotent upsert per (bug, user).","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ value: 1 | -1 | 0 }"}}},"responses":{"default":{"description":"{ score: number, myVote: 1|-1|0 }"}}}},"/bugs/{id}/comments":{"post":{"summary":"Post a comment on a bug.","tags":["Bugs"],"description":"body 1-1000 chars.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ body: string }"}}},"responses":{"default":{"description":"201 { id, bugId, authorId, authorUsername, body, createdAt }"}}}},"/bugs/{id}/comments/{commentId}":{"delete":{"summary":"Delete your own comment (or any comment, as a moderator/admin).","tags":["Bugs"],"security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"default":{"description":"{ id }"}}}},"/bugs/{id}/labels":{"post":{"summary":"Attach a label to a bug.","tags":["Bugs"],"description":"Idempotent: attaching an already-attached label is a no-op. Additionally requires the acting user to have the admin or moderator role.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ labelId: string }"}}},"responses":{"default":{"description":"{ bugId, labelId }"}}}},"/bugs/{id}/labels/{labelId}":{"delete":{"summary":"Detach a label from a bug.","tags":["Bugs"],"description":"Additionally requires the acting user to have the admin or moderator role.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"labelId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"default":{"description":"{ bugId, labelId }"}}}},"/labels":{"post":{"summary":"Create a new label.","tags":["Labels"],"description":"name 1-30 chars; color is a #rrggbb hex string; 409 if the name is already taken. Additionally requires the acting user to have the admin or moderator role.","security":[{"oauth2":["community:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ name: string, color: string }"}}},"responses":{"default":{"description":"201 { id, name, color }"}}}},"/features":{"post":{"summary":"Submit a feature request.","tags":["Feature requests"],"description":"title 1-100 chars, description 1-1000 chars.","security":[{"oauth2":["community:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ title: string, description: string }"}}},"responses":{"default":{"description":"201 { id, title, description, authorId, status: 'open', createdAt, updatedAt }"}}}},"/features/{id}":{"delete":{"summary":"Delete a feature request.","tags":["Feature requests"],"description":"Additionally requires the acting user to have the admin or moderator role.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"default":{"description":"{ id }"}}}},"/features/{id}/status":{"patch":{"summary":"Change a feature request's status.","tags":["Feature requests"],"description":"Additionally requires the acting user to have the admin role.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ status: 'open'|'planned'|'shipped'|'declined' }"}}},"responses":{"default":{"description":"{ status }"}}}},"/features/{id}/vote":{"post":{"summary":"Upvote, downvote, or clear your vote on a feature request.","tags":["Feature requests"],"security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ value: 1 | -1 | 0 }"}}},"responses":{"default":{"description":"{ score: number, myVote: 1|-1|0 }"}}}},"/orgs":{"post":{"summary":"Upload an org definition (JSON matching the org schema).","tags":["Org gallery"],"description":"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.","security":[{"oauth2":["community:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ orgJson: string }, a JSON-encoded org definition, max 500 KB"}}},"responses":{"default":{"description":"201 { id, name, goal, topology, roleCount, createdAt }"}}}},"/orgs/{id}":{"patch":{"summary":"Update an org's name, tagline, description, body, or banner image.","tags":["Org gallery"],"description":"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.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ name?, tagline?, description?, body?, bannerUrl? } (all optional, at least one required)"}}},"responses":{"default":{"description":"{ id, ...updatedFields }"}}},"delete":{"summary":"Delete an org upload.","tags":["Org gallery"],"security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"default":{"description":"{ id }"}}}},"/orgs/{id}/vote":{"post":{"summary":"Upvote, downvote, or clear your vote on an org.","tags":["Org gallery"],"security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ value: 1 | -1 | 0 }"}}},"responses":{"default":{"description":"{ score: number, myVote: 1|-1|0 }"}}}},"/orgs/{id}/comments":{"post":{"summary":"Post a comment on an org.","tags":["Org gallery"],"description":"body 1-1000 chars.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ body: string }"}}},"responses":{"default":{"description":"201 { id, orgUploadId, authorId, authorUsername, body, createdAt }"}}}},"/orgs/{id}/comments/{commentId}":{"delete":{"summary":"Delete your own comment (or any comment, as a moderator/admin).","tags":["Org gallery"],"security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"default":{"description":"{ id }"}}}},"/orgs/{id}/images":{"post":{"summary":"Upload an image (used for the org body and/or banner).","tags":["Org gallery"],"security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"multipart/form-data, field 'image': PNG/JPEG/WebP, max 2 MB"}}},"responses":{"default":{"description":"201 { url }"}}}},"/orgs/{id}/runs":{"post":{"summary":"Upload a run's output files (Markdown and/or HTML).","tags":["Org gallery"],"description":"label is optional despite the field existing — a blank or missing label is stored as null.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"multipart/form-data: field 'label' (string, optional, ≤100 chars), field 'files' (up to 10 .md/.html files, 2 MB each)"}}},"responses":{"default":{"description":"201 { id, label: string | null, createdAt, files: [{ id, filename, fileType, sizeBytes }] }"}}}},"/orgs/{id}/runs/{runId}":{"delete":{"summary":"Delete a run and its files.","tags":["Org gallery"],"description":"Requires ownership of the run (or moderator/admin).","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"runId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"default":{"description":"{ id }"}}}},"/org-run-files/{fileId}":{"get":{"summary":"Fetch a run output file's raw content.","tags":["Org gallery"],"description":"Unauthenticated: anyone with the file id can view it, same as the linked org.","security":[],"parameters":[{"name":"fileId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"default":{"description":"The raw file body, Content-Type text/markdown or text/html."}}}},"/posts":{"post":{"summary":"Create a post.","tags":["Posts"],"description":"title 1-100 chars, body 1-2000 chars.","security":[{"oauth2":["community:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ title: string, body: string }"}}},"responses":{"default":{"description":"201 { id, title, body, authorId, createdAt }"}}}},"/posts/{id}/vote":{"post":{"summary":"Upvote, downvote, or clear your vote on a post.","tags":["Posts"],"security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ value: 1 | -1 | 0 }"}}},"responses":{"default":{"description":"{ score: number, myVote: 1|-1|0 }"}}}},"/blog/{slug}/comments":{"post":{"summary":"Post a comment on a blog post.","tags":["Blog comments"],"description":"slug must match a real post; body 1-1000 chars.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ body: string }"}}},"responses":{"default":{"description":"201 { id, postSlug, authorId, authorUsername, body, createdAt }"}}}},"/blog/{slug}/comments/{commentId}":{"delete":{"summary":"Delete your own comment (or any comment, as a moderator/admin).","tags":["Blog comments"],"security":[{"oauth2":["community:write"]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"commentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"default":{"description":"{ id }"}}}},"/me":{"get":{"summary":"Get the authenticated user's id, username, name, and avatar URL.","tags":["Profile & identity"],"description":"avatarUrl (when set) is /api/images/avatar/<key>?v=<updatedAt ms> — the query param is a cache-buster, not part of the key.","security":[{"oauth2":["community:read"]}],"responses":{"default":{"description":"{ id, username, name: string | null, avatarUrl: string | null }"}}}},"/profile":{"patch":{"summary":"Update your profile: name, tagline, job, tags, and social links.","tags":["Profile & identity"],"description":"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.","security":[{"oauth2":["community:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ name, tagline?, jobTitle?, company?, tags?: string[], githubUrl?, twitterUrl?, linkedinUrl?, websiteUrl? }"}}},"responses":{"default":{"description":"{ name, tagline, jobTitle, company, tags, githubUrl, twitterUrl, linkedinUrl, websiteUrl }"}}}},"/profile/avatar":{"post":{"summary":"Upload a new avatar image.","tags":["Profile & identity"],"description":"Stored at a fixed per-user key (avatars/<userId>) — re-uploading overwrites the previous image.","security":[{"oauth2":["community:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"multipart/form-data, field 'avatar': PNG/JPEG/WebP, max 2 MB"}}},"responses":{"default":{"description":"{ avatarKey, updatedAt }"}}}},"/username":{"post":{"summary":"Set or change your username (required once, during onboarding).","tags":["Profile & identity"],"description":"3-24 chars, letters/numbers/underscore/hyphen; must be unique — a taken username is a 400, not a 409 (see /docs/errors).","security":[{"oauth2":["community:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ username: string }"}}},"responses":{"default":{"description":"{ username }"}}}},"/admin/users":{"get":{"summary":"List all users.","tags":["Admin"],"description":"Admin-only — a moderator token gets 403 here. Additionally requires the acting user to have the admin role.","security":[{"oauth2":["community:read"]}],"responses":{"default":{"description":"{ users: [{ id, email, username, role, blockedAt, createdAt }] }"}}}},"/admin/users/{id}/block":{"patch":{"summary":"Block or unblock a user.","tags":["Admin"],"description":"blockedAt is an ISO timestamp when blocking, null when unblocking. Additionally requires the acting user to have the admin or moderator role.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ blocked: boolean }"}}},"responses":{"default":{"description":"{ blockedAt: string | null }"}}}},"/admin/users/{id}/role":{"patch":{"summary":"Change a user's role.","tags":["Admin"],"description":"Additionally requires the acting user to have the admin role.","security":[{"oauth2":["community:write"]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"},"description":"{ role: 'member'|'moderator'|'admin' }"}}},"responses":{"default":{"description":"{ role }"}}}}}}