{
  "openapi": "3.0.3",
  "info": {
    "title": "MythOS v3 Internal API",
    "description": "Agent-facing API for MythOS memo library operations, community browsing, embedding, and chat.",
    "version": "1.1.0"
  },
  "servers": [
    {
      "url": "https://mythos.one",
      "description": "MythOS v3"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-mythos-key",
        "description": "Internal API key for agent authentication"
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "error": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "error"
        ]
      },
      "NewsletterSubscriber": {
        "type": "object",
        "properties": {
          "subscriptionId": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "firstName": {
            "type": "string",
            "nullable": true
          },
          "lastName": {
            "type": "string",
            "nullable": true
          },
          "company": {
            "type": "string",
            "nullable": true
          },
          "phone": {
            "type": "string",
            "nullable": true
          },
          "location": {
            "type": "string",
            "nullable": true
          },
          "customFields": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "pending",
              "unsubscribed"
            ]
          },
          "frequency": {
            "type": "string",
            "enum": [
              "instant",
              "daily",
              "weekly",
              "monthly",
              "none"
            ]
          },
          "digestMode": {
            "type": "string",
            "enum": [
              "bundled",
              "separate"
            ]
          },
          "scope": {
            "type": "string",
            "enum": [
              "creator",
              "tag"
            ]
          },
          "topics": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "slug": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              }
            }
          },
          "subscribedAt": {
            "type": "integer"
          },
          "unsubscribedAt": {
            "type": "integer",
            "nullable": true
          },
          "lastDigestSentAt": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "MemoSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "snippet": {
            "type": "string"
          },
          "visibility": {
            "type": "string",
            "enum": [
              "public",
              "link",
              "private",
              "hidden"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "collaboratorTags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "author": {
            "type": "object",
            "properties": {
              "username": {
                "type": "string"
              },
              "displayName": {
                "type": "string"
              }
            }
          },
          "createdAt": {
            "type": "number",
            "nullable": true
          },
          "createdAtIso": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "updatedAt": {
            "type": "number",
            "nullable": true
          },
          "updatedAtIso": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "content": {
            "type": "string"
          },
          "collaboratorContent": {
            "type": "string"
          },
          "seoTitle": {
            "type": "string",
            "nullable": true,
            "maxLength": 70
          },
          "seoDescription": {
            "type": "string",
            "nullable": true,
            "maxLength": 160
          }
        }
      },
      "MemoInput": {
        "type": "object",
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Memo title"
          },
          "content": {
            "type": "string",
            "description": "Memo content in markdown. Hashtag chips are serialized as [#name](/tag/name) — always write that explicit form. A bare #word is auto-linked into a chip unless sourceMarkupVersion: 2 is passed."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tags for the memo. If omitted, tags are extracted from content."
          },
          "visibility": {
            "type": "string",
            "enum": [
              "public",
              "link",
              "private"
            ],
            "default": "link",
            "description": "Memo visibility level"
          },
          "collaboratorContent": {
            "type": "string",
            "description": "Notes/collaborator section content displayed separately below the memo body"
          },
          "seoTitle": {
            "type": "string",
            "nullable": true,
            "maxLength": 70,
            "description": "Optional <title>/og:title override for public memo pages"
          },
          "seoDescription": {
            "type": "string",
            "nullable": true,
            "maxLength": 160,
            "description": "Optional <meta name=\"description\">/og:description override"
          },
          "sourceMarkupVersion": {
            "type": "integer",
            "description": "Optional. Markup version of the content being sent. Omit (or 1) for legacy behavior — bare #word is auto-linked into a tag. Set to 2 when the content is already v2 markdown where bare #word is literal text and tags are written as [#name](/tag/name)."
          }
        }
      },
      "Memo": {
        "type": "object",
        "properties": {
          "_id": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "visibility": {
            "type": "string"
          },
          "author": {
            "type": "object",
            "properties": {
              "username": {
                "type": "string"
              },
              "uid": {
                "type": "string"
              },
              "displayName": {
                "type": "string"
              }
            }
          },
          "header": {
            "type": "object",
            "properties": {
              "createdAt": {
                "type": "number"
              },
              "updatedAt": {
                "type": "number"
              },
              "title": {
                "type": "string"
              }
            }
          },
          "content": {
            "type": "string"
          },
          "collaboratorContent": {
            "type": "string"
          },
          "contentVersion": {
            "type": "integer"
          },
          "seoTitle": {
            "type": "string",
            "nullable": true,
            "maxLength": 70
          },
          "seoDescription": {
            "type": "string",
            "nullable": true,
            "maxLength": 160
          }
        }
      },
      "CommunitySummary": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "description": "Community URL identifier"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "memberCount": {
            "type": "integer"
          },
          "postCount": {
            "type": "integer"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CommunityPost": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "snippet": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "author": {
            "type": "object",
            "properties": {
              "displayName": {
                "type": "string"
              },
              "username": {
                "type": "string"
              }
            }
          },
          "score": {
            "type": "integer"
          },
          "submittedAt": {
            "type": "number"
          },
          "postSlug": {
            "type": "string"
          }
        }
      }
    }
  },
  "paths": {
    "/api/internal/memos": {
      "get": {
        "summary": "List memos",
        "description": "Returns memos from the library, filtered by query parameters. Results are sorted by last updated descending.",
        "operationId": "listMemos",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Search query to filter memos by title",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tags",
            "in": "query",
            "description": "Comma-separated tag names to filter by. Two or more tags require the tagMode parameter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tagMode",
            "in": "query",
            "description": "How multiple tags combine: 'all' returns memos carrying every listed tag (intersection); 'any' returns memos carrying at least one (union). Required whenever two or more tags are passed; ignored for a single tag. A multi-tag request without it returns 400.",
            "schema": {
              "type": "string",
              "enum": [
                "any",
                "all"
              ]
            }
          },
          {
            "name": "visibility",
            "in": "query",
            "description": "Comma-separated visibility filter (public, link)",
            "schema": {
              "type": "string",
              "default": "public,link"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of memos to return (1-200, default 50)",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of memos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "memos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MemoSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a memo",
        "description": "Creates a new memo in the library. If tags are not provided, they are automatically extracted from the content.",
        "operationId": "createMemo",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemoInput"
              },
              "example": {
                "title": "My New Memo",
                "content": "This is the memo content with [#tags](/tag/tags) inline.",
                "visibility": "link"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Memo created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "memo": {
                      "$ref": "#/components/schemas/Memo"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input (missing title)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a memo",
        "description": "Updates content and optionally other fields of an existing memo. Increments contentVersion. Optional SEO fields (seoTitle, seoDescription) can be included alongside content. For SEO-only updates without touching content, use PATCH /api/internal/memos/seo instead.",
        "operationId": "updateMemo",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "description": "The memo ID to update",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "description": "Updated memo content (required). Hashtag chips are serialized as [#name](/tag/name) — always write that explicit form. A bare #word is auto-linked into a chip unless sourceMarkupVersion: 2 is passed."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Updated tags. If omitted, tags are extracted from content."
                  },
                  "collaboratorContent": {
                    "type": "string",
                    "description": "Updated notes/collaborator section. Bumps collabContentVersion when provided."
                  },
                  "seoTitle": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 70,
                    "description": "Optional SEO title. Pass null or empty string to clear."
                  },
                  "seoDescription": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 160,
                    "description": "Optional SEO description. Pass null or empty string to clear."
                  },
                  "sourceMarkupVersion": {
                    "type": "integer",
                    "description": "Optional. Markup version of the content being sent. Omit (or 1) for legacy behavior — bare #word is auto-linked into a tag. Set to 2 when the content is already v2 markdown where bare #word is literal text and tags are written as [#name](/tag/name)."
                  }
                }
              },
              "example": {
                "content": "Updated content with new [#tags](/tag/tags)",
                "tags": [
                  "tags",
                  "updated"
                ],
                "seoTitle": "A better title for search engines"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Memo updated successfully. contentVersion reflects the new bumped value.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "contentVersion": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing id, invalid field value, or no updatable field provided",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Memo not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/memos/seo": {
      "patch": {
        "summary": "Update memo SEO metadata",
        "description": "Updates only the seoTitle and/or seoDescription of a memo. Does not touch content, tags, or contentVersion. Pass null or empty string for a field to clear it.",
        "operationId": "updateMemoSeo",
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "description": "The memo ID to update",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "At least one of seoTitle or seoDescription is required.",
                "properties": {
                  "seoTitle": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 70,
                    "description": "SEO title override. Pass null or empty string to clear."
                  },
                  "seoDescription": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 160,
                    "description": "SEO description override. Pass null or empty string to clear."
                  }
                }
              },
              "example": {
                "seoTitle": "A better title for search engines",
                "seoDescription": "Up to 160 characters describing this memo for previews."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SEO metadata updated successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "seoTitle": {
                      "type": "string",
                      "nullable": true
                    },
                    "seoDescription": {
                      "type": "string",
                      "nullable": true
                    },
                    "updatedAt": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing id, invalid JSON body, no SEO field provided, or invalid field value",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Memo not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/memos/batch": {
      "post": {
        "summary": "Batch create memos",
        "description": "Creates multiple memos in a single operation. Maximum 50 memos per request.",
        "operationId": "batchCreateMemos",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "memos"
                ],
                "properties": {
                  "memos": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/MemoInput"
                    },
                    "maxItems": 50
                  },
                  "sourceMarkupVersion": {
                    "type": "integer",
                    "description": "Optional batch-level markup version applied to every memo that does not set its own. See MemoInput.sourceMarkupVersion."
                  }
                }
              },
              "example": {
                "memos": [
                  {
                    "title": "First Memo",
                    "content": "Content one"
                  },
                  {
                    "title": "Second Memo",
                    "content": "Content two",
                    "tags": [
                      "batch"
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Memos created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "created": {
                      "type": "integer"
                    },
                    "memos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Memo"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/embed": {
      "post": {
        "summary": "Trigger memo embedding",
        "description": "Generates vector embeddings for specified memos or all memos. Used for semantic search.",
        "operationId": "embedMemos",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "object",
                    "required": [
                      "memoIds"
                    ],
                    "properties": {
                      "memoIds": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        },
                        "description": "Array of memo IDs to embed"
                      }
                    }
                  },
                  {
                    "type": "object",
                    "required": [
                      "all"
                    ],
                    "properties": {
                      "all": {
                        "type": "boolean",
                        "enum": [
                          true
                        ],
                        "description": "Set to true to embed all memos"
                      }
                    }
                  }
                ]
              },
              "examples": {
                "byIds": {
                  "summary": "Embed specific memos",
                  "value": {
                    "memoIds": [
                      "brianswichkow-abc123",
                      "brianswichkow-def456"
                    ]
                  }
                },
                "all": {
                  "summary": "Embed all memos",
                  "value": {
                    "all": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Embedding results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "embedded": {
                      "type": "integer",
                      "description": "Number of memos successfully embedded"
                    },
                    "failed": {
                      "type": "integer",
                      "description": "Number of memos that failed to embed"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/embed/batch": {
      "post": {
        "summary": "Batch embed memos",
        "description": "Generates vector embeddings for a batch of memos. Maximum 100 memo IDs per request. Returns detailed error information for any failures.",
        "operationId": "batchEmbedMemos",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "memoIds"
                ],
                "properties": {
                  "memoIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 100,
                    "description": "Array of memo IDs to embed"
                  }
                }
              },
              "example": {
                "memoIds": [
                  "brianswichkow-abc123",
                  "brianswichkow-def456"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch embedding results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "embedded": {
                      "type": "integer"
                    },
                    "failed": {
                      "type": "integer"
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "memoId": {
                            "type": "string"
                          },
                          "error": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/upload": {
      "post": {
        "summary": "Upload an image and get a URL you can attach",
        "description": "Stores the bytes and records a confirmed upload owned by you, then returns publicUrl. That record is what makes the URL attachable: every field that stores an image — an event's flyerUrl, a community's avatarUrl, a collection's coverImageUrl — resolves the URL back to a confirmed upload owned by the caller and rejects anything else, so a URL from elsewhere on the domain will not pass. Upload first, then send publicUrl on the write that carries the field. Send raw base64 with no data URI prefix; the decoded bytes are checked against the declared format's own header and terminator, because a payload cut short in transit still decodes as valid base64 and would otherwise store as a corrupt image. Payloads above roughly 24KB are the ones that get truncated through an MCP tool call.",
        "operationId": "uploadImage",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "filename",
                  "data"
                ],
                "properties": {
                  "filename": {
                    "type": "string",
                    "description": "Filename with extension. The extension selects the content type and must match the bytes: .png, .jpg, .jpeg, .gif or .webp"
                  },
                  "data": {
                    "type": "string",
                    "description": "Base64-encoded image bytes, no data URI prefix. Decoded size is capped at 10 MB"
                  }
                }
              },
              "example": {
                "filename": "flyer.png",
                "data": "iVBORw0KGgoAAAANSUhEUgAA..."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stored. publicUrl is attachable to any confirmed-upload image field",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "publicUrl": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing filename or data, an unsupported extension, or bytes that are not a complete image of the declared format. The body carries a code: MALFORMED_BASE64, FORMAT_MISMATCH or TRUNCATED_IMAGE",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Decoded image exceeds the 10 MB limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/chat": {
      "post": {
        "summary": "Chat with a library",
        "description": "Send a message to chat with a user's memo library. Uses RAG to find relevant memos and streams an AI response. Supports conversation threading.",
        "operationId": "chatWithLibrary",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "libraryOwnerUsername",
                  "message"
                ],
                "properties": {
                  "libraryOwnerUsername": {
                    "type": "string",
                    "description": "Username of the library owner to chat with"
                  },
                  "message": {
                    "type": "string",
                    "description": "The user's message"
                  },
                  "threadId": {
                    "type": "string",
                    "description": "Optional thread ID for continuing a conversation"
                  }
                }
              },
              "example": {
                "libraryOwnerUsername": "brianswichkow",
                "message": "What are your thoughts on AI?",
                "threadId": "abc-123"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SSE stream of AI response",
            "headers": {
              "X-Thread-Id": {
                "description": "The thread ID for this conversation",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Missing required fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Library owner not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/openapi": {
      "get": {
        "summary": "Get OpenAPI specification",
        "description": "Returns this OpenAPI JSON specification document.",
        "operationId": "getOpenApiSpec",
        "responses": {
          "200": {
            "description": "OpenAPI specification",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/comment-threads": {
      "get": {
        "summary": "Read an owned comment thread",
        "description": "Returns the full comment thread only for a caller-owned memo or a caller-authored community post. Scope is verified before comments are queried; a missing target and a target outside the caller's scope both return 404. This route has no username or library parameter and does not expose platform-wide comment reads.",
        "operationId": "readCommentThread",
        "parameters": [
          {
            "name": "targetType",
            "in": "query",
            "required": true,
            "description": "`memo` or `community_post`",
            "schema": {
              "type": "string",
              "enum": [
                "memo",
                "community_post"
              ]
            }
          },
          {
            "name": "memoId",
            "in": "query",
            "description": "Required for `targetType=memo`. Accepts a short id, compound dash id, or canonical slash path. The memo must be authored by the API-key owner.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "memoScope",
            "in": "query",
            "description": "For memo targets, choose all comments, stream comments, or composition comments.",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "stream",
                "composition"
              ],
              "default": "all"
            }
          },
          {
            "name": "communitySlug",
            "in": "query",
            "description": "Required for `targetType=community_post`. The community slug containing the post.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "query",
            "description": "Required for `targetType=community_post`. Accepts the community post ObjectId or postSlug. The post must have been authored by the API-key owner.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Thread sort order.",
            "schema": {
              "type": "string",
              "enum": [
                "hot",
                "newest",
                "oldest"
              ],
              "default": "hot"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The scoped comment thread"
          },
          "400": {
            "description": "Missing or invalid target parameters"
          },
          "403": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Comment thread not found. Also returned when the target exists but is outside the caller's scope."
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities": {
      "get": {
        "summary": "List communities you can see",
        "description": "Public communities plus the private and secret ones you are a member of, sorted by member count. Each result carries `joined` and its resolved `visibility`. Each community has its own llms.txt at /we/{slug}/llm.txt.",
        "operationId": "listCommunities",
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "description": "Filter by community slug (exact match)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of communities to return (1-100, default 50)",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "joined",
            "in": "query",
            "description": "Only communities you are a member of",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of communities",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "communities": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CommunitySummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a community",
        "description": "Admin-only agent route for creating a MythOS community. Browser creates remain on /api/communities and require Scholar tier.",
        "operationId": "createCommunity",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "slug",
                  "description"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "rules": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "public",
                      "private",
                      "secret"
                    ],
                    "default": "public"
                  },
                  "nsfw": {
                    "type": "boolean"
                  },
                  "guidelines": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "stewards": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Community created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "community": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid create payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Admin access required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Slug already exists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}": {
      "get": {
        "summary": "Read one community",
        "description": "Name, description, tags, counts, the About page and your own role. A community you may not see answers 404 exactly as one that does not exist. A PRIVATE community you are not a member of answers 403 with a `gate` carrying only its name, description and the stewards' note -- counts, rules and tags stay withheld.",
        "operationId": "readCommunity",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The community",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "community": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Private community -- the request-access gate",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": false
                    },
                    "error": {
                      "type": "string"
                    },
                    "gate": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a community",
        "description": "Admin-only agent route for deleting a MythOS community. Requires the current updatedAt value as expectedUpdatedAt.",
        "operationId": "deleteCommunity",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expectedUpdatedAt",
            "in": "query",
            "description": "The updatedAt value returned by reading the community immediately before delete",
            "required": true,
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Community deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid expectedUpdatedAt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Admin access required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Community not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "expectedUpdatedAt is stale",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/discover": {
      "get": {
        "summary": "Discover public communities",
        "description": "Public communities you have NOT joined, ranked by member count. Use `GET /api/internal/communities?joined=1` for the ones you are already in.",
        "operationId": "discoverCommunities",
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "description": "popular (default) | newest | random",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tags",
            "in": "query",
            "description": "Comma-separated tags to filter by",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nsfw",
            "in": "query",
            "description": "'true' for nsfw only, 'false' to exclude",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Max results (default 20, max 50)",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Communities to discover",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "communities": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/feed": {
      "get": {
        "summary": "Cross-community feed",
        "description": "Approved posts across communities. `scope=mine` is the communities you are a member of; `scope=all` (default) is those plus every public one. Both read your membership rows, never a stored slug list, so a community you left stops appearing immediately.",
        "operationId": "communityFeed",
        "parameters": [
          {
            "name": "scope",
            "in": "query",
            "description": "mine | all (default)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "new (default) | hot",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Filter on post title or tags",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "skip",
            "in": "query",
            "description": "Posts to skip",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Max results (default 20, max 50)",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Feed items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/join": {
      "post": {
        "summary": "Join a community",
        "description": "Join a PUBLIC community. No `expectedUpdatedAt` -- nothing prior is replaced, and the unique `(communityId, uid)` index makes a duplicate answer 409 rather than double-counting. A private community answers 403 and must be asked through its access gate; a secret one answers 404.",
        "operationId": "joinCommunity",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Joined",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Private community -- request access instead",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already a member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/leave": {
      "post": {
        "summary": "Leave a community",
        "description": "Leave a community you are a member of. No `expectedUpdatedAt`. The creator cannot leave their own community.",
        "operationId": "leaveCommunity",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Left",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Not a member of this community",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The creator cannot leave",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/my-membership": {
      "get": {
        "summary": "Read your own membership",
        "description": "Your role, when you joined, how your name and photo render here, the bio on your member card, who it is shown to, and how you appear in your other communities. Requires membership -- visibility alone does not give you a card.",
        "operationId": "readMyCommunityMembership",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your membership",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "membership": {
                      "type": "object"
                    },
                    "globalBio": {
                      "type": "string"
                    },
                    "directory": {
                      "type": "object"
                    },
                    "otherCommunities": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "identity": {
                      "type": "object",
                      "description": "The community facts this member's presentation is resolved from: its visibility, whether stewards hide every name here, and the resolved name and photo. Steward hiding is applied at read time and never written onto the membership."
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not a member of this community",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update your own membership",
        "description": "How you present yourself in THIS community and no other. No `expectedUpdatedAt`, deliberately: the row carries no field that moves on this write, so there is no honest value to derive a token from, and only you may write your own row. `snippetVisibility: hidden` removes you from the directory and no steward can override it. `nameForm` and `photo` are the member's own choice; a community whose stewards hide names overrides both at render time without changing what is stored here.",
        "operationId": "updateMyCommunityMembership",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "snippetSource": {
                    "type": "string",
                    "enum": [
                      "global",
                      "local",
                      "none"
                    ],
                    "description": "Which bio the card shows: the MythOS profile bio, one written for this community, or none. `none` does not fall back to the global bio."
                  },
                  "snippet": {
                    "type": "string",
                    "description": "Plain text -- rendered as a text node, never as markdown"
                  },
                  "snippetVisibility": {
                    "type": "string",
                    "enum": [
                      "all",
                      "members",
                      "hidden"
                    ]
                  },
                  "snippetTags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "snippetLink": {
                    "type": "string"
                  },
                  "linkSource": {
                    "type": "string",
                    "enum": [
                      "creator",
                      "profile",
                      "custom",
                      "none"
                    ],
                    "description": "Which page the card links to. `custom` uses `snippetLink`; absent reads as `custom` when a link is stored and `none` when it is not."
                  },
                  "nameForm": {
                    "type": "string",
                    "enum": [
                      "full",
                      "initial",
                      "first",
                      "anon"
                    ],
                    "description": "The form of your name here. Absent means the community default -- full in a public community, first name plus last initial in a private or secret one."
                  },
                  "photo": {
                    "type": "boolean",
                    "description": "Whether your profile photo shows here. Absent reads as shown. Forced off wherever the name renders anonymously."
                  },
                  "visibilityIntroSeen": {
                    "type": "boolean",
                    "description": "Send `true` to record that you have seen the join-time visibility explainer for this community. The server stamps the time and the steward name-hiding policy in force; a client-supplied timestamp is never read."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "membership": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid snippet field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a member of this community",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/members/directory": {
      "get": {
        "summary": "List community members",
        "description": "The members directory. Every per-viewer decision is made on the SERVER and the payload built from it: a member whose snippet visibility is `hidden` is absent entirely, one set to `members` returns with no snippet to a non-member, and a directory whose stewards closed the tab answers `access` other than `open` with no member data at all. `manage=1` is the steward roster instead -- it keeps unlisted members, since you cannot remove someone you cannot see, and carries no snippets. `meta=1` is the three-number settings summary.",
        "operationId": "listCommunityMembers",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "description": "active | alpha | new | posts",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter",
            "in": "query",
            "description": "all | stewards | trusted | new",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Filter by name or handle. Ignored when the stewards turned search off.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "Members to skip (page size 24)",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "manage",
            "in": "query",
            "description": "Steward roster rather than the public directory",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          },
          {
            "name": "meta",
            "in": "query",
            "description": "Steward settings summary only",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "access": {
                      "type": "string"
                    },
                    "members": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    },
                    "counts": {
                      "type": "object",
                      "nullable": true
                    },
                    "total": {
                      "type": "integer",
                      "nullable": true
                    },
                    "hasMore": {
                      "type": "boolean"
                    },
                    "truncated": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Steward-only variant requested by a non-steward",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/members/growth": {
      "get": {
        "summary": "Member growth",
        "description": "Members joined over time. Steward-only: the member count may be public, but a dated curve is a different disclosure. Counts memberships that SURVIVE -- leaving deletes the row, so there is no departure ledger and the curve can only rise.",
        "operationId": "communityMemberGrowth",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "range",
            "in": "query",
            "description": "week | month (default) | year | all",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Growth series",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "growth": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Moderation role required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/settings": {
      "patch": {
        "summary": "Update community settings",
        "description": "Identity, rules, tags, visibility, the members-directory block, the calendar block and the About page. Creator, moderator or steward.\n\nAddressed as `/settings` rather than as a PATCH on `/{slug}` deliberately: the agent tree's `/{slug}` is a READ, and one address carrying both a read for anyone who may see the community and a write that rewrites who may post there conflates two very different authorities.\n\nREQUIRES `expectedUpdatedAt`. A stale write here does not lose a sentence, it reinstates a visibility or a posting policy somebody has since changed. The token rides the findOneAndUpdate FILTER, so there is no window between checking and writing, and a 409 carries `currentUpdatedAt` while a genuinely missing community answers 404 -- an agent told the wrong one either retries forever or gives up on a write it could have made.\n\n`memberDirectory` and `calendar` are each a COMPLETE block: a partial one is REJECTED WHOLE rather than merged, so a half-rendered form cannot reset the rest. Changing visibility away from `private` closes pending access requests, and that cleanup runs only AFTER the CAS write succeeds.",
        "operationId": "updateCommunitySettings",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "description": {
                    "type": "string"
                  },
                  "rules": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    },
                    "description": "The complete ordered rule list"
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "public",
                      "private",
                      "secret"
                    ]
                  },
                  "nsfw": {
                    "type": "boolean"
                  },
                  "guidelines": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "accessWelcomeMessage": {
                    "type": "string",
                    "description": "Shown on a private community's gate; max 400 characters"
                  },
                  "accessReviewCadence": {
                    "type": "string"
                  },
                  "collectionsRequired": {
                    "type": "boolean"
                  },
                  "avatarUrl": {
                    "type": "string",
                    "description": "Must resolve to an upload the caller confirmed; empty string removes it"
                  },
                  "appearInFeeds": {
                    "type": "boolean"
                  },
                  "appearInRecommendations": {
                    "type": "boolean"
                  },
                  "aboutPage": {
                    "type": "string",
                    "description": "Markdown; may carry memo mention chips, gated per target"
                  },
                  "memberDirectory": {
                    "type": "object",
                    "description": "The COMPLETE members-directory block"
                  },
                  "calendar": {
                    "type": "object",
                    "description": "The COMPLETE calendar block"
                  },
                  "confirmHiddenMentions": {
                    "type": "boolean",
                    "description": "Re-send with true only after a hiddenMentions refusal"
                  },
                  "expectedUpdatedAt": {
                    "type": "number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "community": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing lock token, an invalid value, a rejected settings block, or a hidden-mention refusal carrying hiddenMentions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Moderation role required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Community not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Stale lock token -- body carries currentUpdatedAt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/steward-notifications": {
      "get": {
        "summary": "Read your steward alert preferences",
        "description": "The caller's OWN preferences for this community. Moderation roles only. Nothing reads these yet -- every row is labelled \"coming soon\" on the settings screen -- so this reports a stored intention rather than an alert that fires.",
        "operationId": "readCommunityStewardNotifications",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Preferences",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "settings": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Moderation role required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Write your steward alert preferences",
        "description": "No `expectedUpdatedAt` -- only this steward may write this row, so the two writers a token would arbitrate between are the same person, and the row carries no field that moves on the write to derive one from. The block is validated WHOLE: a partial payload is rejected rather than merged. Read the current block first and amend it.",
        "operationId": "updateCommunityStewardNotifications",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "stewardNotifications"
                ],
                "properties": {
                  "stewardNotifications": {
                    "type": "object",
                    "description": "The complete preferences block"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "settings": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The preferences block was rejected whole",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Moderation role required, or not a member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/invite-suggestions": {
      "get": {
        "summary": "Invite suggestions",
        "description": "People you could invite -- collaborators, active subscribers and accepted contacts, the same three sources the username invite gate enforces. Existing members and anyone holding a pending invite are excluded. Requires creator, moderator or steward. Never returns an invite token.",
        "operationId": "communityInviteSuggestions",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Filter by handle, name or email",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Suggestions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "suggestions": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/collections": {
      "get": {
        "summary": "List collections",
        "description": "A community's collections in steward order, with derived post counts. `viewerTrusted` reports whether THIS caller's submissions publish or queue. A pending count is a moderation signal and reads 0 for a non-steward.",
        "operationId": "listCommunityCollections",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Collections",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "collectionsRequired": {
                      "type": "boolean"
                    },
                    "viewerTrusted": {
                      "type": "boolean"
                    },
                    "collections": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a collection",
        "description": "Stewards only. No `expectedUpdatedAt` -- nothing prior is replaced, and the unique (communityId, slug) index answers a tag collision with 409 rather than a read-then-write check two stewards can interleave through.",
        "operationId": "createCommunityCollection",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string",
                    "description": "Tag slug; derived from the name when omitted"
                  },
                  "description": {
                    "type": "string"
                  },
                  "coverImageUrl": {
                    "type": "string",
                    "description": "Must resolve to an upload the caller confirmed"
                  },
                  "submitPolicy": {
                    "type": "string",
                    "description": "Who may file posts into it"
                  },
                  "reviewPolicy": {
                    "type": "string",
                    "description": "Whether a filed post publishes or queues"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "collection": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input, or an unverifiable cover image",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A collection already uses that tag",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Reorder collections",
        "description": "Writes the COMPLETE ordered list in one request, so two stewards dragging at once cannot interleave into an order neither chose. A partial list is a 400. No `expectedUpdatedAt` for the same reason -- the write is total rather than a delta.",
        "operationId": "reorderCommunityCollections",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "order"
                ],
                "properties": {
                  "order": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Every collection id in this community, exactly once"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reordered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "order must list every collection exactly once",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/collections/{collectionId}": {
      "get": {
        "summary": "Read a collection",
        "description": "By id or by tag slug. Carries `updatedAt`, the lock token the write endpoints require.",
        "operationId": "readCommunityCollection",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "collectionId",
            "in": "path",
            "description": "Collection id or tag slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The collection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "collection": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Edit a collection",
        "description": "Stewards only. REQUIRES `expectedUpdatedAt` -- this replaces stored content, so two stewards editing the same collection is exactly the lost update a hard lock exists for. The token rides the update FILTER, never a pre-check.",
        "operationId": "updateCommunityCollection",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "collectionId",
            "in": "path",
            "description": "Collection id or tag slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string",
                    "description": "Tag slug; derived from the name when omitted"
                  },
                  "description": {
                    "type": "string"
                  },
                  "coverImageUrl": {
                    "type": "string",
                    "description": "Must resolve to an upload the caller confirmed"
                  },
                  "submitPolicy": {
                    "type": "string",
                    "description": "Who may file posts into it"
                  },
                  "reviewPolicy": {
                    "type": "string",
                    "description": "Whether a filed post publishes or queues"
                  },
                  "gallery": {
                    "type": "object"
                  },
                  "expectedUpdatedAt": {
                    "type": "number"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "collection": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing lock token, invalid input, or an unverifiable cover image",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Stale lock token -- body carries currentUpdatedAt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a collection",
        "description": "Its posts are UNFILED, never deleted, and anything still queued for it is approved on the way out rather than stranded in a queue nothing renders. REQUIRES `expectedUpdatedAt` as a query param; the collection delete runs FIRST and the post rewrites follow only on success, so a stale token leaves no partial state across documents.",
        "operationId": "deleteCommunityCollection",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "collectionId",
            "in": "path",
            "description": "Collection id or tag slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expectedUpdatedAt",
            "in": "query",
            "description": "The collection's current updatedAt",
            "required": true,
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing lock token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Stale lock token -- body carries currentUpdatedAt",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/collections/{collectionId}/posts": {
      "get": {
        "summary": "List posts that could be filed into a collection",
        "description": "Stewards only. The community's APPROVED posts as filing candidates, newest first -- what COULD go in, which is the opposite question from `GET /posts?collection=`, which answers what already is. Each candidate carries the collection it currently sits in, so gathering a post that already has a label is a visible move rather than a surprise. `q` matches title, author display name and author username.",
        "operationId": "listCollectionFilingCandidates",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "collectionId",
            "in": "path",
            "description": "Collection id or tag slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Match on post title or author",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "1-50, default 30",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "skip",
            "in": "query",
            "description": "Offset for paging",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Candidate posts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "collection": {
                      "type": "object",
                      "description": "id, name and slug of the target collection"
                    },
                    "posts": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      },
                      "description": "Candidates, each with its current collectionId/collectionName/collectionSlug or null"
                    },
                    "total": {
                      "type": "integer",
                      "description": "Candidates matching the filter, before paging"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Not a steward of this community",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such collection, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "File already-published posts into a collection",
        "description": "Stewards only. NO `expectedUpdatedAt`: this sets a scalar on posts rather than replacing anything stored on the collection, and filing the same posts twice moves nothing the second time. `status` is NEVER touched -- a review policy gates what a SUBMITTER files, not whether a steward may gather a live post. ALL OR NOTHING: every named post is resolved first, so a request naming one post from another community writes none of them.",
        "operationId": "fileCommunityPostsIntoCollection",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "collectionId",
            "in": "path",
            "description": "Collection id or tag slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "postIds"
                ],
                "properties": {
                  "postIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Post ids of APPROVED posts in this community; 1-50 per request"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Filed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "filed": {
                      "type": "integer",
                      "description": "Posts written into this collection"
                    },
                    "moved": {
                      "type": "integer",
                      "description": "How many of those carried a DIFFERENT collection before"
                    },
                    "alreadyFiled": {
                      "type": "integer",
                      "description": "Named posts already in this collection"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "postIds missing, malformed, empty, or over 50",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Not a steward of this community",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such collection, or a named post is not published in this community",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/members": {
      "get": {
        "summary": "The moderation roster",
        "description": "Every member with their uid and role. MODERATION ROLES ONLY, and distinct from `/members/directory`: the roster applies none of the per-member disclosure rules and therefore KEEPS a member who asked not to be listed, because a steward who cannot see someone cannot remove them.",
        "operationId": "listCommunityRoster",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Roster",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "members": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Add a member directly",
        "description": "By username. Creator and moderator only -- deliberately NARROWER than removal, because a tier that can mint its own peers is not a tier. No `expectedUpdatedAt`; the unique (communityId, uid) index answers a race with the same 409 the pre-check would.",
        "operationId": "addCommunityMember",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "username"
                ],
                "properties": {
                  "username": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "member": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "username is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such community, or no such user",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already a member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/members/{uid}": {
      "patch": {
        "summary": "Change a member's role",
        "description": "Creator and moderator only -- NARROWER than removal on purpose: a steward may remove a member but may not promote one. The creator's role cannot be changed. No `expectedUpdatedAt`: the membership row carries no field that moves on a role change, and the write is a total assignment of a stated value.",
        "operationId": "setCommunityMemberRole",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "uid",
            "in": "path",
            "description": "The member's uid",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "role"
                ],
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "moderator",
                      "steward",
                      "member"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Changed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "role": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Role must be moderator, steward or member",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role, or the target is the creator",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Member not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Remove a member",
        "description": "Creator, moderator and steward -- one tier, at parity with the invite side. The creator cannot be removed. No `expectedUpdatedAt`; the end state is the point. Their posts and comments survive.",
        "operationId": "removeCommunityMember",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "uid",
            "in": "path",
            "description": "The member's uid",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role, or the target is the creator",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Member not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/access-requests": {
      "post": {
        "summary": "Request access to a private community",
        "description": "No `expectedUpdatedAt` -- the partial unique index on a pending request makes a duplicate impossible rather than merely unlikely. A secret community answers 404, and so does a public one that needs no request: one answer for three cases, on purpose. A declined request holds for seven days, answered 429 with `retryAfterMs`.",
        "operationId": "requestCommunityAccess",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "A short note to the stewards"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Requested",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "request": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already a member, or a request is already pending",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Declined recently -- body carries retryAfterMs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "Pending access requests",
        "description": "Creator, moderator and steward. Each row carries the requester and their note.",
        "operationId": "listCommunityAccessRequests",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pending requests",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "requests": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/access-requests/{requestId}": {
      "patch": {
        "summary": "Approve or deny an access request",
        "description": "Creator, moderator and steward. Approving inserts the membership; denying starts a seven-day cooldown. No `expectedUpdatedAt`, and this is the one review decision in the domain that takes none: the pending-to-decided transition rides INSIDE the findOneAndUpdate filter, so two moderators deciding at once produce exactly one winner and the loser is told there is no pending request.",
        "operationId": "decideCommunityAccessRequest",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "requestId",
            "in": "path",
            "description": "The access-request id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "decision"
                ],
                "properties": {
                  "decision": {
                    "type": "string",
                    "enum": [
                      "approve",
                      "deny"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decided",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "request": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request id or decision",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No pending request found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/join-link": {
      "get": {
        "summary": "Read community join link",
        "description": "Creator, moderator and steward. Reads the one standing not-revoked join link for the community, including the token and URL because this is the management surface. Public token metadata never discloses the token or slug.",
        "operationId": "readCommunityJoinLink",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The standing join link, or null when disabled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "joinLink": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "token": {
                          "type": "string"
                        },
                        "url": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "active",
                            "expired",
                            "exhausted"
                          ]
                        },
                        "uses": {
                          "type": "number"
                        },
                        "maxUses": {
                          "type": "number",
                          "nullable": true
                        },
                        "expiresAt": {
                          "type": "string",
                          "nullable": true
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create or update community join link",
        "description": "Creator, moderator and steward. Creates or updates the standing join link. Pass `regenerate: true` to revoke the old token and mint a new one. The link always grants `member`; use limits and expiry are enforced at claim time. No `expectedUpdatedAt`: one not-revoked link per community is enforced by a partial unique index.",
        "operationId": "createCommunityJoinLink",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "expires": {
                    "type": "string",
                    "enum": [
                      "never",
                      "24h",
                      "7d",
                      "30d"
                    ]
                  },
                  "maxUses": {
                    "type": "number",
                    "nullable": true,
                    "description": "25, 100, 250, or null for no limit"
                  },
                  "regenerate": {
                    "type": "boolean",
                    "description": "When true, revoke the current token before creating the new one"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing link updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "joinLink": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Link created or regenerated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "joinLink": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid expiry or use limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Revoke community join link",
        "description": "Creator, moderator and steward. Revokes the standing join link. Existing members stay members; holders of the old URL see that it is no longer valid. No `expectedUpdatedAt` because revoke is an idempotent guarded write over the one live link.",
        "operationId": "revokeCommunityJoinLink",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked or already disabled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/invites": {
      "get": {
        "summary": "Pending invites",
        "description": "Creator, moderator and steward. Returns `inviteId` for revoking and NEVER the token -- the token is the secret that grants acceptance and belongs only in the invitee's inbox.",
        "operationId": "listCommunityInvites",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pending invites",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "invites": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Invite someone to a community",
        "description": "Creator, moderator and steward. THIS SENDS EMAIL to a real person and cannot be taken back, so it carries its own hourly rate bucket. Inviting by EMAIL is open; by USERNAME only for someone connected to the caller as a collaborator, an active subscriber, or an accepted contact. Inviting as `steward` is restricted to the creator or a moderator, so an invite is not a side door into a tier its sender could not grant directly. No `expectedUpdatedAt`.",
        "operationId": "inviteToCommunity",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "invitee"
                ],
                "properties": {
                  "invitee": {
                    "type": "string",
                    "description": "An email address or a MythOS handle"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "member",
                      "steward"
                    ]
                  },
                  "note": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invite sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "status": {
                      "type": "string"
                    },
                    "inviteeKind": {
                      "type": "string"
                    },
                    "emailSent": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invitee is required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role, or no connection on the username path",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found, or not yours to see",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already a member, or an invite is already pending",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The invitation email could not be sent; the invite was rolled back",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/invites/{inviteId}": {
      "delete": {
        "summary": "Revoke a pending invite",
        "description": "Creator, moderator and steward. No `expectedUpdatedAt` -- the delete is scoped to the community AND to unused invites in one guarded write, so an invite already accepted cannot be revoked out from under the person who used it, and an id from another community reads as nonexistent.",
        "operationId": "revokeCommunityInvite",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "description": "The community slug",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "inviteId",
            "in": "path",
            "description": "The invite id from the pending list",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient role",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No such unused invite in this community",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/community-invites": {
      "get": {
        "summary": "Read an invite token",
        "description": "What a token points at -- which community, and which address it was sent to -- so a caller can see what it is accepting before accepting. Does not consume the token. Lives at the top level because the token IS the address: the caller does not know the slug yet, which is the reason to ask.",
        "operationId": "readCommunityInviteToken",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "description": "The invite token from the link",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Token metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "communitySlug": {
                      "type": "string"
                    },
                    "communityName": {
                      "type": "string"
                    },
                    "invitedEmail": {
                      "type": "string"
                    },
                    "invitedStatus": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing invite token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Invite not found or expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/community-invites/accept": {
      "post": {
        "summary": "Accept an invite",
        "description": "Binds to the caller's own uid -- an account-less invitee only becomes that uid by signing up with the invited email, so no other account can accept. No `expectedUpdatedAt`: the accept is idempotent by key, a concurrent double-accept loses on the unique index and is treated as success, and a token already consumed answers 410.",
        "operationId": "acceptCommunityInvite",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "communitySlug": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing invite token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "This invitation is bound to another account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Invite not found or expired",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "This invitation has already been used",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/posts": {
      "get": {
        "summary": "List community posts",
        "description": "Approved posts in a community you can see. Each row carries `updatedAt`, the lock token the write endpoints require. Pass `full=1` to read through the shared enricher and get the complete card payload.",
        "operationId": "listCommunityPosts",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Search query to filter posts by title",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of posts to return (1-200, default 50)",
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1,
              "maximum": 200
            }
          },
          {
            "name": "full",
            "in": "query",
            "description": "Return the full enriched payload rather than the lean summary",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of approved posts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "posts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CommunityPost"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a post",
        "operationId": "createCommunityPost",
        "description": "Five kinds. `memo` submits one of your own memos and is ungated; the four native kinds need a paid tier AND membership. A text body or image caption carrying a `hidden` memo mention is refused with a `hiddenMentions` list to confirm and retry; a `private` memo is never allowed. No expectedUpdatedAt.",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "memo",
                      "text",
                      "image",
                      "link",
                      "poll"
                    ]
                  },
                  "memoId": {
                    "type": "string"
                  },
                  "title": {
                    "type": "string"
                  },
                  "content": {
                    "type": "string"
                  },
                  "imageUrl": {
                    "type": "string"
                  },
                  "alt": {
                    "type": "string"
                  },
                  "caption": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string"
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "duration": {
                    "type": "string"
                  },
                  "context": {
                    "type": "string"
                  },
                  "collectionId": {
                    "type": "string"
                  },
                  "confirmHiddenMentions": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created post"
          },
          "400": {
            "description": "Validation failed, or HIDDEN_MENTION_CONFIRM with hiddenMentions"
          },
          "403": {
            "description": "Unauthorized, tier or role insufficient, or not a member"
          },
          "404": {
            "description": "Not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/memos/index": {
      "get": {
        "summary": "Get memo index",
        "description": "Returns a lightweight index of all memos with IDs, titles, tags, and update timestamps. No content included.",
        "operationId": "getMemoIndex",
        "responses": {
          "200": {
            "description": "Memo index",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "memos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "tags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "updatedAt": {
                            "type": "number"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/journal": {
      "get": {
        "summary": "Read, search, or list daily memos",
        "description": "Returns a daily memo for a specific date, or owner-only daily memo search/list/tag results when mode=search, list, or tags.",
        "operationId": "readOrSearchDailyMemos",
        "parameters": [
          {
            "name": "mode",
            "in": "query",
            "description": "Optional daily memo mode. Omit for exact-date read.",
            "schema": {
              "type": "string",
              "enum": [
                "search",
                "list",
                "tags"
              ]
            }
          },
          {
            "name": "date",
            "in": "query",
            "description": "Date in YYYY-MM-DD format. Required when mode is omitted.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "username",
            "in": "query",
            "description": "Username (defaults to configured default user)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Content substring for mode=search. Required unless tags is provided.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start date for search/list in YYYY-MM-DD format",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End date for search/list in YYYY-MM-DD format",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "tags",
            "in": "query",
            "description": "Comma-separated daily memo tags for search/list",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tagMode",
            "in": "query",
            "description": "Required when two or more tags are provided",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "any"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Max daily memo summaries for search/list (default 50, max 200)",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Daily memo content, daily memo summaries, or daily tag counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "dailyMemo": {
                      "type": "object",
                      "properties": {
                        "_id": {
                          "type": "string",
                          "nullable": true,
                          "description": "Format: {username}-{YYYY-MM-DD}, or null if no memo exists"
                        },
                        "date": {
                          "type": "string"
                        },
                        "content": {
                          "type": "string",
                          "nullable": true,
                          "description": "Markdown content"
                        },
                        "createdAt": {
                          "type": "number",
                          "nullable": true
                        },
                        "updatedAt": {
                          "type": "number",
                          "nullable": true
                        }
                      }
                    },
                    "dailyMemos": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "date": {
                            "type": "string"
                          },
                          "title": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string"
                          },
                          "firstLine": {
                            "type": "string"
                          },
                          "snippet": {
                            "type": "string"
                          },
                          "tags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "createdAt": {
                            "type": "number"
                          },
                          "updatedAt": {
                            "type": "number"
                          }
                        }
                      }
                    },
                    "tags": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "count": {
                            "type": "number"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid date",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Insert into daily memo section",
        "description": "Inserts a checklist task or bullet entry into a specific section of a daily memo. Auto-creates the daily memo from the user's default template if none exists for the date. Supports positional insertion via afterLine.",
        "operationId": "insertDailyMemoEntry",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": true,
            "description": "Date in YYYY-MM-DD format",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "name": "username",
            "in": "query",
            "description": "Username (defaults to configured default user)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "section",
                  "entry"
                ],
                "properties": {
                  "section": {
                    "type": "string",
                    "description": "Section heading to insert into (e.g. 'Tasks', 'Timeline')"
                  },
                  "entry": {
                    "type": "string",
                    "description": "Entry text (without bullet/checkbox prefix)"
                  },
                  "entryType": {
                    "type": "string",
                    "enum": [
                      "task",
                      "bullet"
                    ],
                    "default": "bullet",
                    "description": "'task' adds '- [ ] ' prefix, 'bullet' adds '* ' prefix"
                  },
                  "afterLine": {
                    "type": "string",
                    "description": "Insert after a line matching this text exactly (trimmed). If not found, appends at end of section."
                  },
                  "sourceMarkupVersion": {
                    "type": "integer",
                    "description": "Optional. Markup version of the entry text. Omit (or 1) for legacy behavior — bare #word is auto-linked into a tag. Set to 2 to keep bare #word as literal text."
                  }
                }
              },
              "examples": {
                "addTask": {
                  "summary": "Add a checklist task",
                  "value": {
                    "section": "Tasks",
                    "entry": "Review the PR",
                    "entryType": "task"
                  }
                },
                "addTimeline": {
                  "summary": "Add a timeline entry",
                  "value": {
                    "section": "Timeline",
                    "entry": "12:30 PM — Lunch with Richard Titus"
                  }
                },
                "insertAfter": {
                  "summary": "Insert after a specific line",
                  "value": {
                    "section": "Timeline",
                    "entry": "9:45 AM — Quick sync with design team",
                    "afterLine": "* 9:00 AM — Morning standup"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Entry added successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "message": {
                      "type": "string"
                    },
                    "section": {
                      "type": "string"
                    },
                    "entry": {
                      "type": "string",
                      "description": "The formatted entry as inserted (with prefix)"
                    },
                    "warning": {
                      "type": "string",
                      "description": "Present if afterLine was not found"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "User not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Concurrent edit conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/newsletter/subscribers": {
      "post": {
        "summary": "Create a subscriber",
        "description": "Adds a subscriber to the caller's newsletter. Sets importSource=api automatically. Requires an Oracle subscription. REQUIRES an explicit boolean for bypassDoubleOptIn and skipWelcomeEmail — adding a person to a mailing list on their behalf is a decision that must be stated, never defaulted. With bypassDoubleOptIn=false the subscriber is created 'pending' and must confirm by email; unconfirmed pending records are deleted after 30 days.",
        "operationId": "createNewsletterSubscriber",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "firstName",
                  "bypassDoubleOptIn",
                  "skipWelcomeEmail"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "company": {
                    "type": "string"
                  },
                  "phone": {
                    "type": "string",
                    "description": "Stored as E.164. A bare 10-digit number is assumed North American; anything else must carry a country code (e.g. +442079460958). Unparseable input is rejected."
                  },
                  "location": {
                    "type": "string"
                  },
                  "customFields": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Values for custom fields already defined in the caller's newsletter settings. Unknown keys are rejected, not dropped."
                  },
                  "topics": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Topic slugs the caller owns. Omit for creator-level subscription."
                  },
                  "frequency": {
                    "type": "string",
                    "enum": [
                      "instant",
                      "daily",
                      "weekly",
                      "monthly",
                      "none"
                    ],
                    "default": "weekly"
                  },
                  "digestMode": {
                    "type": "string",
                    "enum": [
                      "bundled",
                      "separate"
                    ],
                    "default": "separate"
                  },
                  "bypassDoubleOptIn": {
                    "type": "boolean",
                    "description": "Required. true = active immediately with no confirmation email; only legitimate for an audience that has already consented. false = send the confirmation email and leave them pending."
                  },
                  "skipWelcomeEmail": {
                    "type": "boolean",
                    "description": "Required. true = suppress the welcome email for this subscriber."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Subscriber created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscriptionId": {
                      "type": "string"
                    },
                    "pending": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized, or the caller has no Oracle subscription (NEWSLETTER_NOT_ENTITLED)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already subscribed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "List subscribers",
        "description": "Lists the caller's newsletter subscribers, newest first. Returns the subscriptionId the update and delete endpoints require. Paginated — pass the returned nextOffset to page forward; it is null when no pages remain. Requires an Oracle subscription.",
        "operationId": "listNewsletterSubscribers",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Default 'active'. 'pending' = added but not yet confirmed via double opt-in.",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "pending",
                "unsubscribed",
                "all"
              ],
              "default": "active"
            }
          },
          {
            "name": "topic",
            "in": "query",
            "description": "Topic slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "frequency",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "instant",
                "daily",
                "weekly",
                "monthly",
                "none"
              ]
            }
          },
          {
            "name": "search",
            "in": "query",
            "description": "Case-insensitive substring match on email address",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of subscribers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    },
                    "nextOffset": {
                      "type": "integer",
                      "nullable": true
                    },
                    "subscribers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NewsletterSubscriber"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid status or frequency",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized, or the caller has no Oracle subscription (NEWSLETTER_NOT_ENTITLED)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/newsletter/stats": {
      "get": {
        "summary": "Newsletter audience stats",
        "description": "Active total, pending confirmations, breakdown by cadence, scope and topic, and 7/30-day growth against the prior period. Requires an Oracle subscription.",
        "operationId": "getNewsletterStats",
        "responses": {
          "200": {
            "description": "Audience summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    },
                    "pendingCount": {
                      "type": "integer"
                    },
                    "byFrequency": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "byScope": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "byTopic": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "count": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "allUpdatesCount": {
                      "type": "integer"
                    },
                    "growth": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized, or the caller has no Oracle subscription (NEWSLETTER_NOT_ENTITLED)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/newsletter/topics": {
      "get": {
        "summary": "List newsletter topics",
        "description": "Lists the caller's newsletter topics with subscriber counts. Topic slugs are opaque ids (t_a3f9b2c1d0), so this is how a topic NAME resolves to the slug the subscriber endpoints expect. Never construct a slug. Requires an Oracle subscription.",
        "operationId": "listNewsletterTopics",
        "responses": {
          "200": {
            "description": "Topics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "topics": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "slug": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "hashtags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "hidden": {
                            "type": "boolean"
                          },
                          "threshold": {
                            "type": "integer",
                            "nullable": true
                          },
                          "subscriberCount": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "broadcastsOnlyCount": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized, or the caller has no Oracle subscription (NEWSLETTER_NOT_ENTITLED)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/newsletter/subscribers/{id}": {
      "patch": {
        "summary": "Update a subscriber",
        "description": "Updates an existing subscription owned by the caller. At least one field must be provided. Email is immutable. For topics, pass either `topics` (replaces the whole set) or `addTopics`/`removeTopics` (amends it) — passing both is a 400. Slugs being ADDED must be owned by the caller; a slug being REMOVED need not be, so subscribers stranded on a deleted topic can still be cleaned up. Pass null to company/phone/location to clear the value. Requires an Oracle subscription.",
        "operationId": "updateNewsletterSubscriber",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the subscription",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "firstName": {
                    "type": "string"
                  },
                  "lastName": {
                    "type": "string"
                  },
                  "company": {
                    "type": "string",
                    "nullable": true
                  },
                  "phone": {
                    "type": "string",
                    "nullable": true,
                    "description": "Stored as E.164. A bare 10-digit number is assumed North American; anything else must carry a country code. Unparseable input is rejected."
                  },
                  "location": {
                    "type": "string",
                    "nullable": true
                  },
                  "customFields": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    },
                    "description": "Values for custom fields already defined in the caller's newsletter settings. Unknown keys are rejected."
                  },
                  "topics": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Replaces existing topic preferences. Empty array resets to creator-level scope. Do not combine with addTopics/removeTopics."
                  },
                  "addTopics": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Topic slugs to add, leaving the subscriber's other topics untouched."
                  },
                  "removeTopics": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Topic slugs to remove, leaving the subscriber's other topics untouched."
                  },
                  "frequency": {
                    "type": "string",
                    "enum": [
                      "instant",
                      "daily",
                      "weekly",
                      "monthly",
                      "none"
                    ]
                  },
                  "digestMode": {
                    "type": "string",
                    "enum": [
                      "bundled",
                      "separate"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscriber updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error or no fields to update",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized, or the caller has no Oracle subscription (NEWSLETTER_NOT_ENTITLED)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Subscription not found or belongs to another creator",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Remove a subscriber",
        "description": "Unsubscribes a subscriber and flags the record removedByCreator. A soft removal, not a hard delete: growth metrics distinguish 'they left' from 'the creator removed them', and destroying the row would let a removed person silently re-subscribe as new. Requires an Oracle subscription.",
        "operationId": "removeNewsletterSubscriber",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "MongoDB ObjectId of the subscription",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Subscriber unsubscribed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized, or the caller has no Oracle subscription (NEWSLETTER_NOT_ENTITLED)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Subscription not found or belongs to another creator",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/memos/changes": {
      "get": {
        "summary": "Delta sync",
        "description": "Returns memos that have changed since the given timestamp. Used for incremental synchronization.",
        "operationId": "deltaSyncMemos",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": true,
            "description": "ISO 8601 timestamp to get changes since",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Maximum number of results",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Changed memos since timestamp",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "memos": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MemoSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/communities/{slug}/events": {
      "get": {
        "summary": "List a month of a community calendar",
        "description": "Occurrences you may see for the given month, plus `viewer.canCreate` — whether you may create, may only propose, or may do neither is the community's policy.",
        "operationId": "listCommunityEvents",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "year",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Four-digit year (defaults to the current UTC month)"
          },
          {
            "name": "month",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12
            },
            "description": "Month 1-12"
          }
        ],
        "responses": {
          "200": {
            "description": "The month's occurrences"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      },
      "post": {
        "summary": "Create or propose an event",
        "description": "No expectedUpdatedAt — there is no prior document to lock. Publishing versus queueing for a steward is the community's policy and rides back as `queued`; it is not an error. Send `requestReview: true` to queue your own event whatever that policy says.",
        "operationId": "createCommunityEvent",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "dtstart",
                  "dtend",
                  "timezone"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "dtstart": {
                    "type": "object",
                    "description": "Wall-clock start { year, month, day, hour, minute }"
                  },
                  "dtend": {
                    "type": "object"
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA zone — an event happens at a wall-clock time in a place"
                  },
                  "rrule": {
                    "type": "string",
                    "description": "RFC 5545 RRULE"
                  },
                  "place": {
                    "type": "string"
                  },
                  "address": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string",
                    "description": "External link for the event (tickets, host's own page, livestream). Absolute http(s) URL only."
                  },
                  "description": {
                    "type": "string"
                  },
                  "cost": {
                    "type": "string"
                  },
                  "spots": {
                    "type": [
                      "integer",
                      "string",
                      "null"
                    ],
                    "description": "Capacity: a count if limited, null/omitted for unlimited, or the string \"unknown\" for a real limit only the actual host could state. A submitted-not-hosted event (host: false) with no count defaults to unknown."
                  },
                  "categoryId": {
                    "type": "string"
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "public",
                      "members",
                      "link"
                    ]
                  },
                  "flyerUrl": {
                    "type": "string",
                    "description": "Confirmed MythOS upload URL owned by the caller"
                  },
                  "flyerRatio": {
                    "type": "string",
                    "enum": [
                      "portrait",
                      "square",
                      "landscape"
                    ],
                    "description": "Derived server-side from the image itself whenever the flyer is new or changed; pass a value only to correct a stored ratio on an unchanged flyer."
                  },
                  "rsvpOptions": {
                    "type": "object",
                    "description": "RSVP options: maybe, cant, approveGuests, plusOnes"
                  },
                  "submitterNote": {
                    "type": "string"
                  },
                  "requestReview": {
                    "type": "boolean",
                    "description": "Queue the event for steward review regardless of your role. Additive only — it can put an event in front of a steward, never take one out of review. Use it to watch a new automation land in the stewardship panel before letting it run unmoderated."
                  },
                  "host": {
                    "type": "boolean",
                    "description": "Is this your event? true (the default) lists you as the host. false lists you as the submitter and leaves the host slot open for the real host to claim through the stewards. Display only — you keep edit and cancel authority either way."
                  },
                  "pricing": {
                    "type": "string",
                    "enum": [
                      "free",
                      "paid",
                      "unknown"
                    ],
                    "description": "The answer to 'does this cost money?'. \"unknown\" withholds the price entirely — the page shows no price. Omitted, it derives from the cost text; with no cost text a hosted event defaults free and a submitted-not-hosted one defaults unknown."
                  },
                  "hideSubmitter": {
                    "type": "boolean",
                    "description": "With host: false, keep the \"Submitted by\" attribution off the listing. Display only — stewards and the review queue still see the submitter. Omitted keeps the stored answer on edit."
                  },
                  "hostUsername": {
                    "type": "string",
                    "description": "With host: false, attribute a named MythOS creator as the host by username, shown instead of the open claim slot. Display only. On edit, omitted keeps the stored attribution; empty string clears it."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created event, with `queued`"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded, or the community's per-member weekly cap is reached"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/events/queue": {
      "get": {
        "summary": "The steward review queue",
        "description": "Pending events, oldest first. Requires a moderation role.",
        "operationId": "listPendingCommunityEvents",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pending events"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/events/{eventId}": {
      "get": {
        "summary": "Read one event",
        "description": "Dates, the guest list where you may see it, your own RSVP, and the event's `updatedAt` for the hard-locked writes.",
        "operationId": "readCommunityEvent",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "The event id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "occurrenceStart",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Which date to select, epoch ms. Defaults to the next one still to come."
          }
        ],
        "responses": {
          "200": {
            "description": "The event"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      },
      "patch": {
        "summary": "Edit an event",
        "description": "Series-scoped event edit. Requires expectedUpdatedAt from readCommunityEvent. Takes the same event fields as createCommunityEvent. Changes to dtstart, dtend, timezone, or rrule are refused once RSVPs exist because RSVP rows are keyed to occurrenceStart instants.",
        "operationId": "updateCommunityEvent",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "The event id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "dtstart",
                  "dtend",
                  "timezone",
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "dtstart": {
                    "type": "object",
                    "description": "Wall-clock start { year, month, day, hour, minute }"
                  },
                  "dtend": {
                    "type": "object"
                  },
                  "timezone": {
                    "type": "string",
                    "description": "IANA zone"
                  },
                  "rrule": {
                    "type": "string",
                    "description": "RFC 5545 RRULE"
                  },
                  "place": {
                    "type": "string"
                  },
                  "address": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string",
                    "description": "External link for the event (tickets, host's own page, livestream). Absolute http(s) URL only."
                  },
                  "description": {
                    "type": "string"
                  },
                  "cost": {
                    "type": "string"
                  },
                  "spots": {
                    "type": [
                      "integer",
                      "string",
                      "null"
                    ],
                    "description": "Capacity: a count if limited, null/omitted for unlimited, or the string \"unknown\" for a real limit only the actual host could state. A submitted-not-hosted event (host: false) with no count defaults to unknown."
                  },
                  "categoryId": {
                    "type": "string"
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "public",
                      "members",
                      "link"
                    ]
                  },
                  "flyerUrl": {
                    "type": "string",
                    "description": "Confirmed MythOS upload URL owned by the caller. Empty string clears the flyer."
                  },
                  "flyerRatio": {
                    "type": "string",
                    "enum": [
                      "portrait",
                      "square",
                      "landscape"
                    ],
                    "description": "Derived server-side from the image itself whenever the flyer is new or changed; pass a value only to correct a stored ratio on an unchanged flyer."
                  },
                  "rsvpOptions": {
                    "type": "object",
                    "description": "RSVP options: maybe, cant, approveGuests, plusOnes"
                  },
                  "host": {
                    "type": "boolean",
                    "description": "Is the submitter the listed host? Omitted keeps the stored answer; false shows the event as submitted-not-hosted with an open host slot."
                  },
                  "expectedUpdatedAt": {
                    "type": "integer",
                    "description": "Current event updatedAt token. Mismatch returns 409 STALE_LOCK_TOKEN with currentUpdatedAt."
                  },
                  "pricing": {
                    "type": "string",
                    "enum": [
                      "free",
                      "paid",
                      "unknown"
                    ],
                    "description": "The answer to 'does this cost money?'. \"unknown\" withholds the price entirely — the page shows no price. Omitted, it derives from the cost text; with no cost text a hosted event defaults free and a submitted-not-hosted one defaults unknown."
                  },
                  "hideSubmitter": {
                    "type": "boolean",
                    "description": "With host: false, keep the \"Submitted by\" attribution off the listing. Display only — stewards and the review queue still see the submitter. Omitted keeps the stored answer on edit."
                  },
                  "hostUsername": {
                    "type": "string",
                    "description": "With host: false, attribute a named MythOS creator as the host by username, shown instead of the open claim slot. Display only. On edit, omitted keeps the stored attribution; empty string clears it."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated event, with `queued` and `changes`"
          },
          "400": {
            "description": "Invalid payload or missing expectedUpdatedAt"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "409": {
            "description": "Stale token, cancelled event, or schedule edit refused because RSVPs exist"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/events/{eventId}/rsvp": {
      "post": {
        "summary": "RSVP to one occurrence",
        "description": "Takes NO expectedUpdatedAt: an upsert keyed on (event, occurrence, uid), idempotent and re-answerable. Membership required.",
        "operationId": "rsvpCommunityEvent",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "The event id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "occurrenceStart",
                  "status"
                ],
                "properties": {
                  "occurrenceStart": {
                    "type": "integer",
                    "description": "Epoch ms"
                  },
                  "status": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "going",
                      "maybe",
                      "cant",
                      null
                    ],
                    "description": "null withdraws"
                  },
                  "plusOnes": {
                    "type": "integer"
                  },
                  "note": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Your RSVP and the new counts"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/events/{eventId}/guest-link": {
      "get": {
        "summary": "Read event guest link",
        "description": "Host or moderation role, with the community calendar's guestLinks setting enabled. Reads the one live per-event guest link, including token and URL because this is the steward management surface. Public token routes are deliberately not mirrored.",
        "operationId": "readCommunityEventGuestLink",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "The event id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The live guest link, or null when disabled"
          },
          "403": {
            "description": "Unauthorized, disabled setting, or role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      },
      "post": {
        "summary": "Create or update event guest link",
        "description": "Host or moderation role. Creates, updates, or regenerates the one live guest link for this event. No expectedUpdatedAt: one not-revoked link per event is enforced by a partial unique index, and regenerate is explicitly revoke-then-insert. The link grants event attendance only, never membership.",
        "operationId": "createCommunityEventGuestLink",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "The event id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "expires": {
                    "type": "string",
                    "enum": [
                      "event-end",
                      "24h",
                      "7d",
                      "never"
                    ]
                  },
                  "maxUses": {
                    "type": "number",
                    "nullable": true,
                    "description": "10, 25, 50, or null for no limit"
                  },
                  "regenerate": {
                    "type": "boolean",
                    "description": "When true, revoke the current token before creating the new one"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Existing link updated"
          },
          "201": {
            "description": "Link created or regenerated"
          },
          "400": {
            "description": "Invalid expiry or use limit"
          },
          "403": {
            "description": "Unauthorized, disabled setting, or role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "409": {
            "description": "The event is not open for guest links"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      },
      "delete": {
        "summary": "Revoke event guest link",
        "description": "Host or moderation role. Revokes the live guest link. Existing RSVP rows remain; holders of the old URL see that it is no longer valid. No expectedUpdatedAt because revoke is an idempotent guarded write over the one live link.",
        "operationId": "revokeCommunityEventGuestLink",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "The event id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked or already disabled"
          },
          "403": {
            "description": "Unauthorized, disabled setting, or role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/events/{eventId}/review": {
      "patch": {
        "summary": "Decide a submitted event",
        "description": "REQUIRES expectedUpdatedAt. Approving a series that changed since you read it publishes something nobody reviewed, so the token rides the update filter. Requires a moderation role.",
        "operationId": "reviewCommunityEvent",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "The event id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "decision",
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "decision": {
                    "type": "string",
                    "enum": [
                      "approve",
                      "first",
                      "decline"
                    ]
                  },
                  "expectedUpdatedAt": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decided event"
          },
          "400": {
            "description": "MISSING_LOCK_TOKEN — expectedUpdatedAt was not supplied"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt, or the event was already decided"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/events/{eventId}/cancel": {
      "post": {
        "summary": "Cancel one date or a series",
        "description": "REQUIRES expectedUpdatedAt. A cancellation is never a delete — the event survives so subscribed calendars learn the date is off. Host or moderation role only.",
        "operationId": "cancelCommunityEvent",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "The event id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "scope",
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "scope": {
                    "type": "string",
                    "enum": [
                      "occurrence",
                      "series"
                    ]
                  },
                  "occurrenceStart": {
                    "type": "integer",
                    "description": "Required when scope is occurrence"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 300
                  },
                  "expectedUpdatedAt": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cancelled date or event"
          },
          "400": {
            "description": "MISSING_LOCK_TOKEN — expectedUpdatedAt was not supplied"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt, or the event was already decided"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/events/{eventId}/guests": {
      "patch": {
        "summary": "Approve or decline one guest",
        "description": "For an event whose host asked to approve guests. No expectedUpdatedAt — one field on a row keyed by (event, occurrence, guest), idempotent under retry.",
        "operationId": "decideCommunityEventGuest",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "The event id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "uid",
                  "occurrenceStart",
                  "approved"
                ],
                "properties": {
                  "uid": {
                    "type": "string",
                    "description": "The guest's uid"
                  },
                  "occurrenceStart": {
                    "type": "integer"
                  },
                  "approved": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated RSVP"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community or event not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/posts/{postId}/vote": {
      "post": {
        "summary": "Toggle a vote on a post",
        "description": "No expectedUpdatedAt — a toggle's end state is the point.",
        "operationId": "voteCommunityPost",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "value"
                ],
                "properties": {
                  "value": {
                    "type": "integer",
                    "enum": [
                      1,
                      -1
                    ],
                    "description": "Sending the same value again clears the vote"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new score and your vote"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community, post or comment not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      },
      "delete": {
        "summary": "Withdraw a post vote",
        "description": "Withdraw your vote. No `expectedUpdatedAt` -- a toggle's end state is the point, and a token would re-introduce the race the toggle avoids. Withdrawing a vote you never cast is a no-op rather than an error.",
        "operationId": "clearCommunityPostVote",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The new score"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community, post or comment not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/posts/{postId}/comments": {
      "get": {
        "summary": "Read a post's comment thread",
        "description": "Each comment carries its updatedAt, depth and your userVote. Mention chips pointing at memos you cannot see are redacted.",
        "operationId": "listCommunityComments",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "hot",
                "newest",
                "oldest"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The thread and your read watermark"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community, post or comment not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      },
      "post": {
        "summary": "Add a comment or reply",
        "description": "INLINE markdown only — block markdown renders literally. No expectedUpdatedAt.",
        "operationId": "addCommunityComment",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "maxLength": 10000
                  },
                  "parentCommentId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The created comment"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community, post or comment not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/posts/{postId}/comments/{commentId}": {
      "patch": {
        "summary": "Edit your own comment",
        "description": "REQUIRES expectedUpdatedAt. Author-only.",
        "operationId": "editCommunityComment",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "commentId",
            "in": "path",
            "required": true,
            "description": "The comment id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "content",
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "content": {
                    "type": "string",
                    "maxLength": 10000
                  },
                  "expectedUpdatedAt": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The edited comment"
          },
          "400": {
            "description": "MISSING_LOCK_TOKEN — expectedUpdatedAt was not supplied"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community, post or comment not found — also the answer when it exists and you may not see it"
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      },
      "delete": {
        "summary": "Delete a comment",
        "description": "REQUIRES expectedUpdatedAt as a QUERY PARAM, per the mutation contract's DELETE transport. Author or steward.",
        "operationId": "deleteCommunityComment",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "commentId",
            "in": "path",
            "required": true,
            "description": "The comment id",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expectedUpdatedAt",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "400": {
            "description": "MISSING_LOCK_TOKEN — expectedUpdatedAt was not supplied"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community, post or comment not found — also the answer when it exists and you may not see it"
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/posts/{postId}/comments/{commentId}/vote": {
      "post": {
        "summary": "Toggle a vote on a comment",
        "description": "Upvote or downvote a comment. Same toggle semantics as the post vote and no `expectedUpdatedAt` for the same reason. A comment inside a community you may not see answers 404 exactly as one that does not exist.",
        "operationId": "voteCommunityComment",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "commentId",
            "in": "path",
            "required": true,
            "description": "The comment id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "value"
                ],
                "properties": {
                  "value": {
                    "type": "integer",
                    "enum": [
                      1,
                      -1
                    ],
                    "description": "Sending the same value again clears the vote"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new score and your vote"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community, post or comment not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      },
      "delete": {
        "summary": "Withdraw a comment vote",
        "description": "Withdraw your vote on a comment. No `expectedUpdatedAt`; withdrawing a vote you never cast is a no-op.",
        "operationId": "clearCommunityCommentVote",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "commentId",
            "in": "path",
            "required": true,
            "description": "The comment id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The new score"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community, post or comment not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/posts/{postId}/poll": {
      "post": {
        "summary": "Answer a poll",
        "description": "No expectedUpdatedAt — an upsert on (post, uid), so answering again MOVES your response. Option ids come from the post's own pollOptions.",
        "operationId": "answerCommunityPoll",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "optionId"
                ],
                "properties": {
                  "optionId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The running counts and your choice"
          },
          "400": {
            "description": "NOT_A_POLL or POLL_OPTION_UNKNOWN"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community, post or comment not found — also the answer when it exists and you may not see it"
          },
          "409": {
            "description": "POLL_CLOSED"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/posts/{postId}/bookmark": {
      "post": {
        "summary": "Toggle a bookmark",
        "description": "No expectedUpdatedAt. DELETE-first internally, so calling again removes it; `bookmarked` says which way it went.",
        "operationId": "bookmarkCommunityPost",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bookmark state and count"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community, post or comment not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/posts/{postId}/read": {
      "post": {
        "summary": "Advance your thread-read watermark",
        "description": "Explicit rather than a side effect of reading comments. No expectedUpdatedAt.",
        "operationId": "markCommunityThreadRead",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Marked"
          },
          "403": {
            "description": "Unauthorized, or your role does not permit this action"
          },
          "404": {
            "description": "Community, post or comment not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/posts/{postId}": {
      "get": {
        "summary": "Read one post in full",
        "description": "Through the shared enricher. Returns `updatedAt`, the lock token.",
        "operationId": "readCommunityPost",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The post"
          },
          "403": {
            "description": "Unauthorized, tier or role insufficient, or not a member"
          },
          "404": {
            "description": "Not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      },
      "patch": {
        "summary": "Approve or reject a queued post",
        "description": "Moderation role. Optionally refiles. No expectedUpdatedAt.",
        "operationId": "reviewCommunityPost",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "status"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "approved",
                      "rejected"
                    ]
                  },
                  "collectionId": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decided post"
          },
          "403": {
            "description": "Unauthorized, tier or role insufficient, or not a member"
          },
          "404": {
            "description": "Not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      },
      "delete": {
        "summary": "Delete a post",
        "description": "REQUIRES expectedUpdatedAt as a query param. Not reversible.",
        "operationId": "deleteCommunityPost",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expectedUpdatedAt",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "400": {
            "description": "MISSING_LOCK_TOKEN"
          },
          "403": {
            "description": "Unauthorized, tier or role insufficient, or not a member"
          },
          "404": {
            "description": "Not found — also the answer when it exists and you may not see it"
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/posts/{postId}/content": {
      "patch": {
        "summary": "Rewrite your own text post",
        "description": "REQUIRES expectedUpdatedAt. Author-only, text posts only. The postSlug never changes.",
        "operationId": "editCommunityPost",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "title",
                  "content",
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "content": {
                    "type": "string"
                  },
                  "confirmHiddenMentions": {
                    "type": "boolean"
                  },
                  "expectedUpdatedAt": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The edited post"
          },
          "400": {
            "description": "MISSING_LOCK_TOKEN"
          },
          "403": {
            "description": "Unauthorized, tier or role insufficient, or not a member"
          },
          "404": {
            "description": "Not found — also the answer when it exists and you may not see it"
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/posts/{postId}/context": {
      "patch": {
        "summary": "Edit your own submission note",
        "description": "No expectedUpdatedAt — only the submitter writes this field. Empty string removes the note.",
        "operationId": "editCommunitySubmissionNote",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "postId",
            "in": "path",
            "required": true,
            "description": "The post id or postSlug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "context"
                ],
                "properties": {
                  "context": {
                    "type": "string",
                    "maxLength": 2000
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored note, redacted as readers will see it"
          },
          "403": {
            "description": "Unauthorized, tier or role insufficient, or not a member"
          },
          "404": {
            "description": "Not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/drafts": {
      "get": {
        "summary": "Your own drafts for one community",
        "description": "Your OWN unpublished drafts for one community -- never anyone else's. A draft holds a partial post of any kind and is bounded by the same size limits publishing enforces, so it can never hold something that will be rejected on publish.",
        "operationId": "listCommunityDrafts",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your drafts"
          },
          "403": {
            "description": "Unauthorized, tier or role insufficient, or not a member"
          },
          "404": {
            "description": "Not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      },
      "post": {
        "summary": "Save a draft",
        "description": "No expectedUpdatedAt — autosave-shaped, and oversized fields truncate rather than reject.",
        "operationId": "saveCommunityDraft",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kind",
                  "payload"
                ],
                "properties": {
                  "kind": {
                    "type": "string"
                  },
                  "payload": {
                    "type": "object"
                  },
                  "draftId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The draft id"
          },
          "403": {
            "description": "Unauthorized, tier or role insufficient, or not a member"
          },
          "404": {
            "description": "Not found — also the answer when it exists and you may not see it"
          },
          "409": {
            "description": "DRAFT_LIMIT_REACHED"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    },
    "/api/internal/communities/{slug}/drafts/{draftId}": {
      "delete": {
        "summary": "Discard a draft",
        "description": "Discard one of your own drafts. Ownership rides the delete FILTER rather than a preceding read. No `expectedUpdatedAt` -- a draft is autosave-shaped and the end state is the point.",
        "operationId": "deleteCommunityDraft",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The community slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "draftId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Discarded"
          },
          "403": {
            "description": "Unauthorized, tier or role insufficient, or not a member"
          },
          "404": {
            "description": "Not found — also the answer when it exists and you may not see it"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        }
      }
    }
  }
}