{"openapi":"3.1.0","info":{"title":"server-streampack API","description":"Bundled HTTP API for the server-streampack distribution of Streampack. Includes blog, authentication, and related site endpoints. Advisory version headers are supported: clients may send 'Accept-Version' and responses include 'Content-Version' and resolved 'Accept-Version'.","version":"0.1.0"},"servers":[{"url":"https://api.bytecode.news","description":"Generated server url"}],"paths":{"/posts/{id}":{"get":{"tags":["Posts"],"summary":"Get a post by ID","description":"Returns full post detail by its UUIDv7 identifier. Draft posts are only visible to their author and admins.","operationId":"getPostById","parameters":[{"name":"id","in":"path","description":"Post UUIDv7","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Post detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentDetail"}}}},"404":{"description":"Post not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}},"put":{"tags":["Posts"],"summary":"Edit a post","description":"Updates an existing post's title, content, tags, and/or categories. Editing is admin-only. Edits re-render HTML from the updated markdown source. If a `summary` is provided, it is persisted as the post excerpt.","operationId":"editPost","parameters":[{"name":"id","in":"path","description":"Post UUIDv7","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditContentHttpRequest"}}},"required":true},"responses":{"200":{"description":"Post updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Post not found or deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/factoids/{selector}":{"get":{"tags":["Factoids"],"operationId":"get","parameters":[{"name":"selector","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactoidDetailResponse"}}}}}},"put":{"tags":["Factoids"],"summary":"Set a factoid's attribute, creating the factoid if it's new","description":"Sets one attribute (the text, unless another is named) as chat's `selector=value` does, as the signed-in user. A locked factoid is refused.","operationId":"set","parameters":[{"name":"selector","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactoidSetHttpRequest"}}},"required":true},"responses":{"200":{"description":"Updated; the factoid as it now stands","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactoidDetailResponse"}}}},"201":{"description":"Created; the new factoid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactoidDetailResponse"}}}},"400":{"description":"No value, or an attribute that can't be set","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Factoid changes aren't enabled for the web","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"The factoid is locked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]},"delete":{"tags":["Factoids"],"summary":"Forget a factoid, or one of its attributes","description":"As chat's `forget selector` (or `forget selector.attribute`), as the signed-in user. A locked factoid is refused.","operationId":"forget","parameters":[{"name":"selector","in":"path","required":true,"schema":{"type":"string"}},{"name":"attribute","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"204":{"description":"Forgotten","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"An attribute that can't be forgotten","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Factoid changes aren't enabled for the web","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such factoid, or no such attribute on it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"The factoid is locked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/factoids/{selector}/lock":{"put":{"tags":["Factoids"],"summary":"Lock a factoid against changes (admins)","operationId":"lock","parameters":[{"name":"selector","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Locked","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Not an admin","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such factoid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]},"delete":{"tags":["Factoids"],"summary":"Unlock a factoid (admins)","operationId":"unlock","parameters":[{"name":"selector","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"204":{"description":"Unlocked","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Not an admin","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such factoid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/comments/{id}":{"put":{"tags":["Comments"],"summary":"Edit a comment within the edit window","operationId":"editComment","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditCommentHttpRequest"}}},"required":true},"responses":{"200":{"description":"Comment updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Not authorized or edit window expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Comment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/auth/profile":{"put":{"tags":["Authentication"],"summary":"Update the authenticated user's profile","operationId":"editProfile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditProfileRequest"}}},"required":true},"responses":{"200":{"description":"Updated user principal","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPrincipal"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/users/{username}/unsuspend":{"put":{"tags":["Admin - User Management"],"summary":"Unsuspend a user account","operationId":"unsuspendAccount","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Account unsuspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/users/{username}/suspend":{"put":{"tags":["Admin - User Management"],"summary":"Suspend a user account","operationId":"suspendAccount","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Account suspended","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/users/{username}/role":{"put":{"tags":["Admin - User Management"],"summary":"Change a user's role","operationId":"changeRole","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleUpdateRequest"}}},"required":true},"responses":{"200":{"description":"Role updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPrincipal"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Insufficient privileges","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/items/{id}/rating":{"put":{"tags":["Admin - RSS"],"summary":"Rate a feed item","description":"`RATES` (worth writing about), `MIGHT` or `DULL`. Replaces any rating the item had; every change is kept in the rating history. The same rating again changes nothing.","operationId":"rateRssItem","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RssItemRatingRequest"}}},"required":true},"responses":{"200":{"description":"The item's rating","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RssItemRatingResponse"}}}},"400":{"description":"No rating, or not RATES, MIGHT or DULL","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]},"delete":{"tags":["Admin - RSS"],"summary":"Clear a feed item's rating","description":"Kept in the rating history. An item with no rating is left as it is.","operationId":"clearRssItemRating","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"204":{"description":"The item has no rating","content":{"*/*":{"schema":{"type":"object"}}}},"404":{"description":"No such item","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/posts/{id}":{"put":{"tags":["Admin - Post Management"],"summary":"Admin edit a post","operationId":"editPost_1","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditContentHttpRequest"}}},"required":true},"responses":{"200":{"description":"Post updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Post not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]},"delete":{"tags":["Admin - Post Management"],"summary":"Delete a post (soft by default, hard with ?hard=true)","operationId":"deletePost","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"hard","in":"query","required":false,"schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Post deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentOperationConfirmation"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Post not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/posts/{id}/approve":{"put":{"tags":["Admin - Post Management"],"summary":"Approve a draft post and set publication date","operationId":"approvePost","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApproveContentHttpRequest"}}},"required":true},"responses":{"200":{"description":"Post approved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Post not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/webhooks/github":{"post":{"tags":["git-hub-webhook-controller"],"summary":"Receive GitHub webhook deliveries for github.com repositories","description":"Validates X-Hub-Signature-256, deduplicates deliveries, and fans out issues, pull_request, release, and completed workflow_run events.","operationId":"receive","parameters":[{"name":"X-Hub-Signature-256","in":"header","required":false,"schema":{"type":"string"}},{"name":"X-GitHub-Event","in":"header","required":false,"schema":{"type":"string"}},{"name":"X-GitHub-Delivery","in":"header","required":false,"schema":{"type":"string"}},{"name":"Content-Type","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"202":{"description":"Delivery accepted"},"400":{"description":"Malformed payload or repository metadata"},"401":{"description":"Signature mismatch"}}}},"/webhooks/github/{instanceId}":{"post":{"tags":["git-hub-webhook-controller"],"summary":"Receive GitHub webhook deliveries for one registered GitHub instance","description":"Same processing as the bare route, verified against repositories on the instance named by the path.","operationId":"receiveForInstance","parameters":[{"name":"instanceId","in":"path","required":true,"schema":{"type":"string"}},{"name":"X-Hub-Signature-256","in":"header","required":false,"schema":{"type":"string"}},{"name":"X-GitHub-Event","in":"header","required":false,"schema":{"type":"string"}},{"name":"X-GitHub-Delivery","in":"header","required":false,"schema":{"type":"string"}},{"name":"Content-Type","in":"header","required":false,"schema":{"type":"string"}}],"responses":{"202":{"description":"Delivery accepted"},"400":{"description":"Malformed payload or repository metadata"},"401":{"description":"Signature mismatch"},"404":{"description":"No such instance"}}}},"/rss/items/{id}/access":{"post":{"tags":["RSS Aggregator"],"summary":"Record a UI-driven access of a stored RSS item","operationId":"recordAccess","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Access tracking accepted"}}}},"/posts":{"get":{"tags":["Posts"],"summary":"List published posts","description":"Returns a paginated list of posts that are approved, not deleted, and have a publishedAt date in the past. Authenticated users also see their own drafts interleaved in the results.","operationId":"listPosts","parameters":[{"name":"page","in":"query","description":"Zero-based page index","required":false,"schema":{"type":"integer","format":"int32","default":0},"example":0},{"name":"size","in":"query","description":"Number of posts per page","required":false,"schema":{"type":"integer","format":"int32","default":20},"example":20},{"name":"category","in":"query","description":"Filter by category name","required":false,"schema":{"type":"string"}},{"name":"tag","in":"query","description":"Filter by tag name","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated list of published posts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentListResponse"}}}}}},"post":{"tags":["Posts"],"summary":"Create a new blog post draft","description":"Submits a new post as a DRAFT awaiting admin approval. Authentication is required unless anonymous submission is enabled (see GET /features for the anonymousSubmission flag). Authenticated users must have a verified email. If a `summary` is provided, it is persisted as the post excerpt. Otherwise an excerpt is derived heuristically. Includes honeypot and timing-based spam prevention - frontends should include the 'website' field as a CSS-hidden input (left empty) and set 'formLoadedAt' to Date.now() when the form renders.","operationId":"createPost","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContentHttpRequest"}}},"required":true},"responses":{"201":{"description":"Post created as draft","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContentResponse"}}}},"400":{"description":"Validation error (blank title, blank content, or unverified email)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Authentication required (when anonymous submission is disabled)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/posts/{year}/{month}/{slug}/comments":{"get":{"tags":["Comments"],"summary":"Get threaded comments for a post","operationId":"getComments","parameters":[{"name":"year","in":"path","required":true,"schema":{"type":"integer","format":"int32","maximum":3000,"minimum":2007}},{"name":"month","in":"path","required":true,"schema":{"type":"integer","format":"int32","maximum":12,"minimum":1}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Comment thread","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentThreadResponse"}}}},"404":{"description":"Post not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}},"post":{"tags":["Comments"],"summary":"Add a comment to a post","operationId":"createComment","parameters":[{"name":"year","in":"path","required":true,"schema":{"type":"integer","format":"int32","maximum":3000,"minimum":2007}},{"name":"month","in":"path","required":true,"schema":{"type":"integer","format":"int32","maximum":12,"minimum":1}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCommentHttpRequest"}}},"required":true},"responses":{"201":{"description":"Comment created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CommentDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Post or parent comment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/posts/{id}/access":{"post":{"tags":["Posts"],"summary":"Record a UI-driven access of a blog post","operationId":"recordPostAccess","parameters":[{"name":"id","in":"path","description":"Post UUIDv7","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Access tracking accepted"}}}},"/posts/derive-tags":{"post":{"tags":["Posts"],"summary":"Suggest tags heuristically from unsaved draft content","description":"Returns non-persistent tag suggestions using deterministic heuristics (no AI call). Available to all callers.","operationId":"suggestTags","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestTagsHttpRequest"}}},"required":true},"responses":{"200":{"description":"Suggested tags","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuggestTagsResponse"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/posts/derive-summary":{"post":{"tags":["Posts"],"summary":"Derive a summary from unsaved draft content","description":"Returns a non-persistent summary generated from title + markdown content. Requires authentication. For an admin, with AI enabled, the model writes it in the site's editorial voice (source=ai); otherwise it is the heuristic excerpt (source=heuristic), the same text shape used for persisted excerpts.","operationId":"deriveSummary","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeriveSummaryHttpRequest"}}},"required":true},"responses":{"200":{"description":"Derived summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeriveSummaryResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/posts/derive-factoids":{"post":{"tags":["Posts"],"summary":"Find the factoids an unsaved draft mentions","description":"Returns the factoids the draft's prose mentions (whole words, ignoring case; code and links skipped), each with its definition and whether the draft already links it, and the [[…]] links naming factoids that don't exist. Nothing is stored; no AI. Requires authentication.","operationId":"deriveFactoids","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeriveFactoidsHttpRequest"}}},"required":true},"responses":{"200":{"description":"Factoids mentioned, and links to factoids that don't exist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeriveFactoidsResponse"}}}},"401":{"description":"Authentication required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Invalid input","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/mcp":{"get":{"tags":["mcp-controller"],"operationId":"info","responses":{"200":{"description":"OK","content":{"text/plain":{"schema":{"type":"string"}}}}}},"post":{"tags":["mcp-controller"],"operationId":"handle","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcRequest"}}},"required":true},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/factoids/derive":{"post":{"tags":["Factoids"],"summary":"Draft a factoid for a name, for its author to edit and save","description":"Writes a draft (text, URLs, tags, see-also) in the knowledge base's style, with the line the bot would say and whether it fits. URLs are checked to answer, tags limited to those in use (one new at most), see-also to existing factoids. Nothing is stored: save with PUT /factoids/{selector}. Needs AI; signed-in readers only.","operationId":"deriveFactoid","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeriveFactoidHttpRequest"}}},"required":true},"responses":{"200":{"description":"The draft","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactoidDraft"}}}},"400":{"description":"No name, or one too long","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"A factoid by that name exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"503":{"description":"AI isn't configured, or the draft couldn't be written","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/auth/refresh":{"post":{"tags":["Authentication"],"summary":"Refresh an expired JWT token using a refresh token cookie","operationId":"refresh","responses":{"200":{"description":"Token refreshed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"401":{"description":"Invalid or expired refresh token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/auth/otp/verify":{"post":{"tags":["Authentication"],"summary":"Verify a one-time sign-in code","operationId":"verifyOtp","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpVerifyRequest"}}},"required":true},"responses":{"200":{"description":"Authentication successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"401":{"description":"Invalid or expired code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"429":{"description":"Too many attempts for this identity; try again shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/auth/otp/request":{"post":{"tags":["Authentication"],"summary":"Request a one-time sign-in code","operationId":"requestOtp","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OtpRequest"}}},"required":true},"responses":{"202":{"description":"Accepted; a code is sent only when the identity resolves on the chosen channel","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}}}}},"/auth/logout":{"post":{"tags":["Authentication"],"summary":"Log out the current session","operationId":"logout","responses":{"204":{"description":"Logged out"}}}},"/admin/tags/stoplist":{"get":{"tags":["Admin - Tags"],"summary":"The tag stoplist","operationId":"listTagStops","responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"The stoplisted terms, alphabetically","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TagStopEntry"}}}}}},"security":[{"bearerAuth":[]}]},"post":{"tags":["Admin - Tags"],"summary":"Stoplist a term","description":"The term is dropped, silently, from every tag list written from now on. Tags already stored are left alone.","operationId":"createTagStop","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagStopRequest"}}},"required":true},"responses":{"200":{"description":"The term as stoplisted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagStopEntry"}}}},"400":{"description":"No term, a system tag, or an alias","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]},"delete":{"tags":["Admin - Tags"],"summary":"Take a term off the stoplist","operationId":"deleteTagStop","parameters":[{"name":"term","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The term removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagStopEntry"}}}},"404":{"description":"The term isn't stoplisted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/tags/review/{id}/split":{"post":{"tags":["Admin - Tags"],"summary":"Split a queued tag into its parts","description":"For a missing comma: every post and factoid carrying the queued tag carries the parts instead, in one transaction, and the queued tag's row goes. The parts default to the entry's hint tags. The entry is marked SPLIT. With `dryRun=true` nothing changes: the answer is what the split would do.","operationId":"splitTagReview","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"dryRun","in":"query","description":"true to preview: the answer is what the action would do, and nothing is changed","required":false,"schema":{"type":"boolean","default":false}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagSplitRequest"}}}},"responses":{"200":{"description":"What changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagChangeResult"}}}},"400":{"description":"Fewer than two parts, or no parts and no hint to take them from","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such entry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/tags/review/{id}/keep":{"post":{"tags":["Admin - Tags"],"summary":"Keep a queued tag as a real tag","description":"The tag stays as it is; the entry is marked KEPT.","operationId":"keepTagReview","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The tag kept","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagChangeResult"}}}},"400":{"description":"The entry was already decided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such entry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/tags/review/{id}/dismiss":{"post":{"tags":["Admin - Tags"],"summary":"Dismiss a queued tag","description":"The tag stays as it is; the entry is marked DISMISSED.","operationId":"dismissTagReview","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The tag dismissed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagChangeResult"}}}},"400":{"description":"The entry was already decided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such entry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/tags/review/{id}/alias":{"post":{"tags":["Admin - Tags"],"summary":"Alias a queued tag to an existing one","description":"Every post and factoid carrying the queued tag is re-pointed to `tag`, in one transaction; the queued tag's own row goes, and it becomes an alias of `tag`. The entry is marked ALIASED. With `dryRun=true` nothing changes: the answer is what the alias would do, the posts and factoids it would re-point.","operationId":"aliasTagReview","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"dryRun","in":"query","description":"true to preview: the answer is what the action would do, and nothing is changed","required":false,"schema":{"type":"boolean","default":false}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagReviewAliasRequest"}}},"required":true},"responses":{"200":{"description":"What changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagChangeResult"}}}},"400":{"description":"The target isn't an existing tag, or is the same tag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such entry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/tags/aliases":{"get":{"tags":["Admin - Tags"],"summary":"Every tag alias","operationId":"listTagAliases","responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"The aliases, by alias","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TagAliasEntry"}}}}}},"security":[{"bearerAuth":[]}]},"post":{"tags":["Admin - Tags"],"summary":"Make a tag an alias of another","description":"`alias` becomes an alias of the existing tag `tag`. Any posts and factoids carrying `alias` are re-pointed to `tag` in one transaction, its row goes, and its own aliases move to `tag`. Writing or looking up `alias` then finds `tag`. With `dryRun=true` nothing changes: the answer is what the alias would do.","operationId":"createTagAlias","parameters":[{"name":"dryRun","in":"query","description":"true to preview: the answer is what the action would do, and nothing is changed","required":false,"schema":{"type":"boolean","default":false}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagAliasRequest"}}},"required":true},"responses":{"200":{"description":"What changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagChangeResult"}}}},"400":{"description":"No alias, a target that isn't a tag, a system tag, or the same tag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]},"delete":{"tags":["Admin - Tags"],"summary":"Remove a tag alias","description":"Nothing is re-pointed back: what was merged stays merged, and the name is free to be a tag of its own again.","operationId":"deleteTagAlias","parameters":[{"name":"alias","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The alias removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagAliasEntry"}}}},"404":{"description":"No such alias","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/tags/map":{"post":{"tags":["Admin - RSS"],"summary":"Map a feed tag to an existing tag","description":"`name` becomes an alias of the existing tag `tag` (as `POST /admin/tags/aliases` makes one, taken off the stoplist first if it was there), so feed entries carrying it carry `tag`, and so does anything written with it. Marked MAPPED.","operationId":"mapFeedTag","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedTagMapRequest"}}},"required":true},"responses":{"200":{"description":"The feed tag as decided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedTagEntry"}}}},"400":{"description":"The target isn't an existing tag, or is the feed tag itself","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No feed has carried that tag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/tags/ignore":{"post":{"tags":["Admin - RSS"],"summary":"Ignore a feed tag","description":"`name` is stoplisted (its alias removed first if it had one): dropped from feed entries' tags, and from every tag list written from now on. Marked IGNORED.","operationId":"ignoreFeedTag","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedTagRequest"}}},"required":true},"responses":{"200":{"description":"The feed tag as decided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedTagEntry"}}}},"400":{"description":"No name given","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No feed has carried that tag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/tags/create":{"post":{"tags":["Admin - RSS"],"summary":"Create a feed tag as a tag now","description":"`name` is created as a tag without waiting for the threshold, through the vocabulary's create rule: a doubtful one goes to the review queue, and the AI near-miss checks it, as for any new tag. Marked CREATED.","operationId":"createFeedTag","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedTagRequest"}}},"required":true},"responses":{"200":{"description":"The feed tag as decided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedTagEntry"}}}},"400":{"description":"No name given","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No feed has carried that tag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/rating-guesses/run":{"post":{"tags":["Admin - RSS"],"summary":"Run a guess pass now","description":"Guesses the items received in the lookback that have no guess yet, as the daily pass does, and answers what it did. Only while the guess is on (`RSS_RATING_MODEL_GUESS`).","operationId":"runRssRatingGuesses","responses":{"200":{"description":"What the pass did","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RssRatingGuessRunResponse"}}}},"409":{"description":"The guess is off, or a pass is running already","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/opml/import":{"post":{"tags":["Admin - RSS"],"summary":"Import RSS feeds from an OPML or plain text upload","description":"Accepts a multipart file field named `file`. OPML is parsed first; non-OPML files fall back to line-by-line URL extraction.","operationId":"importOpml","requestBody":{"content":{"text/plain":{"schema":{"type":"string"}},"application/xml":{"schema":{"type":"string"}},"text/xml":{"schema":{"type":"string"}},"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}},"required":true},"responses":{"200":{"description":"Import summary","content":{"application/json":{}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/posts/{id}/versions/{kind}":{"post":{"tags":["Admin - Post Versions"],"summary":"Attach or replace a post's version","description":"Uploads a post's recording (`kind` `audio`, the only kind accepted) as multipart/form-data: `file`, and optionally `duration` (whole seconds; read from the file when left out) and `note` (up to 500 characters, such as \"read by the author\"). The file must be MP3 (`audio/mpeg`), MP4/M4A (`audio/mp4`), Ogg Vorbis (`audio/ogg`) or Ogg Opus (`audio/ogg`, `audio/opus`); its first bytes are checked, and a declared type that disagrees is refused. A version the post had is replaced at the same address. Answers the version as the post API shows it.","operationId":"putPostVersion","parameters":[{"name":"id","in":"path","description":"Post UUIDv7","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"kind","in":"path","description":"The version's kind","required":true,"schema":{"type":"string"},"example":"audio"},{"name":"duration","in":"query","description":"Length in whole seconds; read from the file when left out","required":false,"schema":{"type":"integer","format":"int32"}},{"name":"note","in":"query","description":"A note, such as \"read by the author\"","required":false,"schema":{"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]}}}},"responses":{"201":{"description":"Attached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostVersionView"}}}},"200":{"description":"Replaced","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PostVersionView"}}}},"400":{"description":"A kind other than audio, an empty file, or a bad duration or note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Another upload of the same version was being stored","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"413":{"description":"The file is larger than the limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"415":{"description":"Not audio of an accepted type, or not the type it was sent as","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"503":{"description":"Media storage isn't configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such post, or it's deleted; or, to delete, it has no such version","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]},"delete":{"tags":["Admin - Post Versions"],"summary":"Remove a post's version","description":"Removes the post's version of this kind and its file.","operationId":"deletePostVersion","parameters":[{"name":"id","in":"path","description":"Post UUIDv7","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"kind","in":"path","description":"The version's kind","required":true,"schema":{"type":"string"},"example":"audio"}],"responses":{"401":{"description":"Not authenticated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such post, or it's deleted; or, to delete, it has no such version","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"204":{"description":"Removed","content":{"*/*":{"schema":{"type":"object"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/posts/{id}/derive-tags":{"post":{"tags":["Admin - Post Management"],"summary":"Derive AI tag suggestions from unsaved editor content","operationId":"deriveTags","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeriveTagsHttpRequest"}}},"required":true},"responses":{"200":{"description":"Derived tags","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeriveTagsResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"400":{"description":"Invalid input or AI unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/moderation/reports/{id}/unhide":{"post":{"tags":["Admin - Moderation"],"summary":"Unhide lines of a report","description":"Puts hidden lines of the report's excerpt back. The report's status stays.","operationId":"unhideModerationReportLines","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationLinesRequest"}}},"required":true},"responses":{"200":{"description":"The action, as recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationActionResult"}}}},"400":{"description":"No lines, too many, or lines not in the report's excerpt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/moderation/reports/{id}/purge":{"post":{"tags":["Admin - Moderation"],"summary":"Purge lines of a report","description":"Deletes lines of the report's excerpt for good, for what must not be kept. It can't be undone, so `confirm` must be true. The ids stay on the report and the action. Marks the report actioned.","operationId":"purgeModerationReportLines","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationPurgeRequest"}}},"required":true},"responses":{"200":{"description":"The action, as recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationActionResult"}}}},"400":{"description":"Not confirmed, no lines, too many, or lines not in the report's excerpt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/moderation/reports/{id}/hide":{"post":{"tags":["Admin - Moderation"],"summary":"Hide lines of a report","description":"Takes lines of the report's excerpt out of the public log browser, its search and everything else that reads the log. They're kept, and an admin can unhide them. Marks the report actioned.","operationId":"hideModerationReportLines","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationLinesRequest"}}},"required":true},"responses":{"200":{"description":"The action, as recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationActionResult"}}}},"400":{"description":"No lines, too many, or lines not in the report's excerpt","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/moderation/reports/{id}/dismiss":{"post":{"tags":["Admin - Moderation"],"summary":"Dismiss a report","description":"Closes an open report as not abuse. The report stays readable.","operationId":"dismissModerationReport","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationDismissRequest"}}}},"responses":{"200":{"description":"The action, as recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationActionResult"}}}},"400":{"description":"The report isn't open","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"No such report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/moderation/lines/unhide":{"post":{"tags":["Admin - Moderation"],"summary":"Unhide log lines","description":"Puts hidden lines back in public view.","operationId":"unhideModerationLines","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationLinesRequest"}}},"required":true},"responses":{"200":{"description":"The action, as recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationActionResult"}}}},"400":{"description":"No lines, too many, or lines that don't exist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/moderation/lines/hide":{"post":{"tags":["Admin - Moderation"],"summary":"Hide log lines","description":"Hides lines found in a log day rather than a report: kept, and out of public view.","operationId":"hideModerationLines","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationLinesRequest"}}},"required":true},"responses":{"200":{"description":"The action, as recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationActionResult"}}}},"400":{"description":"No lines, too many, or lines that don't exist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/console":{"post":{"tags":["Web console"],"summary":"Run a console command","description":"Submits one line, as typed at the stdin console (`aho-corasick`, `foo is bar`, `calc 2+2`), and returns at once with its correlation id: 202 means it was accepted, not that it succeeded. Its output arrives on GET /admin/console/stream as `result` events carrying that id, possibly before this answer does; a command may produce none, one or several. Open the stream first; output with no stream open is dropped. Requires an active admin, a JSON body, and the header `X-Web-Console: 1`; a request authenticated by the access-token cookie must also come from a trusted Origin (or Referer). Not retried automatically: a lost answer doesn't mean the command didn't run.","operationId":"command","parameters":[{"name":"X-Web-Console","in":"header","required":true,"schema":{"type":"string","enum":["1"]}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebConsoleCommand"}}}},"responses":{"202":{"description":"Accepted for processing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebConsoleAccepted"}}}},"400":{"description":"No line, or one with newlines or too long","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not signed in","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Not an active admin, or the request's source isn't trusted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"413":{"description":"The body is too large","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"415":{"description":"The body isn't JSON","content":{"application/json":{"schema":{"type":"object"}}}},"429":{"description":"Too many commands; try again shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/admin/categories":{"post":{"tags":["Admin - Category Management"],"summary":"Create a new category","operationId":"createCategory","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCategoryRequest"}}},"required":true},"responses":{"201":{"description":"Category created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCategoryResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/atlas/relayout":{"post":{"tags":["Atlas"],"summary":"Lay the Atlas out again (admin)","description":"Recomputes the whole layout from every published post and factoid, stores it and returns it. Every place may move, so readers lose the map they've learned; new tags are placed without this.","operationId":"relayout","responses":{"200":{"description":"The new map","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AtlasResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/taxonomy":{"get":{"tags":["Taxonomy"],"summary":"List taxonomy tags, categories, and aggregate union counts","operationId":"getTaxonomy","responses":{"200":{"description":"Taxonomy snapshot","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TaxonomySnapshot"}}}}}}},"/ssr/posts/{year}/{month}/{slug}":{"get":{"tags":["ssr-controller"],"operationId":"post","parameters":[{"name":"year","in":"path","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"month","in":"path","required":true,"schema":{"type":"integer","format":"int32"}},{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/ssr/pages/{slug}":{"get":{"tags":["ssr-controller"],"operationId":"page","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/ssr/":{"get":{"tags":["ssr-controller"],"operationId":"home","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":20}}],"responses":{"200":{"description":"OK","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/sitemap.xml":{"get":{"tags":["sitemap-controller"],"operationId":"sitemap","responses":{"200":{"description":"OK","content":{"application/xml":{"schema":{"type":"string"}}}}}}},"/rss/items":{"get":{"tags":["RSS Aggregator"],"summary":"List stored RSS items","operationId":"items","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":20}},{"name":"feed","in":"query","required":false,"schema":{"type":"string"}},{"name":"title","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated stored RSS items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RssAggregatedItemsResponse"}}}}}}},"/rss/feeds":{"get":{"tags":["RSS Aggregator"],"summary":"List active RSS feed sources","operationId":"feeds","responses":{"200":{"description":"Active RSS feed sources available to the aggregator UI","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RssFeedSourcesResponse"}}}}}}},"/posts/{year}/{month}/{slug}":{"get":{"tags":["Posts"],"summary":"Get a published post by slug","description":"Returns full post detail including rendered HTML, tags, categories, and comment count. Draft posts are only visible to their author and admins.","operationId":"getPostBySlug","parameters":[{"name":"year","in":"path","description":"Publication year","required":true,"schema":{"type":"integer","format":"int32","maximum":3000,"minimum":2007},"example":2026},{"name":"month","in":"path","description":"Publication month (1-12)","required":true,"schema":{"type":"integer","format":"int32","maximum":12,"minimum":1},"example":3},{"name":"slug","in":"path","description":"URL-safe slug derived from the post title","required":true,"schema":{"type":"string"},"example":"my-post"}],"responses":{"200":{"description":"Post detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentDetail"}}}},"404":{"description":"Post not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/posts/search":{"get":{"tags":["Posts"],"summary":"Search published posts","description":"Full-text search across post titles (weighted higher) and excerpts. Only published, non-deleted posts are searched. Results are ranked by relevance. Blank queries return an empty list.","operationId":"searchPosts","parameters":[{"name":"q","in":"query","description":"Search query","required":true,"schema":{"type":"string"},"example":"virtual threads"},{"name":"page","in":"query","description":"Zero-based page index","required":false,"schema":{"type":"integer","format":"int32","default":0},"example":0},{"name":"size","in":"query","description":"Number of results per page","required":false,"schema":{"type":"integer","format":"int32","default":20},"example":20}],"responses":{"200":{"description":"Search results ranked by relevance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentListResponse"}}}}}}},"/posts/popular":{"get":{"tags":["Posts"],"summary":"List popular published posts","description":"Returns published posts ordered by decayed access temperature. The default page size is intentionally small for compact homepage widgets.","operationId":"listPopularPosts","parameters":[{"name":"page","in":"query","description":"Zero-based page index","required":false,"schema":{"type":"integer","format":"int32","default":0},"example":0},{"name":"size","in":"query","description":"Number of posts per page","required":false,"schema":{"type":"integer","format":"int32","default":3},"example":3}],"responses":{"200":{"description":"Paginated list of popular published posts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentListResponse"}}}}}}},"/podcast.xml":{"get":{"tags":["blog-feed-controller"],"operationId":"podcast","responses":{"200":{"description":"OK","content":{"application/xml":{"schema":{"type":"string"}}}}}}},"/pages/{slug}":{"get":{"tags":["Pages"],"summary":"Get a system page by slug","description":"Returns a page from the _pages or _sidebar category (About, Policies, and the like) as the same ContentDetail a post has, at its undated address.","operationId":"getPage","parameters":[{"name":"slug","in":"path","description":"The page's slug","required":true,"schema":{"type":"string"},"example":"about"}],"responses":{"200":{"description":"Page detail","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentDetail"}}}},"404":{"description":"Page not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/media/posts/{postId}/{kind}":{"get":{"tags":["Media"],"summary":"Fetch a post version's media","description":"The bytes of a post's version, such as its recording (`kind` `audio`). Supports `Range` requests (206 Partial Content; 416 when the range is past the end) and `If-None-Match`. The address stays the same when the version is replaced; the ETag changes. Public for published posts.","operationId":"getPostMedia","parameters":[{"name":"postId","in":"path","description":"Post UUIDv7","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"kind","in":"path","description":"The version's kind","required":true,"schema":{"type":"string"},"example":"audio"}],"responses":{"200":{"description":"The whole file","content":{"audio/*":{"schema":{"type":"string","format":"binary"}}}},"206":{"description":"The range asked for","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}},"304":{"description":"Not modified","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"No such media, or it isn't public","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}},"416":{"description":"The range is past the end of the file","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}}}}},"/media/posts/{postId}/{kind}/{variant}":{"get":{"tags":["Media"],"summary":"Fetch a post version's media, by variant","description":"As `/media/posts/{postId}/{kind}`, for a kind with several variants.","operationId":"getPostMediaVariant","parameters":[{"name":"postId","in":"path","description":"Post UUIDv7","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"kind","in":"path","description":"The version's kind","required":true,"schema":{"type":"string"}},{"name":"variant","in":"path","description":"The version's variant","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The whole file","content":{"audio/*":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"No such media, or it isn't public","content":{"*/*":{"schema":{"type":"string","format":"binary"}}}}}}},"/logs":{"get":{"tags":["Logs"],"summary":"Get one day of logs for a provenance","description":"Every line of one UTC day, oldest first, each with its `kind`: `MESSAGE`, `JOIN`, `PART`, `QUIT`, `NICK` or `TOPIC`. `events=none` leaves out channel events (joins, parts, quits, nick changes and topics); `kinds` picks the kinds to include. Without either, every line is returned.","operationId":"getDayLogs","parameters":[{"name":"provenance","in":"query","required":true,"schema":{"type":"string"}},{"name":"day","in":"query","required":false,"schema":{"type":"string"}},{"name":"events","in":"query","description":"`all` (the default) for every line, `none` for what was said only, without joins, parts, quits, nick changes and topics","required":false,"schema":{"type":"string"}},{"name":"kinds","in":"query","description":"Only these kinds, comma-separated: `MESSAGE`, `JOIN`, `PART`, `QUIT`, `NICK`, `TOPIC` (any case). With `events`, a line must pass both.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Log entries for day","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogDayResponse"}}}},"400":{"description":"A bad day, or an unknown `events` or `kinds` value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Provenance not found or not authorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/logs/search":{"get":{"tags":["Logs"],"summary":"Search one channel's logs","description":"Lines in one channel whose text contains `q`, ignoring case, and written by `sender` (a nick, ignoring case), newest first. Give `q`, `sender`, or both: `sender` alone lists everything that person said there. Only a channel the caller may browse is searched; any other is a 404, as for the day logs, so a search can't reveal that a hidden channel exists. `q` is matched as written, between 3 and 200 characters. Each hit carries its `kind`. `events=none` leaves out channel events (joins, parts, quits, nick changes and topics); `kinds` picks the kinds to include. Without either, every line is returned.","operationId":"search","parameters":[{"name":"provenance","in":"query","required":true,"schema":{"type":"string"}},{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"sender","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":50}},{"name":"events","in":"query","description":"`all` (the default) for every line, `none` for what was said only, without joins, parts, quits, nick changes and topics","required":false,"schema":{"type":"string"}},{"name":"kinds","in":"query","description":"Only these kinds, comma-separated: `MESSAGE`, `JOIN`, `PART`, `QUIT`, `NICK`, `TOPIC` (any case). With `events`, a line must pass both.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page of matching lines","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogSearchResponse"}}}},"400":{"description":"Neither a query nor a sender, a short or long query, a bad page or size, or an unknown `events` or `kinds` value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Provenance not found or not authorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"429":{"description":"Too many searches; try again shortly","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/logs/provenances":{"get":{"tags":["Logs"],"summary":"List browseable log provenances for the current user","operationId":"listProvenances","responses":{"200":{"description":"Filtered provenance list","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogProvenanceListResponse"}}}}}}},"/logs/channels/{protocol}/{service}/{name}":{"get":{"tags":["Logs"],"summary":"Find a channel by its readable address","description":"The channel `/logs/{protocol}/{service}/{name}` names, among those the caller may browse: `irc/libera/primate`. An IRC name is matched as written, then with `#`, then with `##`; other protocols' channels by their names, or their ids. Its `provenanceUri` is what the day view and search take. Any other name is a 404, as a hidden channel is, so an address can't reveal one.","operationId":"channel","parameters":[{"name":"protocol","in":"path","required":true,"schema":{"type":"string"}},{"name":"service","in":"path","required":true,"schema":{"type":"string"}},{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The channel","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogChannelResponse"}}}},"404":{"description":"No such channel, or not one the caller may browse","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/karma/leaderboard":{"get":{"tags":["karma-controller"],"operationId":"leaderboard","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":10}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KarmaLeaderboardResponse"}}}}}}},"/feed.atom":{"get":{"tags":["blog-feed-controller"],"operationId":"feed","responses":{"200":{"description":"OK","content":{"application/xml":{"schema":{"type":"string"}}}}}}},"/feed.xml":{"get":{"tags":["blog-feed-controller"],"operationId":"feed_1","responses":{"200":{"description":"OK","content":{"application/xml":{"schema":{"type":"string"}}}}}}},"/features":{"get":{"tags":["features-controller"],"operationId":"getFeatures","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeaturesResponse"}}}}}}},"/factoids":{"get":{"tags":["Factoids"],"operationId":"list","parameters":[{"name":"q","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":20}},{"name":"since","in":"query","required":false,"schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactoidListResponse"}}}}}}},"/categories":{"get":{"tags":["Categories"],"summary":"List active categories","operationId":"listCategories","responses":{"200":{"description":"List of active categories","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CategorySummary"}}}}}}}},"/blog/rss.xml":{"get":{"tags":["rss-feed-controller"],"operationId":"rssFeed","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}},"/blog/atom.xml":{"get":{"tags":["rss-feed-controller"],"operationId":"atomFeed","responses":{"200":{"description":"OK","content":{"*/*":{"schema":{"type":"string"}}}}}}},"/auth/session":{"get":{"tags":["Authentication"],"summary":"Validate the current session token","operationId":"session","responses":{"200":{"description":"Authenticated user principal","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserPrincipal"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/auth/export":{"get":{"tags":["Authentication"],"summary":"Export the authenticated user's data","operationId":"exportUserData","responses":{"200":{"description":"User data export","content":{"application/json":{"schema":{"type":"object"}}}}},"security":[{"bearerAuth":[]}]}},"/atlas":{"get":{"tags":["Atlas"],"summary":"The Atlas: regions and places (tags) with positions, counts and pins","description":"Laid out on the first request and stored; later requests read the stored map. A new tag is placed beside its strongest relative without moving anything; a tag no longer used is left out but keeps its place. Conditional (ETag, Last-Modified).","operationId":"getAtlas","responses":{"200":{"description":"The map","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AtlasResponse"}}}}}}},"/atlas/places/{tag}":{"get":{"tags":["Atlas"],"summary":"One place on the Atlas, with every article and factoid found there","description":"The tag is matched ignoring case, and an alias finds the place of the tag it means. 404 when it isn't on the map.","operationId":"getPlace","parameters":[{"name":"tag","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The place","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AtlasPlaceDetailResponse"}}}},"404":{"description":"No place on the map has that tag","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/admin/users":{"get":{"tags":["Admin - User Management"],"summary":"List erased user sentinels","operationId":"listByStatus","parameters":[{"name":"status","in":"query","required":true,"schema":{"type":"string","enum":["ACTIVE","SUSPENDED","ERASED"]}}],"responses":{"200":{"description":"Users with the requested status","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserPrincipal"}}}}}},"security":[{"bearerAuth":[]}]}},"/admin/users/{username}/export":{"get":{"tags":["Admin - User Management"],"summary":"Export a user's data for admin review","operationId":"exportUserData_1","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"User data export","content":{"application/json":{"schema":{"type":"object"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/tags/review":{"get":{"tags":["Admin - Tags"],"summary":"The tag review queue","description":"Doubtful new tags: a trailing-s pair with an existing tag (PLURAL), several words that are each a tag (MISSING_COMMA), or a near-duplicate only the AI found (AI). Open entries by default, the AI's most confident first, then newest. `openCount` is how many are open in all, for a launcher badge; `totalCount` and `totalPages` are for the status asked for, so any view can show page N of M.","operationId":"listTagReviews","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"open"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":50}}],"responses":{"200":{"description":"A page of the queue","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagReviewListResponse"}}}},"400":{"description":"A bad status, page or size","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/tags/review/{id}":{"get":{"tags":["Admin - Tags"],"summary":"One entry in the tag review queue","description":"The entry, open or decided, as the queue lists it.","operationId":"getTagReview","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The entry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TagReviewEntry"}}}},"404":{"description":"No such entry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/tags/actions":{"get":{"tags":["Admin - Tags"],"summary":"Recent changes to the tag vocabulary","description":"Aliases, splits, keeps, dismissals and stoplist changes: who and when.","operationId":"listTagActions","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":50}}],"responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"The latest changes, newest first","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/TagActionEntry"}}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/tags":{"get":{"tags":["Admin - RSS"],"summary":"Feed tags the tag vocabulary didn't know","description":"Waiting feed tags by default, the most carried first: counted, and created as tags once on `promoteEntries` entries across `promoteFeeds` feeds. `status=all` includes the decided ones (PROMOTED, CREATED, MAPPED, IGNORED). Feed tags that are tags, aliases or stoplisted already never wait, and aren't listed. `waitingCount` is how many wait in all, for a launcher badge; `totalCount` and `totalPages` are for the status asked for, so any view can show page N of M.","operationId":"listFeedTags","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"waiting"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":50}}],"responses":{"200":{"description":"A page of feed tags","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedTagListResponse"}}}},"400":{"description":"A bad status, page or size","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/ratings.csv":{"get":{"tags":["Admin - RSS"],"summary":"Export ratings and guesses as CSV","description":"Every item with a rating or a guess: item_id, feed, title, link, published, rating, rated_by, rated_at, guess, guess_confidence, guess_reason, guess_model, guess_source, guessed_at, guess_rubric. Empty where there's no rating or no guess; guess_rubric is empty for guesses made before the rubric was recorded.","operationId":"exportRssRatings","responses":{"401":{"description":"Not authenticated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"The CSV","content":{"text/csv":{"schema":{"type":"string"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/rating-stats":{"get":{"tags":["Admin - RSS"],"summary":"How the model's guess agrees with the ratings","description":"Counts per rating; agreement overall and per rating, and the confusion matrix, over the items with both a rating and a guess; and the items guessed RATES but rated DULL, and rated RATES but guessed DULL.","operationId":"rssRatingStats","responses":{"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"200":{"description":"The stats","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RssRatingStatsResponse"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/rating-guesses":{"get":{"tags":["Admin - RSS"],"summary":"The model's guesses","description":"The model's guesses, the newest first, each with its item, the rubric it was made with (a short hash), and the editor's `rating` and whether the guess `agrees` with it (both null when the item is unrated). Unrated items are listed too; the item list (`/admin/rss/items`) never carries a guess. `label` is `all` (default), `rates`, `might` or `dull`; `rated` is `all` (default), `yes` or `no`; `feed` matches a feed's title or address and `title` a part of the item's title, as on `/rss/items`; `size` is 1 to 100. `totalCount` and `totalPages` are for the filter asked for.","operationId":"listRssRatingGuesses","parameters":[{"name":"label","in":"query","required":false,"schema":{"type":"string","default":"all"}},{"name":"rated","in":"query","required":false,"schema":{"type":"string","default":"all"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":50}},{"name":"feed","in":"query","required":false,"schema":{"type":"string"}},{"name":"title","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page of guesses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RssRatingGuessesResponse"}}}},"400":{"description":"A bad label or rated filter, page or size","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/opml":{"get":{"tags":["Admin - RSS"],"summary":"Export active RSS feeds as OPML","operationId":"export","responses":{"200":{"description":"OPML export of active feeds","content":{"application/xml":{}}},"401":{"description":"Not authenticated","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/rss/items":{"get":{"tags":["Admin - RSS"],"summary":"Feed items with their ratings","description":"Stored items as `/rss/items` lists them (active feeds, newest first, `feed` and `title` filters alike), each with its `rating`, `ratedBy` and `ratedAt` (null when unrated). `rating` is `all` (default), `rates`, `might`, `dull` or `unrated`; `size` is 1 to 100. `totalCount` and `totalPages` are for the filter asked for. Never carries the model's guess.","operationId":"listRatedRssItems","parameters":[{"name":"rating","in":"query","required":false,"schema":{"type":"string","default":"all"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":50}},{"name":"feed","in":"query","required":false,"schema":{"type":"string"}},{"name":"title","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"A page of items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRssItemsResponse"}}}},"400":{"description":"A bad rating filter, page or size","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/posts/scheduled":{"get":{"tags":["Admin - Post Management"],"summary":"List scheduled posts","description":"Approved posts whose publication date is still to come, soonest first. They're in neither the public lists nor the review queue until they go out.","operationId":"listScheduled","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":20}}],"responses":{"200":{"description":"Paginated list of scheduled posts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/posts/pending":{"get":{"tags":["Admin - Post Management"],"summary":"List pending draft posts for review","operationId":"listPending","parameters":[{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":20}},{"name":"deleted","in":"query","required":false,"schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Paginated list of draft posts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentListResponse"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/moderation/reports":{"get":{"tags":["Admin - Moderation"],"summary":"List abuse reports","description":"Reports from the hourly review, open ones first and newest first within each; or only those in `status`. `openCount` is how many are open in all, for a launcher badge.","operationId":"listModerationReports","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":0}},{"name":"size","in":"query","required":false,"schema":{"type":"integer","format":"int32","default":25}}],"responses":{"200":{"description":"A page of reports","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportListResponse"}}}},"400":{"description":"A bad status, page or size","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/moderation/reports/{id}":{"get":{"tags":["Admin - Moderation"],"summary":"One abuse report with its excerpt","description":"The report, the lines the review read (the person's and those around them, oldest first, hidden ones included and marked `hidden`; `flagged` raised a signal, `cited` the model pointed at), the ids of excerpt lines since purged, and every action taken on it.","operationId":"getModerationReport","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportDetail"}}}},"404":{"description":"No such report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/moderation/logs":{"get":{"tags":["Admin - Moderation"],"summary":"A day of a channel's log, hidden lines included","description":"One UTC day of a channel's log as an admin reviews it: every line with its id, hidden ones included and marked `hidden`, oldest first, at most 5000. Direct conversations are never returned. `day` defaults to today.","operationId":"getModerationLogDay","parameters":[{"name":"provenance","in":"query","required":true,"schema":{"type":"string"}},{"name":"day","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"The day's lines","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModerationLogDay"}}}},"400":{"description":"A day that isn't YYYY-MM-DD","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/factoids/report":{"get":{"tags":["Admin - Factoids"],"summary":"Where the factoid graph could be better","description":"Advisory, never edits (#133): factoids naming a parent that lists them without linking back; other factoids a factoid names without linking; see-also to factoids that don't exist; factoids whose one-line answer leaves parts out, or is cut off even so.","operationId":"factoidGraphReport","responses":{"200":{"description":"The report","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FactoidGraphReport"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/console/stream":{"get":{"tags":["Web console"],"summary":"The console's output, as server-sent events","description":"A stream of this admin's console output, shared by all their windows. Events: `ready` (once, when the stream is registered: wait for it before submitting), `result` (data: {correlationId, status, text}; status is success, error or unhandled, and text is absent when unhandled; correlationId is null for output nobody asked for here, such as a notification or a tell from elsewhere), and heartbeat comments. Several results may share a correlation id, and none marks a command finished. The stream closes when the credential expires, the account is no longer an admin, the client falls too far behind, or after an hour; reconnect for a fresh stream. Nothing missed while disconnected is replayed. Authenticates by the access-token cookie (as a browser's EventSource sends it) or a bearer token. Single backend instance only. Behind nginx, turn off buffering for this path (the response says `X-Accel-Buffering: no`) and allow a long read timeout.","operationId":"stream","responses":{"200":{"description":"The stream (text/event-stream); `result` events carry WebConsoleResult","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/WebConsoleResult"}}}},"401":{"description":"Not signed in","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Not an active admin","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"429":{"description":"Too many streams open, or opened too often","content":{"*/*":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}},"/auth/account":{"delete":{"tags":["Authentication"],"summary":"Erase the authenticated user's account","operationId":"deleteAccount","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAccountRequest"}}},"required":true},"responses":{"200":{"description":"Account deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/users/{username}":{"delete":{"tags":["Admin - User Management"],"summary":"Erase a user account (admin-initiated)","operationId":"eraseAccount","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Account erased","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/users/{username}/purge":{"delete":{"tags":["Admin - User Management"],"summary":"Purge all content from an erased user sentinel","operationId":"purgeErasedContent","parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Content purged","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MessageResponse"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/comments/{id}":{"delete":{"tags":["Admin - Comment Management"],"summary":"Delete a comment (soft by default, hard with ?hard=true)","operationId":"deleteComment","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"hard","in":"query","required":false,"schema":{"type":"boolean","default":false}}],"responses":{"200":{"description":"Comment deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentOperationConfirmation"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Comment not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}},"/admin/categories/{id}":{"delete":{"tags":["Admin - Category Management"],"summary":"Soft-delete a category","operationId":"deleteCategory","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Category deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContentOperationConfirmation"}}}},"401":{"description":"Not authenticated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Admin access required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Category not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}},"security":[{"bearerAuth":[]}]}}},"components":{"schemas":{"EditContentHttpRequest":{"type":"object","properties":{"title":{"type":["string","null"]},"markdownSource":{"type":["string","null"]},"slug":{"type":["string","null"]},"tags":{"type":["array","null"],"items":{"type":"string"}},"categoryIds":{"type":["array","null"],"items":{"type":"string","format":"uuid"}},"summary":{"type":["string","null"]},"publishedAt":{"type":["string","null"],"format":"date-time"},"sortOrder":{"type":["integer","null"],"format":"int32"}}},"ContentDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"renderedHtml":{"type":"string"},"excerpt":{"type":["string","null"]},"authorId":{"type":["string","null"],"format":"uuid"},"authorDisplayName":{"type":"string"},"status":{"type":"string","enum":["DRAFT","APPROVED"]},"publishedAt":{"type":["string","null"],"format":"date-time"},"sortOrder":{"type":"integer","format":"int32"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"commentCount":{"type":"integer","format":"int32"},"tags":{"type":"array","items":{"type":"string"}},"categories":{"type":"array","items":{"type":"string"}},"markdownSource":{"type":["string","null"]},"versions":{"type":"array","items":{"$ref":"#/components/schemas/PostVersionView"}}},"required":["authorDisplayName","createdAt","id","renderedHtml","slug","status","title","updatedAt"]},"PostVersionView":{"type":"object","description":"A version of a post: the post in another form, such as a recording of it","properties":{"kind":{"type":"string","description":"What it is; `audio` is the only kind so far","example":"audio"},"variant":{"type":["string","null"],"description":"Which one of its kind, when a kind has several; null for audio"},"mediaType":{"type":"string","description":"Its media type","example":"audio/mpeg"},"size":{"type":"integer","format":"int64","description":"Its size in bytes"},"duration":{"type":["integer","null"],"format":"int32","description":"Its length in whole seconds, when known"},"url":{"type":"string","description":"Where to fetch it: an absolute address on the API's own origin, stable while the version is replaced. Supports Range requests. Front ends must not rewrite it.","example":"https://api.bytecode.news/media/posts/0190a1b2-0000-7000-8000-000000000000/audio"},"note":{"type":["string","null"],"description":"A note about it, such as \"read by the author\""},"maker":{"type":["string","null"],"description":"Who made it: the uploader's display name, when known"},"current":{"type":"boolean","description":"Whether it was made from the post as it is now: false once the post's markdown has been edited since it was attached"},"createdAt":{"type":"string","format":"date-time","description":"When it was first attached"},"replacedAt":{"type":["string","null"],"format":"date-time","description":"When its content was last replaced; null if never"}},"required":["createdAt","current","kind","mediaType","size","url"]},"ProblemDetail":{"type":"object","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer","format":"int32"},"detail":{"type":"string"},"instance":{"type":"string","format":"uri"},"properties":{"type":"object","additionalProperties":{}}}},"FactoidSetHttpRequest":{"type":"object","properties":{"value":{"type":"string","description":"The attribute's new value","example":"a string-searching algorithm"},"attribute":{"type":["string","null"],"description":"The attribute to set: text (the default), urls, tags, languages, type, seealso, see or maven","example":"text"}},"required":["value"]},"FactoidAttributeResponse":{"type":"object","properties":{"type":{"type":"string"},"value":{"type":["string","null"]},"rendered":{"type":"string"}},"required":["rendered","type"]},"FactoidDetailResponse":{"type":"object","properties":{"selector":{"type":"string"},"locked":{"type":"boolean"},"updatedBy":{"type":["string","null"]},"updatedAt":{"type":"string","format":"date-time"},"lastAccessedAt":{"type":["string","null"],"format":"date-time"},"accessCount":{"type":"integer","format":"int64"},"createdAt":{"type":["string","null"],"format":"date-time"},"attributes":{"type":"array","items":{"$ref":"#/components/schemas/FactoidAttributeResponse"}}},"required":["accessCount","attributes","locked","selector","updatedAt"]},"EditCommentHttpRequest":{"type":"object","properties":{"markdownSource":{"type":"string"}},"required":["markdownSource"]},"CommentDetail":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"postId":{"type":"string","format":"uuid"},"authorDisplayName":{"type":"string"},"renderedHtml":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}},"required":["authorDisplayName","createdAt","id","postId","renderedHtml"]},"EditProfileRequest":{"type":"object","properties":{"displayName":{"type":["string","null"]},"email":{"type":["string","null"]}}},"UserPrincipal":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"username":{"type":"string"},"displayName":{"type":"string"},"role":{"type":"string","enum":["GUEST","USER","ADMIN","SUPER_ADMIN"]}},"required":["displayName","id","username"]},"MessageResponse":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]},"RoleUpdateRequest":{"type":"object","properties":{"newRole":{"type":"string","enum":["GUEST","USER","ADMIN","SUPER_ADMIN"]}},"required":["newRole"]},"RssItemRatingRequest":{"type":"object","properties":{"rating":{"type":"string","enum":["RATES","MIGHT","DULL"]}},"required":["rating"]},"RssItemRatingResponse":{"type":"object","properties":{"itemId":{"type":"string","format":"uuid"},"rating":{"type":"string","enum":["RATES","MIGHT","DULL"]},"ratedBy":{"type":"string"},"ratedAt":{"type":"string","format":"date-time"}},"required":["itemId","ratedAt","ratedBy","rating"]},"ApproveContentHttpRequest":{"type":"object","properties":{"publishedAt":{"type":"string","format":"date-time"}},"required":["publishedAt"]},"CreateContentHttpRequest":{"type":"object","description":"Request body for creating a new blog post draft","properties":{"title":{"type":"string","description":"Post title","example":"Understanding Virtual Threads"},"markdownSource":{"type":"string","description":"Post content in Markdown. No raw HTML allowed.","example":"Virtual threads change the concurrency model..."},"tags":{"type":["array","null"],"description":"Tag names to associate with the post. Created automatically if new.","example":["java","concurrency"],"items":{"type":"string"}},"categoryIds":{"type":["array","null"],"description":"Category UUIDv7s to associate. Non-existent or deleted IDs are skipped.","items":{"type":"string","format":"uuid"}},"summary":{"type":["string","null"],"description":"Optional manual summary text from the UI. This value is persisted as the post excerpt. If omitted or blank, the backend derives excerpt text heuristically from markdown content."},"formLoadedAt":{"type":["integer","null"],"format":"int64","description":"Epoch milliseconds when the form was rendered (Date.now()). Required. Submissions arriving less than 3 seconds after this timestamp are silently rejected.","example":1709740800000}}},"CreateContentResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"excerpt":{"type":["string","null"]},"status":{"type":"string","enum":["DRAFT","APPROVED"]},"authorId":{"type":["string","null"],"format":"uuid"},"authorDisplayName":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"tags":{"type":"array","items":{"type":"string"}},"categories":{"type":"array","items":{"type":"string"}}},"required":["authorDisplayName","createdAt","id","slug","status","title"]},"CreateCommentHttpRequest":{"type":"object","properties":{"parentCommentId":{"type":["string","null"],"format":"uuid"},"markdownSource":{"type":"string"}},"required":["markdownSource"]},"SuggestTagsHttpRequest":{"type":"object","properties":{"title":{"type":["string","null"]},"markdownSource":{"type":["string","null"]},"existingTags":{"type":["array","null"],"items":{"type":"string"}}}},"SuggestTagsResponse":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"required":["tags"]},"DeriveSummaryHttpRequest":{"type":"object","properties":{"title":{"type":["string","null"]},"markdownSource":{"type":["string","null"]}}},"DeriveSummaryResponse":{"type":"object","properties":{"summary":{"type":"string"},"source":{"type":"string","enum":["ai","heuristic"]}},"required":["summary"]},"DeriveFactoidsHttpRequest":{"type":"object","properties":{"markdownSource":{"type":["string","null"]}}},"DeriveFactoidsResponse":{"type":"object","properties":{"mentions":{"type":"array","items":{"$ref":"#/components/schemas/FactoidMention"}},"missing":{"type":"array","items":{"$ref":"#/components/schemas/MissingFactoid"}}},"required":["mentions","missing"]},"FactoidMention":{"type":"object","properties":{"selector":{"type":"string"},"term":{"type":"string"},"occurrences":{"type":"integer","format":"int32"},"definition":{"type":"string"},"linked":{"type":"boolean"}},"required":["definition","linked","occurrences","selector","term"]},"MissingFactoid":{"type":"object","properties":{"selector":{"type":"string"},"term":{"type":"string"}},"required":["selector","term"]},"JsonRpcRequest":{"type":"object","properties":{"jsonrpc":{"type":["string","null"]},"id":{},"method":{"type":["string","null"]},"params":{"type":["object","null"],"additionalProperties":{}}}},"DeriveFactoidHttpRequest":{"type":"object","properties":{"selector":{"type":["string","null"]},"context":{"type":["string","null"]}}},"FactoidDraft":{"type":"object","properties":{"selector":{"type":"string"},"text":{"type":"string"},"urls":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"seeAlso":{"type":"array","items":{"type":"string"}},"line":{"type":"string"},"lineLength":{"type":"integer","format":"int32"},"fits":{"type":"boolean"},"droppedUrls":{"type":"array","items":{"type":"string"}}},"required":["fits","line","lineLength","seeAlso","selector","tags","text","urls"]},"LoginResponse":{"type":"object","properties":{"token":{"type":"string"},"principal":{"$ref":"#/components/schemas/UserPrincipal"}},"required":["principal","token"]},"OtpVerifyRequest":{"type":"object","properties":{"email":{"type":["string","null"]},"code":{"type":"string"},"channel":{"type":"string","enum":["email","mattermost","sms"]},"address":{"type":["string","null"]},"server":{"type":["string","null"]}}},"OtpRequest":{"type":"object","properties":{"email":{"type":["string","null"]},"channel":{"type":"string","enum":["email","mattermost","sms"]},"address":{"type":["string","null"]},"server":{"type":["string","null"]}}},"TagStopRequest":{"type":"object","description":"A term for the stoplist","properties":{"term":{"type":"string"}}},"TagStopEntry":{"type":"object","description":"A stoplisted term","properties":{"term":{"type":"string"},"createdBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}},"required":["createdAt","createdBy","term"]},"TagSplitRequest":{"type":"object","description":"Split a tag into its parts","properties":{"parts":{"type":["array","null"],"description":"The parts; the entry's hint tags when left out","items":{"type":"string"}}}},"TagChangeResult":{"type":"object","description":"What an alias, split, keep or dismiss did, or, for a dry run, what an alias or split would do","properties":{"tag":{"type":"string","description":"The tag acted on"},"now":{"type":"array","description":"What it now stands for: the alias target, or the split's parts","items":{"type":"string"}},"posts":{"type":"integer","format":"int32","description":"Posts re-pointed"},"factoids":{"type":"integer","format":"int32","description":"Factoids whose tag lists were rewritten"}},"required":["factoids","now","posts","tag"]},"TagReviewAliasRequest":{"type":"object","description":"Alias a queued tag to an existing one","properties":{"tag":{"type":"string","description":"The existing tag the queued tag means"}},"required":["tag"]},"TagAliasRequest":{"type":"object","description":"Make a tag an alias of another","properties":{"alias":{"type":"string","description":"The name to alias"},"tag":{"type":"string","description":"The existing tag it means"}},"required":["alias","tag"]},"FeedTagMapRequest":{"type":"object","description":"Map a feed tag to an existing tag","properties":{"name":{"type":"string","description":"The feed tag"},"tag":{"type":"string","description":"The existing tag it means"}}},"FeedTagEntry":{"type":"object","description":"A feed tag the tag vocabulary didn't know, and what became of it","properties":{"name":{"type":"string","description":"The feed tag, normalized"},"written":{"type":"array","description":"How feeds wrote it, a few of the forms","items":{"type":"string"}},"status":{"type":"string","enum":["WAITING","PROMOTED","CREATED","MAPPED","IGNORED"]},"entries":{"type":"integer","format":"int32","description":"Feed entries carrying it"},"feeds":{"type":"integer","format":"int32","description":"Distinct feeds carrying it"},"firstSeen":{"type":"string","format":"date-time"},"lastSeen":{"type":"string","format":"date-time"},"tag":{"type":["string","null"],"description":"The tag it became or maps to; absent while waiting or ignored"},"decidedBy":{"type":["string","null"],"description":"Who decided: an admin, rss (promoted), or vocabulary"},"decidedAt":{"type":["string","null"],"format":"date-time"},"examples":{"type":"array","description":"A few of the entries carrying it, newest first","items":{"$ref":"#/components/schemas/FeedTagExample"}}},"required":["entries","examples","feeds","firstSeen","lastSeen","name","status","written"]},"FeedTagExample":{"type":"object","description":"A feed entry carrying a feed tag, as an example","properties":{"entryId":{"type":"string","format":"uuid"},"title":{"type":"string"},"link":{"type":"string"},"feedTitle":{"type":"string"}},"required":["entryId","feedTitle","link","title"]},"FeedTagRequest":{"type":"object","description":"A feed tag to ignore or create","properties":{"name":{"type":"string","description":"The feed tag"}}},"RssRatingGuessRunResponse":{"type":"object","properties":{"candidates":{"type":"integer","format":"int32","description":"Items received in the lookback with no guess, sent to the model"},"calls":{"type":"integer","format":"int32","description":"Calls made to the model, one per chunk"},"stored":{"type":"integer","format":"int32","description":"Guesses stored"},"skipped":{"type":"integer","format":"int32","description":"Items left without a guess: a bad, partial or missing answer"}},"required":["calls","candidates","skipped","stored"]},"DeriveTagsHttpRequest":{"type":"object","properties":{"title":{"type":["string","null"]},"markdownSource":{"type":["string","null"]},"existingTags":{"type":["array","null"],"items":{"type":"string"}}}},"DeriveTagsResponse":{"type":"object","properties":{"tags":{"type":"array","items":{"type":"string"}}},"required":["tags"]},"ModerationLinesRequest":{"type":"object","properties":{"lineIds":{"type":"array","description":"Message log line ids, at most 500","items":{"type":"string","format":"uuid"}},"note":{"type":["string","null"],"description":"Why, for the record"}},"required":["lineIds"]},"ModerationActionResult":{"type":"object","properties":{"action":{"$ref":"#/components/schemas/ModerationActionView"},"changed":{"type":"integer","format":"int32"}},"required":["action","changed"]},"ModerationActionView":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"reportId":{"type":["string","null"],"format":"uuid"},"action":{"type":"string","enum":["HIDE","UNHIDE","PURGE","DISMISS"]},"lineIds":{"type":"array","items":{"type":"string","format":"uuid"}},"note":{"type":["string","null"]},"actor":{"type":"string"},"actedAt":{"type":"string","format":"date-time"}},"required":["actedAt","action","actor","id","lineIds"]},"ModerationPurgeRequest":{"type":"object","properties":{"lineIds":{"type":"array","description":"Message log line ids, at most 500","items":{"type":"string","format":"uuid"}},"confirm":{"type":"boolean","description":"Must be true: the admin confirmed that this can't be undone"},"note":{"type":["string","null"],"description":"Why, for the record"}},"required":["lineIds"]},"ModerationDismissRequest":{"type":"object","properties":{"note":{"type":["string","null"],"description":"Why, for the record"}}},"WebConsoleCommand":{"type":"object","description":"One command line, as at the console: no prefix, no newlines.","properties":{"line":{"type":["string","null"]}}},"WebConsoleAccepted":{"type":"object","properties":{"correlationId":{"type":"string"}},"required":["correlationId"]},"CreateCategoryRequest":{"type":"object","properties":{"name":{"type":"string"},"parentId":{"type":["string","null"],"format":"uuid"}},"required":["name"]},"CreateCategoryResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"parentName":{"type":["string","null"]}},"required":["id","name","slug"]},"AtlasBounds":{"type":"object","description":"A rectangle in world units.","properties":{"x":{"type":"number","format":"double"},"y":{"type":"number","format":"double"},"width":{"type":"number","format":"double"},"height":{"type":"number","format":"double"}},"required":["height","width","x","y"]},"AtlasPin":{"type":"object","description":"An article or factoid pinned around its place.","properties":{"kind":{"type":"string","description":"What a pin is: an article or a factoid.","enum":["article","factoid"]},"title":{"type":"string","description":"An article's title, or a factoid's selector."},"slug":{"type":["string","null"],"description":"An article's canonical slug path (GET /posts/{slug})."},"selector":{"type":["string","null"],"description":"A factoid's selector (GET /factoids/{selector})."},"x":{"type":"number","format":"double"},"y":{"type":"number","format":"double"},"publishedAt":{"type":["string","null"],"format":"date-time","description":"When an article was published."},"createdAt":{"type":["string","null"],"format":"date-time","description":"When a factoid was first taught."},"updatedAt":{"type":["string","null"],"format":"date-time","description":"When a factoid last changed."}},"required":["kind","title","x","y"]},"AtlasPlaceResponse":{"type":"object","description":"A place on the map: a tag, where it is, and what's found there.","properties":{"tag":{"type":"string","description":"The tag, lowercased, as /taxonomy and /posts?tag= name it."},"region":{"type":"integer","format":"int32","description":"The id of the region it's in."},"x":{"type":"number","format":"double"},"y":{"type":"number","format":"double"},"articleCount":{"type":"integer","format":"int64","description":"Published posts carrying the tag: taxonomy's tags count."},"factoidCount":{"type":"integer","format":"int64","description":"Factoids carrying the tag: taxonomy's factoidTags count."},"kind":{"type":"string","description":"What is found at a place: articles only, factoids only, or both.","enum":["articles","factoids","shared"]},"r":{"type":"number","format":"double","description":"The mark's radius, in world units: 4 + 2.6·√(articles + factoids)."},"labelSize":{"type":"number","format":"double","description":"The label's font size, in pixels: 10 + √size, at most 20."},"minor":{"type":"boolean","description":"A small place (size under 3): its name shows only when zoomed in."},"pins":{"type":"array","description":"The newest articles and the factoids (by name) found here, at most 8 of each, each placed around the mark. GET /atlas/places/{tag} lists them all.","items":{"$ref":"#/components/schemas/AtlasPin"}}},"required":["articleCount","factoidCount","kind","labelSize","minor","pins","r","region","tag","x","y"]},"AtlasRegionResponse":{"type":"object","description":"A region of the map: tags that go together.","properties":{"id":{"type":"integer","format":"int32","description":"Stable while the layout is kept; 0 is the Uncharted region."},"name":{"type":"string","description":"The region's two biggest tags when laid out, joined by \" · \"; \"Uncharted\" for id 0."},"x":{"type":"number","format":"double"},"y":{"type":"number","format":"double"},"r":{"type":"number","format":"double","description":"How far the region reaches: its outermost place's mark, and a margin."},"uncharted":{"type":"boolean","description":"True for the region of tags tied to nothing (id 0)."}},"required":["id","name","r","uncharted","x","y"]},"AtlasResponse":{"type":"object","description":"The Atlas: a map of the site's tags, grouped into regions by how they're used together.","properties":{"regions":{"type":"array","description":"The regions, biggest first, the Uncharted region (id 0) last.","items":{"$ref":"#/components/schemas/AtlasRegionResponse"}},"places":{"type":"array","description":"Every tag in use, each with its region and position, in region order and then biggest first. A stored tag no longer used is left out but keeps its position for when it's back.","items":{"$ref":"#/components/schemas/AtlasPlaceResponse"}},"bounds":{"$ref":"#/components/schemas/AtlasBounds","description":"The world the map spans, with a margin and room for region names."},"laidOutAt":{"type":["string","null"],"format":"date-time","description":"When the oldest place was laid out: the last full layout. Null for an empty map."}},"required":["bounds","places","regions"]},"TaxonomySnapshot":{"type":"object","description":"Usage counts per taxonomy term. Terms starting with _ are never included.","properties":{"tags":{"type":"object","additionalProperties":{"type":"integer","format":"int64"},"description":"Published posts carrying each tag: approved, published by now, not deleted and not in a hidden category. Matches the totalCount of GET /posts?tag=."},"categories":{"type":"object","additionalProperties":{"type":"integer","format":"int64"},"description":"Posts in each category."},"aggregate":{"type":"object","additionalProperties":{"type":"integer","format":"int64"},"description":"The union of tags, factoidTags and categories, summed per term."},"factoidTags":{"type":"object","additionalProperties":{"type":"integer","format":"int64"},"description":"Factoids carrying each tag."}},"required":["aggregate","categories","tags"]},"RssAggregatedItemResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"feedTitle":{"type":"string"},"feedUrl":{"type":"string"},"siteUrl":{"type":["string","null"]},"guid":{"type":"string"},"link":{"type":"string"},"title":{"type":"string"},"summary":{"type":["string","null"]},"publishedAt":{"type":["string","null"],"format":"date-time"},"receivedAt":{"type":"string","format":"date-time"},"categories":{"type":"array","description":"The feed's own tags for the item (RSS category, Atom category term), as written; empty when it gave none or the item was stored before tags were kept","items":{"type":"string"}},"tags":{"type":"array","description":"The BCN tags the item's feed tags map to: a tag of the same name, or an alias's tag. Stoplisted and waiting feed tags map to nothing. Feed items don't count toward tag counts or the taxonomy.","items":{"type":"string"}}},"required":["categories","feedTitle","feedUrl","guid","id","link","receivedAt","tags","title"]},"RssAggregatedItemsResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RssAggregatedItemResponse"}},"page":{"type":"integer","format":"int32"},"totalPages":{"type":"integer","format":"int32"},"totalCount":{"type":"integer","format":"int64"}},"required":["items","page","totalCount","totalPages"]},"RssFeedSourceResponse":{"type":"object","properties":{"title":{"type":"string"},"feedUrl":{"type":"string"},"siteUrl":{"type":["string","null"]},"itemCount":{"type":"integer","format":"int64"},"latestItemTimestamp":{"type":["string","null"],"format":"date-time"}},"required":["feedUrl","itemCount","title"]},"RssFeedSourcesResponse":{"type":"object","properties":{"feeds":{"type":"array","items":{"$ref":"#/components/schemas/RssFeedSourceResponse"}}},"required":["feeds"]},"ContentListResponse":{"type":"object","properties":{"posts":{"type":"array","items":{"$ref":"#/components/schemas/ContentSummary"}},"page":{"type":"integer","format":"int32"},"totalPages":{"type":"integer","format":"int32"},"totalCount":{"type":"integer","format":"int64"}},"required":["page","posts","totalCount","totalPages"]},"ContentSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"slug":{"type":"string"},"excerpt":{"type":["string","null"]},"authorDisplayName":{"type":"string"},"publishedAt":{"type":["string","null"],"format":"date-time"},"sortOrder":{"type":"integer","format":"int32"},"commentCount":{"type":"integer","format":"int32"},"tags":{"type":"array","items":{"type":"string"}},"categories":{"type":"array","items":{"type":"string"}},"versionKinds":{"type":"array","description":"The kinds of version the post has, sorted, such as `audio` for a recording of it; empty when it has none or media is off. The versions themselves are in the post's details.","example":["audio"],"items":{"type":"string"}}},"required":["authorDisplayName","id","slug","title"]},"CommentNode":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"authorId":{"type":["string","null"],"format":"uuid"},"authorDisplayName":{"type":"string"},"renderedHtml":{"type":"string"},"markdownSource":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"deleted":{"type":"boolean"},"editable":{"type":"boolean"},"children":{"type":"array","items":{"$ref":"#/components/schemas/CommentNode"}}},"required":["authorDisplayName","children","createdAt","deleted","editable","id","renderedHtml","updatedAt"]},"CommentThreadResponse":{"type":"object","properties":{"postId":{"type":"string","format":"uuid"},"comments":{"type":"array","items":{"$ref":"#/components/schemas/CommentNode"}},"totalActiveCount":{"type":"integer","format":"int32"}},"required":["comments","postId","totalActiveCount"]},"LogDayResponse":{"type":"object","properties":{"provenanceUri":{"type":"string"},"day":{"type":"string"},"entries":{"type":"array","items":{"$ref":"#/components/schemas/LogEntry"}}},"required":["day","entries","provenanceUri"]},"LogEntry":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time"},"sender":{"type":"string"},"content":{"type":"string"},"direction":{"type":"string","enum":["INBOUND","OUTBOUND"]},"kind":{"type":"string","enum":["MESSAGE","JOIN","PART","QUIT","NICK","TOPIC"]}},"required":["content","direction","sender","timestamp"]},"LogSearchHit":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time"},"day":{"type":"string"},"sender":{"type":"string"},"content":{"type":"string"},"direction":{"type":"string","enum":["INBOUND","OUTBOUND"]},"kind":{"type":"string","enum":["MESSAGE","JOIN","PART","QUIT","NICK","TOPIC"]}},"required":["content","day","direction","sender","timestamp"]},"LogSearchResponse":{"type":"object","properties":{"provenanceUri":{"type":"string"},"query":{"type":["string","null"]},"sender":{"type":["string","null"]},"page":{"type":"integer","format":"int32"},"size":{"type":"integer","format":"int32"},"totalCount":{"type":"integer","format":"int64"},"totalPages":{"type":"integer","format":"int32"},"hits":{"type":"array","items":{"$ref":"#/components/schemas/LogSearchHit"}}},"required":["hits","page","provenanceUri","size","totalCount","totalPages"]},"LogProvenanceListResponse":{"type":"object","properties":{"provenances":{"type":"array","items":{"$ref":"#/components/schemas/LogProvenanceSummary"}}},"required":["provenances"]},"LogProvenanceSummary":{"type":"object","properties":{"provenanceUri":{"type":"string"},"protocol":{"type":"string"},"serviceId":{"type":["string","null"]},"replyTo":{"type":"string"},"latestTimestamp":{"type":["string","null"],"format":"date-time"},"latestSender":{"type":["string","null"]},"latestContentPreview":{"type":["string","null"]},"path":{"type":["string","null"]}},"required":["protocol","provenanceUri","replyTo"]},"LogChannelResponse":{"type":"object","properties":{"provenanceUri":{"type":"string"},"path":{"type":"string"}},"required":["path","provenanceUri"]},"KarmaLeaderboardEntryResponse":{"type":"object","properties":{"subject":{"type":"string"},"score":{"type":"integer","format":"int32"},"lastUpdated":{"type":"string","format":"date"}},"required":["lastUpdated","score","subject"]},"KarmaLeaderboardResponse":{"type":"object","properties":{"top":{"type":"array","items":{"$ref":"#/components/schemas/KarmaLeaderboardEntryResponse"}},"bottom":{"type":"array","items":{"$ref":"#/components/schemas/KarmaLeaderboardEntryResponse"}},"limit":{"type":"integer","format":"int32"}},"required":["bottom","limit","top"]},"AuthenticationFeatures":{"type":"object","properties":{"otp":{"type":"boolean"},"otpFrom":{"type":"string"},"oidc":{"oneOf":[{"$ref":"#/components/schemas/OidcFeatures"},{"type":"null"}]},"codeChannels":{"type":"array","items":{"$ref":"#/components/schemas/CodeChannelFeature"}}},"required":["otp","otpFrom"]},"CodeChannelFeature":{"type":"object","properties":{"channel":{"type":"string"},"servers":{"type":"array","items":{"type":"string"}}},"required":["channel"]},"FeaturesResponse":{"type":"object","properties":{"siteName":{"type":"string"},"version":{"$ref":"#/components/schemas/VersionInfo"},"authentication":{"$ref":"#/components/schemas/AuthenticationFeatures"},"operationGroups":{"type":"array","items":{"type":"string"}},"adapters":{"type":"array","items":{"type":"string"}},"ai":{"type":"boolean"},"anonymousSubmission":{"type":"boolean"}},"required":["adapters","ai","anonymousSubmission","authentication","operationGroups","siteName","version"]},"OidcFeatures":{"type":"object","properties":{"google":{"type":"boolean"},"github":{"type":"boolean"}},"required":["github","google"]},"VersionInfo":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":["string","null"]},"commit":{"type":["string","null"]},"branch":{"type":["string","null"]},"buildTime":{"type":["string","null"]}},"required":["name"]},"FactoidListResponse":{"type":"object","properties":{"factoids":{"type":"array","items":{"$ref":"#/components/schemas/FactoidSummaryResponse"}},"page":{"type":"integer","format":"int32"},"totalPages":{"type":"integer","format":"int32"},"totalCount":{"type":"integer","format":"int64"}},"required":["factoids","page","totalCount","totalPages"]},"FactoidSummaryResponse":{"type":"object","properties":{"selector":{"type":"string"},"locked":{"type":"boolean"},"updatedBy":{"type":["string","null"]},"updatedAt":{"type":"string","format":"date-time"},"lastAccessedAt":{"type":["string","null"],"format":"date-time"},"accessCount":{"type":"integer","format":"int64"},"createdAt":{"type":["string","null"],"format":"date-time"},"text":{"type":["string","null"]},"tags":{"type":"array","items":{"type":"string"}}},"required":["accessCount","locked","selector","tags","updatedAt"]},"CategorySummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"parentName":{"type":["string","null"]}},"required":["id","name","slug"]},"AtlasArticle":{"type":"object","description":"A published post found at a place.","properties":{"title":{"type":"string"},"slug":{"type":["string","null"],"description":"Its canonical slug path; null only for a post without one."},"publishedAt":{"type":"string","format":"date-time"},"tags":{"type":"array","description":"Every tag it carries: the places it's found at.","items":{"type":"string"}}},"required":["publishedAt","tags","title"]},"AtlasFactoid":{"type":"object","description":"A factoid found at a place.","properties":{"selector":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"tags":{"type":"array","description":"Every tag it carries: the places it's found at.","items":{"type":"string"}}},"required":["createdAt","selector","tags","updatedAt"]},"AtlasPlaceDetailResponse":{"type":"object","description":"One place, with everything found there.","properties":{"place":{"$ref":"#/components/schemas/AtlasPlaceResponse"},"regionName":{"type":"string","description":"The name of the place's region."},"articles":{"type":"array","description":"Every published post carrying the tag, newest first.","items":{"$ref":"#/components/schemas/AtlasArticle"}},"factoids":{"type":"array","description":"Every factoid carrying the tag, by selector.","items":{"$ref":"#/components/schemas/AtlasFactoid"}}},"required":["articles","factoids","place","regionName"]},"TagReviewEntry":{"type":"object","description":"A doubtful new tag in the admin review queue","properties":{"id":{"type":"string","format":"uuid"},"tag":{"type":"string","description":"The tag as it was written and created"},"firstSeen":{"type":"string","format":"date-time","description":"When it was first written"},"source":{"type":"string","description":"What first wrote it: post, factoid, or rss (a feed tag promoted or created as a tag)"},"hintKind":{"type":"string","description":"PLURAL (a trailing-s pair with hintTags[0]), MISSING_COMMA (hintTags are the existing tags it's made of) or AI (only the AI near-miss found something)","enum":["PLURAL","MISSING_COMMA","AI"]},"hintTags":{"type":"array","description":"The existing tags the hint names","items":{"type":"string"}},"aiCandidate":{"type":["string","null"],"description":"The existing tag the AI thinks this duplicates"},"aiConfidence":{"type":["number","null"],"format":"double","description":"The AI's confidence, 0 to 1"},"aiReason":{"type":["string","null"],"description":"Why the AI thinks so"},"aiModel":{"type":["string","null"],"description":"The model that answered"},"status":{"type":"string","enum":["OPEN","ALIASED","SPLIT","KEPT","DISMISSED"]},"actedBy":{"type":["string","null"],"description":"Who decided, once decided"},"actedAt":{"type":["string","null"],"format":"date-time","description":"When it was decided"}},"required":["firstSeen","hintKind","hintTags","id","source","status","tag"]},"TagReviewListResponse":{"type":"object","description":"The review queue, a page of it","properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/TagReviewEntry"}},"openCount":{"type":"integer","format":"int64","description":"Open entries in all, for a launcher badge"},"totalCount":{"type":["integer","null"],"format":"int64","description":"Entries in all for the status asked for, across every page"},"totalPages":{"type":["integer","null"],"format":"int32","description":"Pages in all for the status and size asked for; 0 when none"}},"required":["entries","openCount"]},"TagAliasEntry":{"type":"object","description":"An alias and the tag it means","properties":{"alias":{"type":"string"},"tag":{"type":"string"},"createdBy":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}},"required":["alias","createdAt","createdBy","tag"]},"TagActionEntry":{"type":"object","description":"One change to the tag vocabulary","properties":{"action":{"type":"string","enum":["ALIAS","UNALIAS","SPLIT","KEEP","DISMISS","STOP","UNSTOP","REMOVE"]},"subject":{"type":"string","description":"The tag or term acted on"},"detail":{"type":["string","null"],"description":"What it became, or other detail"},"actor":{"type":"string"},"actedAt":{"type":"string","format":"date-time"}},"required":["actedAt","action","actor","subject"]},"FeedTagListResponse":{"type":"object","description":"Feed tags, a page of them","properties":{"tags":{"type":"array","items":{"$ref":"#/components/schemas/FeedTagEntry"}},"waitingCount":{"type":"integer","format":"int64","description":"Waiting feed tags in all, for a launcher badge"},"promoteEntries":{"type":"integer","format":"int32","description":"Entries across feeds a waiting tag needs to become a tag"},"promoteFeeds":{"type":"integer","format":"int32","description":"Distinct feeds a waiting tag needs to become a tag"},"totalCount":{"type":["integer","null"],"format":"int64","description":"Feed tags in all for the status asked for, across every page"},"totalPages":{"type":["integer","null"],"format":"int32","description":"Pages in all for the status and size asked for; 0 when none"}},"required":["promoteEntries","promoteFeeds","tags","waitingCount"]},"RssRatingClassAgreement":{"type":"object","properties":{"rating":{"type":"string","enum":["RATES","MIGHT","DULL"]},"rated":{"type":"integer","format":"int64"},"guessed":{"type":"integer","format":"int64"},"agreed":{"type":"integer","format":"int64"},"recall":{"type":["number","null"],"format":"double"},"precision":{"type":["number","null"],"format":"double"}},"required":["agreed","guessed","rated","rating"]},"RssRatingConfusionCell":{"type":"object","properties":{"rated":{"type":"string","enum":["RATES","MIGHT","DULL"]},"guessed":{"type":"string","enum":["RATES","MIGHT","DULL"]},"count":{"type":"integer","format":"int64"}},"required":["count","guessed","rated"]},"RssRatingCounts":{"type":"object","properties":{"rates":{"type":"integer","format":"int64"},"might":{"type":"integer","format":"int64"},"dull":{"type":"integer","format":"int64"},"rated":{"type":"integer","format":"int64","description":"Items with any rating"},"guessed":{"type":"integer","format":"int64","description":"Items with a guess from the model"},"compared":{"type":"integer","format":"int64","description":"Items with both a rating and a guess"}},"required":["compared","dull","guessed","might","rated","rates"]},"RssRatingStatsItem":{"type":"object","properties":{"itemId":{"type":"string","format":"uuid"},"title":{"type":"string"},"feedTitle":{"type":"string"}},"required":["feedTitle","itemId","title"]},"RssRatingStatsResponse":{"type":"object","properties":{"counts":{"$ref":"#/components/schemas/RssRatingCounts"},"agreement":{"type":["number","null"],"format":"double","description":"The share of compared items whose guess matches the rating; null when none are compared"},"perClass":{"type":"array","items":{"$ref":"#/components/schemas/RssRatingClassAgreement"}},"confusion":{"type":"array","description":"All nine cells, rated by guessed, zeros included","items":{"$ref":"#/components/schemas/RssRatingConfusionCell"}},"guessedRatesRatedDull":{"type":"array","description":"Guessed RATES, rated DULL","items":{"$ref":"#/components/schemas/RssRatingStatsItem"}},"ratedRatesGuessedDull":{"type":"array","description":"Rated RATES, guessed DULL","items":{"$ref":"#/components/schemas/RssRatingStatsItem"}}},"required":["confusion","counts","guessedRatesRatedDull","perClass","ratedRatesGuessedDull"]},"RssRatingGuessResponse":{"type":"object","properties":{"itemId":{"type":"string","format":"uuid"},"title":{"type":"string"},"link":{"type":"string"},"feedTitle":{"type":"string"},"label":{"type":"string","description":"The guess: RATES, MIGHT or DULL","enum":["RATES","MIGHT","DULL"]},"confidence":{"type":"number","format":"double","description":"The model's confidence in the guess, from 0 to 1"},"reason":{"type":"string","description":"The model's reason, one short sentence"},"textSource":{"type":"string","description":"What the model judged the item on: CONTENT (the feed's full text), PAGE (the article page), SUMMARY or TITLE","enum":["CONTENT","PAGE","SUMMARY","TITLE","NONE"]},"model":{"type":["string","null"],"description":"The model that guessed; null when it wasn't recorded"},"rubric":{"type":["string","null"],"description":"The first 12 hex characters of the SHA-256 of the rubric the guess was made with; null for guesses made before the rubric was recorded"},"guessedAt":{"type":"string","format":"date-time"},"rating":{"type":["string","null"],"description":"The editor's rating; null when the item is unrated","enum":["RATES","MIGHT","DULL"]},"agrees":{"type":["boolean","null"],"description":"Whether the guess matches the editor's rating; null when the item is unrated"}},"required":["confidence","feedTitle","guessedAt","itemId","label","link","reason","textSource","title"]},"RssRatingGuessesResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/RssRatingGuessResponse"}},"page":{"type":"integer","format":"int32"},"totalPages":{"type":"integer","format":"int32"},"totalCount":{"type":"integer","format":"int64"}},"required":["items","page","totalCount","totalPages"]},"AdminRssItemResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"feedTitle":{"type":"string"},"feedUrl":{"type":"string"},"siteUrl":{"type":["string","null"]},"guid":{"type":"string"},"link":{"type":"string"},"title":{"type":"string"},"summary":{"type":["string","null"]},"publishedAt":{"type":["string","null"],"format":"date-time"},"receivedAt":{"type":"string","format":"date-time"},"categories":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"rating":{"type":["string","null"],"description":"The item's rating; null when it has none","enum":["RATES","MIGHT","DULL"]},"ratedBy":{"type":["string","null"],"description":"The admin who rated it; null when it has no rating"},"ratedAt":{"type":["string","null"],"format":"date-time","description":"When it was rated; null when it has no rating"}},"required":["categories","feedTitle","feedUrl","guid","id","link","receivedAt","tags","title"]},"AdminRssItemsResponse":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/AdminRssItemResponse"}},"page":{"type":"integer","format":"int32"},"totalPages":{"type":"integer","format":"int32"},"totalCount":{"type":"integer","format":"int64"}},"required":["items","page","totalCount","totalPages"]},"ReportListResponse":{"type":"object","properties":{"page":{"type":"integer","format":"int32"},"size":{"type":"integer","format":"int32"},"totalCount":{"type":"integer","format":"int64"},"totalPages":{"type":"integer","format":"int32"},"openCount":{"type":"integer","format":"int64"},"reports":{"type":"array","items":{"$ref":"#/components/schemas/ReportSummary"}}},"required":["openCount","page","reports","size","totalCount","totalPages"]},"ReportSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"provenanceUri":{"type":"string"},"protocol":{"type":"string"},"serviceId":{"type":["string","null"]},"channel":{"type":"string"},"sender":{"type":"string"},"userId":{"type":["string","null"],"format":"uuid"},"score":{"type":"number","format":"double"},"signals":{"type":"object","additionalProperties":{"type":"integer","format":"int32"}},"verdict":{"oneOf":[{"$ref":"#/components/schemas/VerdictView"},{"type":"null"}]},"status":{"type":"string","enum":["OPEN","DISMISSED","ACTIONED"]},"windowStart":{"type":"string","format":"date-time"},"windowEnd":{"type":"string","format":"date-time"},"createdAt":{"type":"string","format":"date-time"},"actedBy":{"type":["string","null"]},"actedAt":{"type":["string","null"],"format":"date-time"}},"required":["channel","createdAt","id","protocol","provenanceUri","score","sender","signals","status","windowEnd","windowStart"]},"VerdictView":{"type":"object","properties":{"abusive":{"type":"boolean"},"reason":{"type":["string","null"]},"model":{"type":["string","null"]}},"required":["abusive"]},"ModerationLine":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"timestamp":{"type":"string","format":"date-time"},"day":{"type":"string"},"sender":{"type":"string"},"content":{"type":"string"},"direction":{"type":"string","enum":["INBOUND","OUTBOUND"]},"hidden":{"type":"boolean"},"flagged":{"type":"boolean"},"cited":{"type":"boolean"},"signals":{"type":["array","null"],"items":{"type":"string"}},"weight":{"type":["number","null"],"format":"double"},"strong":{"type":["boolean","null"]}},"required":["cited","content","day","direction","flagged","hidden","id","sender","timestamp"]},"ReportDetail":{"type":"object","properties":{"report":{"$ref":"#/components/schemas/ReportSummary"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ModerationLine"}},"purgedLineIds":{"type":"array","items":{"type":"string","format":"uuid"}},"actions":{"type":"array","items":{"$ref":"#/components/schemas/ModerationActionView"}}},"required":["actions","lines","purgedLineIds","report"]},"ModerationLogDay":{"type":"object","properties":{"provenanceUri":{"type":"string"},"day":{"type":"string"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/ModerationLine"}}},"required":["day","lines","provenanceUri"]},"FactoidGraphReport":{"type":"object","properties":{"parentsNotLinked":{"type":"array","items":{"$ref":"#/components/schemas/UnlinkedMentions"}},"mentionsNotLinked":{"type":"array","items":{"$ref":"#/components/schemas/UnlinkedMentions"}},"seeAlsoToNothing":{"type":"array","items":{"$ref":"#/components/schemas/SeeAlsoToNothing"}},"linesThatDontFit":{"type":"array","items":{"$ref":"#/components/schemas/LineThatDoesntFit"}}},"required":["linesThatDontFit","mentionsNotLinked","parentsNotLinked","seeAlsoToNothing"]},"LineThatDoesntFit":{"type":"object","properties":{"selector":{"type":"string"},"length":{"type":"integer","format":"int32"},"saidLength":{"type":"integer","format":"int32"},"dropped":{"type":"array","items":{"type":"string"}},"cutOff":{"type":"boolean"}},"required":["cutOff","dropped","length","saidLength","selector"]},"SeeAlsoToNothing":{"type":"object","properties":{"selector":{"type":"string"},"missing":{"type":"array","items":{"type":"string"}}},"required":["missing","selector"]},"UnlinkedMentions":{"type":"object","properties":{"selector":{"type":"string"},"mentions":{"type":"array","items":{"type":"string"}}},"required":["mentions","selector"]},"WebConsoleResult":{"type":"object","description":"The data of a `result` event on GET /admin/console/stream.","properties":{"correlationId":{"type":["string","null"],"description":"The command it answers; null for output nobody asked for here."},"status":{"type":"string","enum":["success","error","unhandled"]},"text":{"type":["string","null"],"description":"Plain text, which may span lines; absent when unhandled."}},"required":["status"]},"DeleteAccountRequest":{"type":"object","properties":{"username":{"type":["string","null"]}}},"ContentOperationConfirmation":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"message":{"type":"string"}},"required":["id","message"]}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}}}