{
  "openapi": "3.0.3",
  "info": {
    "title": "MythOS v3 Internal API",
    "description": "Agent-facing API for MythOS memo library operations, community browsing, embedding, and chat. Key-authenticated writes (POST, PUT, PATCH, DELETE) require an x-mythos-context header carrying the contextSessionId from GET /api/internal/augmentation; without one they answer 428 GET_CONTEXT_REQUIRED.",
    "version": "1.1.0"
  },
  "servers": [
    {
      "url": "https://staging.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"
      },
      "InternalServiceKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-internal-key",
        "description": "Service credential; not an account mtk_ key."
      },
      "DripWorkerKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-drip-secret"
      },
      "SessionBearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Human session JWT; account API keys are not equivalent."
      }
    },
    "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": {
          "memoType": {
            "type": "string",
            "enum": [
              "workflow"
            ],
            "description": "Staff-only while Loops is unreleased. Creates the memo already on /loops, in the caller's own library only, private unless visibility is passed."
          },
          "loopTemplate": {
            "type": "object",
            "description": "With memoType workflow and no content: compose the body from a starting template, as the surface's create form does. The title becomes the /command; Status is draft.",
            "required": [
              "templateId"
            ],
            "properties": {
              "templateId": {
                "type": "string",
                "enum": [
                  "queue",
                  "sweep",
                  "digest",
                  "review",
                  "prodtest",
                  "blank"
                ]
              },
              "triggerKind": {
                "type": "string",
                "enum": [
                  "command",
                  "schedule",
                  "queue",
                  "none"
                ],
                "default": "command"
              },
              "summary": {
                "type": "string",
                "description": "One line."
              }
            }
          },
          "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/channels": {
      "get": {
        "operationId": "listChannels",
        "summary": "List the owner's channels",
        "description": "Owner-scoped, staff-gated. Each channel carries unread (agent/loop top-level posts since the last read) and openAsks (unresolved asks).",
        "responses": {
          "200": {
            "description": "success and channels[] with slug, section, topic, private, workflowMemoIds, unread, openAsks, lastReadAt."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or the channel/message is not the caller's."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      },
      "post": {
        "operationId": "createChannel",
        "summary": "Create a channel, or return the existing one",
        "description": "Idempotent on slug: a repeat returns the existing channel unchanged and never overwrites topic or section. Posting to a missing channel also creates it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "slug"
                ],
                "properties": {
                  "slug": {
                    "type": "string"
                  },
                  "section": {
                    "type": "string"
                  },
                  "topic": {
                    "type": "string"
                  },
                  "private": {
                    "type": "boolean"
                  },
                  "workflowMemoIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Loops that post here; a gate's Needs-you message goes to the first channel naming its loop, else #loops."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already existed; returned unchanged."
          },
          "201": {
            "description": "Created."
          },
          "400": {
            "description": "INVALID_CHANNEL."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or the channel/message is not the caller's."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/channels/{channel}/messages": {
      "get": {
        "operationId": "listChannelMessages",
        "summary": "Read a channel's stream or one thread",
        "description": "Oldest first with a cursor. Pass the cursor back as after to read only newer messages — how an agent polls for replies and chosen actions.",
        "parameters": [
          {
            "name": "channel",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Channel slug, e.g. bbot-health; a leading # (URL-encoded) is ignored."
          },
          {
            "name": "threadId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Read this top-level message's replies instead of the stream."
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor from a previous read."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success, messages[], cursor."
          },
          "400": {
            "description": "INVALID_CHANNEL."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or the channel/message is not the caller's."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      },
      "post": {
        "operationId": "postChannelMessage",
        "summary": "Post a message, an ask, or a reply",
        "description": "An append: takes no lock token, and a retry without episodeKey can duplicate. An API key posts as an agent or loop, never a person. episodeKey collapses repeats into the open message (deduped: true). A reply advances its parent's updatedAt.",
        "parameters": [
          {
            "name": "channel",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Channel slug, e.g. bbot-health; a leading # (URL-encoded) is ignored."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "maxLength": 8000
                  },
                  "title": {
                    "type": "string"
                  },
                  "author": {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "agent",
                          "workflow"
                        ]
                      },
                      "name": {
                        "type": "string"
                      },
                      "workflowMemoId": {
                        "type": "string"
                      }
                    }
                  },
                  "ask": {
                    "type": "boolean"
                  },
                  "actions": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 6
                  },
                  "episodeKey": {
                    "type": "string"
                  },
                  "run": {
                    "type": "object",
                    "properties": {
                      "runId": {
                        "type": "string"
                      },
                      "stepId": {
                        "type": "string"
                      }
                    }
                  },
                  "threadId": {
                    "type": "string"
                  },
                  "action": {
                    "type": "string",
                    "description": "On a reply: one of the parent's actions."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "deduped: true — counted onto the open episode message."
          },
          "201": {
            "description": "Posted."
          },
          "400": {
            "description": "INVALID_CHANNEL, INVALID_MESSAGE, INVALID_AUTHOR or UNKNOWN_ACTION."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff, or THREAD_NOT_FOUND."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/channels/{channel}/messages/{messageId}/resolve": {
      "post": {
        "operationId": "resolveChannelMessage",
        "summary": "Resolve a top-level message",
        "description": "REQUIRES expectedUpdatedAt from the message as last read; the CAS is in the write filter. with must be one of the message's actions. A loop gate's message is decided by a signed-in person and is refused here.",
        "parameters": [
          {
            "name": "channel",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Channel slug, e.g. bbot-health; a leading # (URL-encoded) is ignored."
          },
          {
            "name": "messageId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "expectedUpdatedAt": {
                    "type": "number"
                  },
                  "with": {
                    "type": "string"
                  },
                  "note": {
                    "type": "string"
                  },
                  "author": {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "agent",
                          "workflow"
                        ]
                      },
                      "name": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resolved; returns the message."
          },
          "400": {
            "description": "MISSING_LOCK_TOKEN or UNKNOWN_ACTION."
          },
          "401": {
            "description": "Invalid API key."
          },
          "403": {
            "description": "HUMAN_DECISION_REQUIRED — a loop gate's message."
          },
          "404": {
            "description": "Not staff (Loops release gate), or the channel/message is not the caller's."
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt, or ALREADY_RESOLVED."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/channels/{channel}/read": {
      "post": {
        "operationId": "markChannelRead",
        "summary": "Mark a channel read up to now",
        "description": "Idempotent; the mark only moves forward.",
        "parameters": [
          {
            "name": "channel",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Channel slug, e.g. bbot-health; a leading # (URL-encoded) is ignored."
          }
        ],
        "responses": {
          "200": {
            "description": "success and lastReadAt."
          },
          "400": {
            "description": "INVALID_CHANNEL."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or the channel/message is not the caller's."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/devices/enroll": {
      "post": {
        "operationId": "enrollDevice",
        "summary": "Exchange a single-use install code for a device's key",
        "description": "No header credential: the enrollment code from Add device (POST /api/loops/devices/enrollments, ten minutes, single use) is the credential, with a per-IP limit of ten an hour. Returns the device's key once, uncached. The key carries scopes [\"device\", \"loop-executor\"] with executorId equal to deviceId, so it heartbeats and claims runs as that device. The code is spent before anything is minted, so a refusal spends it too. The installer (`mythos-device setup --code … --url …`) writes the key to its config with mode 0600 and never prints it.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "code"
                ],
                "properties": {
                  "code": {
                    "type": "string",
                    "description": "XXXX-XXXX; case, spaces and the hyphen are ignored."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The device's key, shown this once.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "key": {
                      "type": "string"
                    },
                    "deviceId": {
                      "type": "string"
                    },
                    "executorId": {
                      "type": "string"
                    },
                    "settings": {
                      "type": "object",
                      "description": "What Add device chose, for the installer to adopt; each field is null when not chosen. The same choices fill whatever the device's first heartbeat omits.",
                      "properties": {
                        "platform": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Platform label, e.g. macOS, PC, Linux."
                        },
                        "intervalSeconds": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "The chosen interval after the 60-second floor."
                        },
                        "alertChannel": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "INVALID_CODE: unknown, expired or already used."
          },
          "403": {
            "description": "ACCESS_DENIED (before release, the owner is not staff) or TIER_REQUIRED (after release, the owner is below Oracle)."
          },
          "409": {
            "description": "DEVICE_LIMIT (the account already has five devices with a live key), KEY_LIMIT or DEVICE_KEY_LIVE."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/devices/heartbeat": {
      "post": {
        "operationId": "sendDeviceHeartbeat",
        "summary": "A device reports its health",
        "description": "Device-scoped key only (scopes: [\"device\"], carrying deviceId); an owner or executor key is refused with 403 SCOPE_REFUSED. The first heartbeat creates the device record. Each heartbeat replaces the latest report; a check that turns failing opens an episode alert in the device's alertChannel and turning passing resolves it. A beat sooner than the 60-second floor after the last one that reached history updates the device but adds no history. A device silent for three effective intervals is marked missed by the run-loops tick, unless no live key names it, and the next heartbeat resolves that alert. Safe to retry.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "hostname": {
                    "type": "string"
                  },
                  "platform": {
                    "type": "string"
                  },
                  "intervalSeconds": {
                    "type": "integer",
                    "minimum": 30,
                    "maximum": 86400,
                    "description": "How often the device reports, 30 to 86400; default 300. Below the 60-second floor it is lifted to 60, the effective interval the record stores and settings.intervalSeconds returns. Kept from earlier heartbeats when omitted."
                  },
                  "alertChannel": {
                    "type": "string",
                    "description": "Channel slug for this device's alerts, e.g. bbot-health; default device-health. Kept when omitted."
                  },
                  "uptimeSeconds": {
                    "type": "number"
                  },
                  "disk": {
                    "type": "object",
                    "properties": {
                      "totalBytes": {
                        "type": "number"
                      },
                      "freeBytes": {
                        "type": "number"
                      }
                    }
                  },
                  "memory": {
                    "type": "object",
                    "description": "RAM. availableBytes is what a new process can take without swapping. Optional: older runners omit it.",
                    "properties": {
                      "totalBytes": {
                        "type": "number"
                      },
                      "availableBytes": {
                        "type": "number"
                      }
                    }
                  },
                  "checks": {
                    "type": "array",
                    "maxItems": 20,
                    "items": {
                      "type": "object",
                      "required": [
                        "name",
                        "ok"
                      ],
                      "properties": {
                        "name": {
                          "type": "string",
                          "maxLength": 60
                        },
                        "ok": {
                          "type": "boolean"
                        },
                        "detail": {
                          "type": "string",
                          "maxLength": 500
                        }
                      }
                    }
                  },
                  "tmuxSessions": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "object",
                      "required": [
                        "name"
                      ],
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "attached": {
                          "type": "boolean"
                        },
                        "windows": {
                          "type": "integer"
                        },
                        "createdAt": {
                          "type": "number",
                          "description": "Epoch ms, from tmux #{session_created}."
                        },
                        "lastActivity": {
                          "type": "number",
                          "description": "Epoch ms, from tmux #{session_activity}: when the session last saw input or output."
                        }
                      }
                    }
                  },
                  "readiness": {
                    "type": "object",
                    "description": "Whether this device can run loops, as its setup sees it. Stored apart from checks, so a heartbeat-only device never alerts; replaced whole each heartbeat. A device with runnerEnabled true that reports agentSignedIn false opens one sign-in alert in its alertChannel. A malformed block is refused.",
                    "properties": {
                      "runnerVersion": {
                        "type": "string",
                        "description": "The runner's version, e.g. 1.2.0. Below runner.minimumVersion the device's claims are refused. A runner that sends none is never refused (the legacy exemption)."
                      },
                      "runnerEnabled": {
                        "type": "boolean",
                        "description": "Run claiming is enabled on this device."
                      },
                      "agentCli": {
                        "type": "boolean",
                        "description": "The agent CLI is installed."
                      },
                      "agentSignedIn": {
                        "type": "boolean",
                        "description": "The agent CLI is signed in for background runs."
                      },
                      "tmux": {
                        "type": "boolean",
                        "description": "tmux is installed."
                      }
                    }
                  },
                  "laptop": {
                    "type": "boolean",
                    "description": "The device is a laptop: silent past three intervals it is asleep, not missed, and opens no heartbeat alert until the silence passes 24 hours. Kept when omitted."
                  },
                  "attachUrl": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "The device's own web terminal for its tmux sessions, as an https URL template; {session} marks where the session name goes, after the host. The device page opens it only when its host is a Tailscale name (*.ts.net) or one the owner approved, and falls back to an ssh command otherwise. Replaced whole each heartbeat; a malformed value is dropped."
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "success, created: false, device, settings, alertsOpened, alertsResolved. settings is { keepFinishedRunsDays, maxSessions, intervalSeconds }: keepFinishedRunsDays is how long the device's runner keeps a settled run's folder and tmux socket, in days (0 removes it at once, null keeps it; default 7), and maxSessions is how many runs the runner may hold at once, 1 to 8 (default 1), both chosen on the device page; intervalSeconds is the effective heartbeat interval, never below the 60-second floor, for the runner to adopt. relay is { fastIntervalSeconds: 3, quietCeilingSeconds: 15, recentChangeSeconds: 120 }: sync a live run's Session every fastIntervalSeconds while an ask is open, a reply is pending or the pane changed within recentChangeSeconds, else back off to at most quietCeilingSeconds. runner is { currentVersion, minimumVersion }, both null until MythOS serves a packaged runner; below the minimum the device lists updateRequired true and its claims are refused 403 RUNNER_UPDATE_REQUIRED. woke is true on a laptop's first beat after it slept."
          },
          "201": {
            "description": "The device's first heartbeat: its record was created."
          },
          "400": {
            "description": "INVALID_HEARTBEAT: malformed checks, intervalSeconds, alertChannel, readiness or laptop."
          },
          "401": {
            "description": "MISSING_CREDENTIAL or INVALID_CREDENTIAL."
          },
          "403": {
            "description": "SCOPE_REFUSED (not a device key) or DEVICE_ID_MISSING."
          },
          "404": {
            "description": "Not staff (Loops release gate)."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/devices/revoke": {
      "post": {
        "operationId": "revokeThisDevice",
        "summary": "A device revokes its own keys",
        "description": "Device-scoped key only. Revokes every live key of the calling device (its device key and any executor key under its id) and evicts them from this instance's identity cache; another instance may honour a revoked key for up to 60 seconds. The device stays listed as Revoked with its history. With forget: true the keys are revoked and evicted first, then the device and its history are deleted. Not behind the Loops gate: revoking only removes access.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "forget": {
                    "type": "boolean",
                    "description": "Also delete the device and its history (mythos-device uninstall --forget). Default false."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success, revoked (keys revoked), forgotten (the device was deleted)."
          },
          "400": {
            "description": "INVALID_BODY: forget is not true or false."
          },
          "401": {
            "description": "MISSING_CREDENTIAL or INVALID_CREDENTIAL."
          },
          "403": {
            "description": "SCOPE_REFUSED (not a device key) or DEVICE_ID_MISSING."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/devices": {
      "get": {
        "operationId": "listDevices",
        "summary": "The owner's devices and their health",
        "description": "Owner key. Each device carries status: healthy, failing (a check fails), missed (silent past three intervals; a laptop, past 24 hours as well), asleep (a laptop silent past three intervals but under 24 hours) or never (a live device key names it but it has not reported), and hasLiveKey. readiness is the latest heartbeat's readiness block or null, laptop its laptop flag, updateRequired true below the runner minimum (unknown when a minimum is set and the device reports no version), and takeUnpinnedLoops whether its runner is offered loops pinned to no device.",
        "responses": {
          "200": {
            "description": "success and devices."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate)."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/monitors": {
      "get": {
        "operationId": "listMonitors",
        "summary": "The owner's registered monitors and their liveness",
        "description": "Owner key. Each monitor carries status: healthy, running, never, failing, missed, silent or paused.",
        "responses": {
          "200": {
            "description": "success and monitors."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or not the caller's monitor."
          }
        }
      }
    },
    "/api/internal/monitors/{monitorId}": {
      "get": {
        "operationId": "getMonitor",
        "summary": "One registered monitor and its liveness",
        "description": "Owner key. Its declaration, last run, next due slot, open alerts, and status.",
        "parameters": [
          {
            "name": "monitorId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success and monitor."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or not the caller's monitor."
          }
        }
      },
      "put": {
        "operationId": "declareMonitor",
        "summary": "Declare when a monitor should run, so a missed or silent run alerts",
        "description": "Idempotent upsert, no lock token: the monitor re-declares itself as config and the last declaration stands. paused: true stops the watch and closes open missed and silent alerts; un-pausing restarts the clock.",
        "parameters": [
          {
            "name": "monitorId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "schedule"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "schedule": {
                    "type": "object",
                    "description": "Either { everySeconds } (60–604800) or { times: [\"HH:MM\"], days?: [\"mon\"…\"sun\"], timezone } — slots are computed in the monitor's IANA zone.",
                    "properties": {
                      "everySeconds": {
                        "type": "integer",
                        "minimum": 60,
                        "maximum": 604800
                      },
                      "times": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                        },
                        "maxItems": 48
                      },
                      "days": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "enum": [
                            "sun",
                            "mon",
                            "tue",
                            "wed",
                            "thu",
                            "fri",
                            "sat"
                          ]
                        }
                      },
                      "timezone": {
                        "type": "string"
                      }
                    }
                  },
                  "graceSeconds": {
                    "type": "integer",
                    "minimum": 60,
                    "maximum": 86400,
                    "default": 900
                  },
                  "maxRunSeconds": {
                    "type": "integer",
                    "minimum": 60,
                    "maximum": 86400,
                    "default": 3600
                  },
                  "alertChannel": {
                    "type": "string",
                    "default": "loop-health"
                  },
                  "deviceId": {
                    "type": "string"
                  },
                  "paused": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Re-declared: success, created false, monitor."
          },
          "201": {
            "description": "Declared: success, created true, monitor."
          },
          "400": {
            "description": "INVALID_MONITOR — a malformed schedule, grace, channel or id is refused, not dropped."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or not the caller's monitor."
          }
        }
      }
    },
    "/api/internal/monitors/{monitorId}/runs": {
      "post": {
        "operationId": "reportMonitorRun",
        "summary": "Report a monitor's run: started, succeeded or failed",
        "description": "A failed run opens a failed alert once (a repeat is absorbed); the next success resolves it. Any run resolves an open missed or silent alert. Not idempotent: a retried finish moves the last-run time.",
        "parameters": [
          {
            "name": "monitorId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "status"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "started",
                      "succeeded",
                      "failed"
                    ]
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "runKey": {
                    "type": "string",
                    "maxLength": 200
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success, monitor, alertsOpened, alertsResolved."
          },
          "400": {
            "description": "INVALID_RUN"
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "NOT_FOUND — declare the monitor first; also the release gate and another owner's monitor."
          }
        }
      }
    },
    "/api/internal/loop-sessions": {
      "get": {
        "operationId": "listLoopSessions",
        "summary": "The owner's agent sessions, newest activity first",
        "description": "Owner key. status filters to running, needs_you, waiting or ended; deviceId to one device's sessions; parentSessionId to one session's children. A device-scoped key lists its own device's sessions only, whatever deviceId names.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "running",
                "needs_you",
                "waiting",
                "ended"
              ]
            }
          },
          {
            "name": "deviceId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parentSessionId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success and sessions."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or not the caller's session."
          }
        }
      },
      "post": {
        "operationId": "startLoopSession",
        "summary": "Start a session record for a running agent",
        "description": "No lock token. Pass clientKey to make a retry safe: a repeat returns the first session with 200. A device-scoped key's session is stamped with its deviceId; a body naming another device, or null, is refused 400 INVALID_SESSION. parentSessionId starts it under one of the caller's own sessions (a device key: its own device's); any other id is refused 400 INVALID_SESSION, and the parent cannot change afterwards.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "origin": {
                    "type": "string",
                    "enum": [
                      "coding",
                      "runner",
                      "workflow_step",
                      "coordinator",
                      "kickoff"
                    ],
                    "description": "`coding` for a coding session. `kickoff` is a deprecated alias, stored as `coding`."
                  },
                  "parentSessionId": {
                    "type": "string",
                    "nullable": true,
                    "description": "Start under this session of the caller's; set at start only."
                  },
                  "compute": {
                    "type": "string",
                    "enum": [
                      "local_subscription",
                      "local_model",
                      "cloud"
                    ]
                  },
                  "deviceId": {
                    "type": "string",
                    "nullable": true
                  },
                  "repo": {
                    "type": "string",
                    "nullable": true
                  },
                  "branch": {
                    "type": "string",
                    "nullable": true
                  },
                  "worktree": {
                    "type": "string",
                    "nullable": true
                  },
                  "tmuxName": {
                    "type": "string",
                    "nullable": true
                  },
                  "model": {
                    "type": "string",
                    "nullable": true
                  },
                  "workflowMemoId": {
                    "type": "string",
                    "nullable": true
                  },
                  "step": {
                    "type": "string",
                    "nullable": true
                  },
                  "run": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "runId": {
                        "type": "string"
                      },
                      "stepId": {
                        "type": "string"
                      }
                    }
                  },
                  "pr": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "number": {
                        "type": "integer"
                      },
                      "url": {
                        "type": "string",
                        "description": "https only"
                      }
                    }
                  },
                  "tokens": {
                    "type": "integer",
                    "nullable": true
                  },
                  "progress": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "done": {
                        "type": "integer"
                      },
                      "total": {
                        "type": "integer"
                      }
                    }
                  },
                  "touched": {
                    "type": "array",
                    "maxItems": 30,
                    "items": {
                      "type": "string"
                    }
                  },
                  "clientKey": {
                    "type": "string",
                    "description": "Makes a retried start return the first session."
                  }
                },
                "required": [
                  "title",
                  "origin",
                  "compute"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "clientKey repeat: the existing session."
          },
          "201": {
            "description": "success, created, session."
          },
          "400": {
            "description": "INVALID_SESSION."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or not the caller's session."
          }
        }
      }
    },
    "/api/internal/loop-sessions/{sessionId}": {
      "get": {
        "operationId": "getLoopSession",
        "summary": "Read one session",
        "description": "Owner key. Its updatedAt is the lock token for update and end; another owner's session reads as 404. A device-scoped key reads its own device's sessions; any other reads as 404.",
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success and session (updatedAt is the lock token)."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or not the caller's session."
          }
        }
      },
      "patch": {
        "operationId": "updateLoopSession",
        "summary": "Set named session fields",
        "description": "REQUIRES expectedUpdatedAt. status moves only through asks and end; null clears a field. Owner key only; a device-scoped key is refused 403 SCOPE_REFUSED.",
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "deviceId": {
                    "type": "string",
                    "nullable": true
                  },
                  "repo": {
                    "type": "string",
                    "nullable": true
                  },
                  "branch": {
                    "type": "string",
                    "nullable": true
                  },
                  "worktree": {
                    "type": "string",
                    "nullable": true
                  },
                  "tmuxName": {
                    "type": "string",
                    "nullable": true
                  },
                  "model": {
                    "type": "string",
                    "nullable": true
                  },
                  "workflowMemoId": {
                    "type": "string",
                    "nullable": true
                  },
                  "step": {
                    "type": "string",
                    "nullable": true
                  },
                  "run": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "runId": {
                        "type": "string"
                      },
                      "stepId": {
                        "type": "string"
                      }
                    }
                  },
                  "pr": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "number": {
                        "type": "integer"
                      },
                      "url": {
                        "type": "string",
                        "description": "https only"
                      }
                    }
                  },
                  "tokens": {
                    "type": "integer",
                    "nullable": true
                  },
                  "progress": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "done": {
                        "type": "integer"
                      },
                      "total": {
                        "type": "integer"
                      }
                    }
                  },
                  "touched": {
                    "type": "array",
                    "maxItems": 30,
                    "items": {
                      "type": "string"
                    }
                  },
                  "expectedUpdatedAt": {
                    "type": "number"
                  }
                },
                "required": [
                  "expectedUpdatedAt"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success and session."
          },
          "400": {
            "description": "MISSING_LOCK_TOKEN or INVALID_SESSION."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or not the caller's session."
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt, or SESSION_ENDED."
          }
        }
      }
    },
    "/api/internal/loop-sessions/{sessionId}/end": {
      "post": {
        "operationId": "endLoopSession",
        "summary": "End a session with a result",
        "description": "REQUIRES expectedUpdatedAt. Open asks close with the session and a closing sys event is written. A device-scoped key ends its own device's sessions only; any other reads as 404. A run's Session ends when its run settles, and a device key's end before then is refused 409 RUN_NOT_SETTLED.",
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "expectedUpdatedAt",
                  "result"
                ],
                "properties": {
                  "expectedUpdatedAt": {
                    "type": "number"
                  },
                  "result": {
                    "type": "string",
                    "enum": [
                      "succeeded",
                      "failed",
                      "stopped",
                      "partial"
                    ]
                  },
                  "note": {
                    "type": "string"
                  },
                  "author": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "success and session."
          },
          "400": {
            "description": "MISSING_LOCK_TOKEN or INVALID_SESSION."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or not the caller's session."
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt, SESSION_ENDED, or RUN_NOT_SETTLED."
          }
        }
      }
    },
    "/api/internal/loop-sessions/{sessionId}/events": {
      "get": {
        "operationId": "listLoopSessionEvents",
        "summary": "The session summary transcript, oldest first, or one summary event's full record",
        "description": "Pass the returned cursor as after to read only what arrived since. Record lines (appended with record: true) never appear; records counts the record lines behind each returned event and recordTail those after the newest. With record=<eventId>, returns the record lines behind that summary event instead (record=tail: after the newest), with truncated. A device-scoped key reads its own device's sessions only; any other reads as 404.",
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "record",
            "in": "query",
            "description": "A summary event id, or tail: return the full-record lines behind it.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "success, events, cursor, records and recordTail; with record, success, events and truncated."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or not the caller's session."
          }
        }
      },
      "post": {
        "operationId": "appendLoopSessionEvent",
        "summary": "Append an agent's event to the transcript",
        "description": "No lock token; pass clientEventId to make a retry safe. ask and approve turn the session to needs_you. record: true appends a line of the full record (sys, prompt, agent or tool), kept out of the summary reads. An API key never writes a human event. A device-scoped key appends to its own device's sessions only; any other reads as 404.",
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kind"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "sys",
                      "prompt",
                      "agent",
                      "tool",
                      "ask",
                      "approve"
                    ]
                  },
                  "text": {
                    "type": "string",
                    "maxLength": 8000
                  },
                  "tool": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "arg": {
                        "type": "string"
                      },
                      "out": {
                        "type": "string"
                      }
                    }
                  },
                  "ask": {
                    "type": "object",
                    "properties": {
                      "question": {
                        "type": "string"
                      },
                      "options": {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 6,
                        "items": {
                          "type": "string"
                        }
                      },
                      "items": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "author": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      }
                    }
                  },
                  "record": {
                    "type": "boolean"
                  },
                  "clientEventId": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "clientEventId repeat: deduped."
          },
          "201": {
            "description": "success, deduped, event, session."
          },
          "400": {
            "description": "INVALID_EVENT."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff (Loops release gate), or not the caller's session."
          },
          "409": {
            "description": "SESSION_ENDED."
          }
        }
      }
    },
    "/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. A content write that would cut a body of 2,000+ characters to half or less returns 422 LARGE_DELETION and writes nothing; retry with confirmLargeDeletion: true to confirm it.",
        "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)."
                  },
                  "confirmLargeDeletion": {
                    "type": "boolean",
                    "description": "Pass true only after a 422 LARGE_DELETION, to confirm a content write that deletes most of the body."
                  }
                }
              },
              "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"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteMemos",
        "summary": "DELETE /api/internal/memos",
        "description": "Soft-delete a memo using required id and expectedUpdatedAt query parameters. Ownership or the appropriate grant capability and atomic CAS apply; returns success after moving to trash. Stale tokens return 409 with currentUpdatedAt.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expectedUpdatedAt",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/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"
                    },
                    "skipped": {
                      "type": "integer",
                      "description": "Memos skipped because their author cannot fund indexing or has not given AI consent"
                    }
                  }
                }
              }
            }
          },
          "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"
                    },
                    "skipped": {
                      "type": "integer",
                      "description": "Memos skipped because their author cannot fund indexing or has not given AI consent"
                    },
                    "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": "Visibility, steward name and image ceilings, a stable per-community pseudonym, and the resolved name and photo. Steward policy narrows rendering without changing stored member choices."
                    }
                  }
                }
              }
            }
          },
          "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`, `imageNameForm`, `photo`, and `imagePhoto` are member choices; steward ceilings narrow them at render time without changing stored values.",
        "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",
                      "pseudo",
                      "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."
                  },
                  "imageNameForm": {
                    "type": "string",
                    "enum": [
                      "full",
                      "initial",
                      "first",
                      "pseudo",
                      "anon"
                    ],
                    "description": "Name form for image-post bylines. Absent follows nameForm; a stable pseudonym carries through comment threads."
                  },
                  "imagePhoto": {
                    "type": "boolean",
                    "description": "Profile photo on image-post bylines. Absent follows photo; pseudonym and anonymous forms never show it."
                  },
                  "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"
                  },
                  "hideMemberNames": {
                    "type": "boolean",
                    "description": "Legacy all-anonymous steward override; applied at read time without changing member choices"
                  },
                  "memberIdentity": {
                    "type": "object",
                    "description": "Complete steward privacy policy. nameCeiling and imageCeiling each accept full, initial, first, pseudo, or anon; choices more identifying than the ceiling render at the ceiling without changing storage."
                  },
                  "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"
                    },
                    "holdFirstPost": {
                      "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; the four native kinds need membership of the community. No kind is tier-gated. 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"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "putJournal",
        "summary": "PUT /api/internal/journal",
        "description": "Replace a daily memo body. Query date (YYYY-MM-DD) is required; username targets a library. JSON content and expectedUpdatedAt are required; null token is supported only for first creation. Cross-library writes require the appropriate grant capability and dailyMemoAccess. Returns the daily memo with the advanced token.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {},
                  "expectedUpdatedAt": {},
                  "sourceMarkupVersion": {}
                }
              },
              "description": "Replace a daily memo body. Query date (YYYY-MM-DD) is required; username targets a library. JSON content and expectedUpdatedAt are required; null token is supported only for first creation. Cross-library writes require the appropriate grant capability and dailyMemoAccess. Returns the daily memo with the advanced token."
            }
          }
        }
      }
    },
    "/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."
                  },
                  "hostCommunity": {
                    "type": "boolean",
                    "description": "With host: false, show this community as host. Display only; omitted keeps the stored answer on edit."
                  },
                  "hostName": {
                    "type": "string",
                    "description": "With host: false, show an external host name. Omitted keeps the stored name; empty string clears it."
                  },
                  "hostUrl": {
                    "type": "string",
                    "description": "Optional absolute http(s) link for hostName. Omitted keeps the stored link; 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}": {
      "delete": {
        "tags": [
          "Communities"
        ],
        "summary": "Delete a community event and all dates",
        "description": "REQUIRES expectedUpdatedAt from a fresh event read. Host or community moderation role only. Removes the event from app reads while retaining a cancelled ICS record for subscribers. Series-only; use cancel for one date.",
        "operationId": "deleteCommunityEvent",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expectedUpdatedAt",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Event deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing/invalid token or occurrence-scoped request"
          },
          "403": {
            "description": "Caller is neither a host nor a community moderation role"
          },
          "404": {
            "description": "Event missing, deleted, or inaccessible"
          },
          "409": {
            "description": "STALE_LOCK_TOKEN; includes currentUpdatedAt"
          }
        }
      },
      "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."
                  },
                  "hostCommunity": {
                    "type": "boolean",
                    "description": "With host: false, show this community as host. Display only; omitted keeps the stored answer on edit."
                  },
                  "hostName": {
                    "type": "string",
                    "description": "With host: false, show an external host name. Omitted keeps the stored name; empty string clears it."
                  },
                  "hostUrl": {
                    "type": "string",
                    "description": "Optional absolute http(s) link for hostName. Omitted keeps the stored link; 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": "Edit a community post",
        "description": "REQUIRES expectedUpdatedAt. Send any of title+content, postTags, collectionId; only fields sent change. Authors edit title and content (text posts only) for 15 minutes after posting (EDIT_WINDOW_CLOSED after), and postTags and collectionId any time; a collection that would review or refuse the post returns COLLECTION_REVIEW_REQUIRED or COLLECTION_CLOSED. Community stewards edit every field of a post the community made as itself, indefinitely. Stewards cannot retag or rewrite a member's post. 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": [
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "content": {
                    "type": "string"
                  },
                  "postTags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Existing community topics; an empty array clears unless a topic is required"
                  },
                  "collectionId": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Collection id or slug; null unfiles unless a collection is required"
                  },
                  "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 — the submitter writes this field, or on a post the community made as itself, its creator and stewards; last write wins. 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"
          }
        }
      }
    },
    "/api/internal/loops/{memoId}/fields": {
      "patch": {
        "operationId": "patchLoopFields",
        "summary": "Rewrite Trigger or Policy field lines of an owned loop",
        "description": "Staff-only. The lossless field patcher: rewrites the named `- **Field** value` lines in one section and preserves every other byte of the body. A null value removes the field's line; an absent field is inserted after the section's last field. `Status` answers to a legacy `State` line and writes the current name. Owner only; grants and act-as do not reach it. Runs the same content pipeline as update_memo_section, with CAS on header.updatedAt. Pause: {section: \"Trigger\", fields: {Status: \"paused\"}}.",
        "parameters": [
          {
            "name": "memoId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Memo short id, compound id, or URL-encoded canonical /me/library/id."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "expectedUpdatedAt",
                  "section",
                  "fields"
                ],
                "properties": {
                  "expectedUpdatedAt": {
                    "type": "number",
                    "description": "updatedAt from read_memo or read_loop."
                  },
                  "section": {
                    "type": "string",
                    "enum": [
                      "Trigger",
                      "Policy"
                    ]
                  },
                  "fields": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string",
                      "nullable": true
                    },
                    "description": "Field name to a one-line value, or null to remove. 1 to 12 fields."
                  },
                  "acknowledgeUnlinked": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Written; returns the memo's new updatedAt like update_memo."
          },
          "400": {
            "description": "MISSING_LOCK_TOKEN, or a malformed section/fields body."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff, or no such loop owned by the caller."
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt."
          },
          "422": {
            "description": "SECTION_NOT_FOUND, SECTION_AMBIGUOUS, FIELD_AMBIGUOUS or INVALID_FIELD."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/loops/{memoId}/import": {
      "post": {
        "operationId": "importLoopMemo",
        "summary": "Import an owned loop memo",
        "description": "Staff-only loop association. Requires real memo ownership; grants do not confer ownership. Changes memoType and header.updatedAt with atomic CAS; preserves content and contentVersion. Read the memo first. Already associated/unassociated states are accepted with a fresh token. Another surface type is refused.",
        "parameters": [
          {
            "name": "memoId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Memo short id, compound id, or URL-encoded canonical /me/library/id."
          }
        ],
        "responses": {
          "200": {
            "description": "Association updated; returns success, memoId, memoType and updatedAt."
          },
          "400": {
            "description": "Missing or invalid expectedUpdatedAt."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff, or memo missing, trashed or not owned."
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt, or MEMO_TYPE_CONFLICT."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "expectedUpdatedAt": {
                    "type": "number",
                    "description": "updatedAt from read_memo; required and finite."
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "unimportLoopMemo",
        "summary": "Unimport an owned loop memo",
        "description": "Staff-only loop association. Requires real memo ownership; grants do not confer ownership. Changes memoType and header.updatedAt with atomic CAS; preserves content and contentVersion. Read the memo first. Already associated/unassociated states are accepted with a fresh token. Another surface type is refused.",
        "parameters": [
          {
            "name": "memoId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Memo short id, compound id, or URL-encoded canonical /me/library/id."
          },
          {
            "name": "expectedUpdatedAt",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "description": "updatedAt from read_memo; required and finite."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Association updated; returns success, memoId, memoType and updatedAt."
          },
          "400": {
            "description": "Missing or invalid expectedUpdatedAt."
          },
          "401": {
            "description": "Invalid API key."
          },
          "404": {
            "description": "Not staff, or memo missing, trashed or not owned."
          },
          "409": {
            "description": "STALE_LOCK_TOKEN with currentUpdatedAt, or MEMO_TYPE_CONFLICT."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/activity": {
      "get": {
        "operationId": "getActivity",
        "summary": "GET /api/internal/activity",
        "description": "Read library activity trends, top and rising memos. Optional username targets a granted library; content-scoped grants are refused. Returns activity with metric, trends, top and rising.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/augmentation": {
      "get": {
        "operationId": "getAugmentation",
        "summary": "GET /api/internal/augmentation",
        "description": "Load augmentation and contextStats for the caller or username library, and open a context session: the response's contextSessionId is the x-mythos-context value writes require for 24 hours. Cross-library access requires a grant; content-scoped grants are refused.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/collaborators": {
      "get": {
        "operationId": "getCollaborators",
        "summary": "GET /api/internal/collaborators",
        "description": "Read, invite or remove memo collaborators. GET requires memoId and optionally username; POST requires memoId and collaboratorInput; DELETE requires memoId and collaboratorUsername. Invites also check the caller plan. Ownership or a permitted library role is required; returns collaborator details or a mutation result.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "memoId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "post": {
        "operationId": "postCollaborators",
        "summary": "POST /api/internal/collaborators",
        "description": "Read, invite or remove memo collaborators. GET requires memoId and optionally username; POST requires memoId and collaboratorInput; DELETE requires memoId and collaboratorUsername. Invites also check the caller plan. Ownership or a permitted library role is required; returns collaborator details or a mutation result.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "memoId": {},
                  "collaboratorInput": {}
                }
              },
              "description": "Read, invite or remove memo collaborators. GET requires memoId and optionally username; POST requires memoId and collaboratorInput; DELETE requires memoId and collaboratorUsername. Invites also check the caller plan. Ownership or a permitted library role is required; returns collaborator details or a mutation result."
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteCollaborators",
        "summary": "DELETE /api/internal/collaborators",
        "description": "Read, invite or remove memo collaborators. GET requires memoId and optionally username; POST requires memoId and collaboratorInput; DELETE requires memoId and collaboratorUsername. Invites also check the caller plan. Ownership or a permitted library role is required; returns collaborator details or a mutation result.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "memoId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "collaboratorUsername",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/cron/backfill-descriptions": {
      "get": {
        "operationId": "getCronBackfillDescriptions",
        "summary": "GET /api/internal/cron/backfill-descriptions",
        "description": "Backfill descriptions for opted-in libraries using bounded owner BYOK batches. Service-only GET with side effects; requires x-internal-key. No account API key or request body.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/cron/cleanup-media-reservations": {
      "get": {
        "operationId": "getCronCleanupMediaReservations",
        "summary": "GET /api/internal/cron/cleanup-media-reservations",
        "description": "Expire abandoned media upload reservations and return the expired count. Service-only GET with side effects; requires x-internal-key. No account API key or request body.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/cron/cleanup-pending-subscriptions": {
      "get": {
        "operationId": "getCronCleanupPendingSubscriptions",
        "summary": "GET /api/internal/cron/cleanup-pending-subscriptions",
        "description": "Delete expired pending subscriptions and purge memos past their trash retention window; returns deleted and purgedTrash. Service-only GET with side effects; requires x-internal-key. No account API key or request body.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/cron/drain-email-queue": {
      "get": {
        "operationId": "getCronDrainEmailQueue",
        "summary": "GET /api/internal/cron/drain-email-queue",
        "description": "Drain a bounded batch from the outgoing email queue; returns send and failure counts. Service-only GET with side effects; requires x-internal-key. No account API key or request body.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/cron/gift-ttl-sweeper": {
      "get": {
        "operationId": "getCronGiftTtlSweeper",
        "summary": "GET /api/internal/cron/gift-ttl-sweeper",
        "description": "Clear stale non-claiming gift locks. Oracle gifts no longer expire; stuck claiming gifts require operator review. Service-only GET with side effects; requires x-internal-key. No account API key or request body.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/cron/notification-milestones": {
      "get": {
        "operationId": "getCronNotificationMilestones",
        "summary": "GET /api/internal/cron/notification-milestones",
        "description": "Sweep memo traffic milestones and emit deduplicated notifications; returns scanned and emitted. Service-only GET with side effects; requires x-internal-key. No account API key or request body.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/cron/reconcile-sitemap-shards": {
      "get": {
        "operationId": "getCronReconcileSitemapShards",
        "summary": "GET /api/internal/cron/reconcile-sitemap-shards",
        "description": "Reconcile the sitemap shard registry under its lease lock, outside crawler requests. Service-only GET with side effects; requires x-internal-key. No account API key or request body.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/cron/recover-stuck-imports": {
      "get": {
        "operationId": "getCronRecoverStuckImports",
        "summary": "GET /api/internal/cron/recover-stuck-imports",
        "description": "Recover stale subscriber-import jobs under a claim, resuming the runner or marking unrecoverable jobs failed. Service-only GET with side effects; requires x-internal-key. No account API key or request body.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/cron/run-loops": {
      "get": {
        "operationId": "getCronRunLoops",
        "summary": "GET /api/internal/cron/run-loops",
        "description": "Run the loop scheduler, recover stuck runs, resume approvals, poll sources and sweep external leases. Service-only GET with side effects; requires x-internal-key. No account API key or request body.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/cron/steward-queue-alerts": {
      "get": {
        "operationId": "getCronStewardQueueAlerts",
        "summary": "GET /api/internal/cron/steward-queue-alerts",
        "description": "Sweep waiting moderation queues and emit bounded steward notifications. Service-only GET with side effects; requires x-internal-key. No account API key or request body.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/cron/verify-promoted-sitemaps": {
      "get": {
        "operationId": "getCronVerifyPromotedSitemaps",
        "summary": "GET /api/internal/cron/verify-promoted-sitemaps",
        "description": "Verify promoted sitemap URLs and report per-URL checks. Failures are recorded in Sentry; HTTP 200 contains the actual verdict. Service-only GET with side effects; requires x-internal-key. No account API key or request body.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/drip/enroll": {
      "post": {
        "operationId": "postDripEnroll",
        "summary": "POST /api/internal/drip/enroll",
        "description": "Enroll a user in the onboarding drip. JSON uid and email are required. Uses the internal API-key guard; returns success.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "uid": {},
                  "email": {}
                }
              },
              "description": "Enroll a user in the onboarding drip. JSON uid and email are required. Uses the internal API-key guard; returns success."
            }
          }
        }
      }
    },
    "/api/internal/drip/process": {
      "post": {
        "operationId": "postDripProcess",
        "summary": "POST /api/internal/drip/process",
        "description": "Process queued onboarding drip work. Service-only: requires x-drip-secret, not an account API key. No request body required; returns success and worker data.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "DripWorkerKey": []
          }
        ]
      }
    },
    "/api/internal/events/{eventId}/cancel": {
      "post": {
        "operationId": "postEventsEventidCancel",
        "summary": "POST /api/internal/events/{eventId}/cancel",
        "description": "Cancel an event as its authorized creator. JSON expectedUpdatedAt is required; optional cancellation fields follow the creator-event contract. Returns the cancellation result or stale-token conflict.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/events/{eventId}/guest-link": {
      "get": {
        "operationId": "getEventsEventidGuestLink",
        "summary": "GET /api/internal/events/{eventId}/guest-link",
        "description": "Read, create/regenerate or revoke a creator event guest link. POST accepts regenerate. Uses creator-event authorization; returns the guest-link operation result. This existing route does not require expectedUpdatedAt.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "post": {
        "operationId": "postEventsEventidGuestLink",
        "summary": "POST /api/internal/events/{eventId}/guest-link",
        "description": "Read, create/regenerate or revoke a creator event guest link. POST accepts regenerate. Uses creator-event authorization; returns the guest-link operation result. This existing route does not require expectedUpdatedAt.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "delete": {
        "operationId": "deleteEventsEventidGuestLink",
        "summary": "DELETE /api/internal/events/{eventId}/guest-link",
        "description": "Read, create/regenerate or revoke a creator event guest link. POST accepts regenerate. Uses creator-event authorization; returns the guest-link operation result. This existing route does not require expectedUpdatedAt.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/events/{eventId}/guests": {
      "patch": {
        "operationId": "patchEventsEventidGuests",
        "summary": "PATCH /api/internal/events/{eventId}/guests",
        "description": "Decide an event guest request using the creator-event guest decision JSON contract. Creator authorization applies; returns the guest operation result. See docs/api/internal.md Decide a Guest.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/events/{eventId}": {
      "get": {
        "operationId": "getEventsEventid",
        "summary": "GET /api/internal/events/{eventId}",
        "description": "Read an event (optional occurrenceStart query), or edit it. PATCH requires expectedUpdatedAt from a fresh event read and permitted event fields. Creator-event permissions and CAS apply; returns the event operation result.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "occurrenceStart",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "patch": {
        "operationId": "patchEventsEventid",
        "summary": "PATCH /api/internal/events/{eventId}",
        "description": "Read an event (optional occurrenceStart query), or edit it. PATCH requires expectedUpdatedAt from a fresh event read and permitted event fields. Creator-event permissions and CAS apply; returns the event operation result.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/events/{eventId}/rsvp": {
      "post": {
        "operationId": "postEventsEventidRsvp",
        "summary": "POST /api/internal/events/{eventId}/rsvp",
        "description": "RSVP as the authenticated caller using the event RSVP JSON contract. Creator-event access applies; returns the RSVP operation result. See docs/api/internal.md RSVP to a Creator Event.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/events": {
      "get": {
        "operationId": "getEvents",
        "summary": "GET /api/internal/events",
        "description": "List creator events with required creator and optional year/month query parameters, or create an event from its JSON definition. Uses the creator-event agent guard and the existing creation entitlement. See docs/api/internal.md Creator Events for the complete event fields.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "creator",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "year",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "month",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "post": {
        "operationId": "postEvents",
        "summary": "POST /api/internal/events",
        "description": "List creator events with required creator and optional year/month query parameters, or create an event from its JSON definition. Uses the creator-event agent guard and the existing creation entitlement. See docs/api/internal.md Creator Events for the complete event fields.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/journal/line": {
      "patch": {
        "operationId": "patchJournalLine",
        "summary": "PATCH /api/internal/journal/line",
        "description": "Edit a single daily memo line. Query date is required; username is optional. JSON anchor, operation, content and expectedUpdatedAt are required; tags optional. Grant capability and dailyMemoAccess apply. Ambiguous or missing anchors are refused; stale tokens return 409.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "anchor": {},
                  "operation": {},
                  "content": {},
                  "tags": {},
                  "expectedUpdatedAt": {}
                }
              },
              "description": "Edit a single daily memo line. Query date is required; username is optional. JSON anchor, operation, content and expectedUpdatedAt are required; tags optional. Grant capability and dailyMemoAccess apply. Ambiguous or missing anchors are refused; stale tokens return 409."
            }
          }
        }
      }
    },
    "/api/internal/journal/section": {
      "patch": {
        "operationId": "patchJournalSection",
        "summary": "PATCH /api/internal/journal/section",
        "description": "Edit a daily memo section. Query date is required; username is optional. JSON heading, operation, content and expectedUpdatedAt are required; tags optional. Grant capability and dailyMemoAccess apply. Missing or ambiguous headings return 422; stale tokens return 409.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "heading": {},
                  "operation": {},
                  "content": {},
                  "tags": {},
                  "expectedUpdatedAt": {}
                }
              },
              "description": "Edit a daily memo section. Query date is required; username is optional. JSON heading, operation, content and expectedUpdatedAt are required; tags optional. Grant capability and dailyMemoAccess apply. Missing or ambiguous headings return 422; stale tokens return 409."
            }
          }
        }
      }
    },
    "/api/internal/memos/{id}/related": {
      "get": {
        "operationId": "getMemosIdRelated",
        "summary": "GET /api/internal/memos/{id}/related",
        "description": "Traverse related memos for id through accessible graph edges. Optional depth, direction, includeContext, limit, linkType, tags and tagMode narrow the traversal. Grants and content scope apply; returns related memo context.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "depth",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeContext",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "linkType",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tags",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tagMode",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/memos/{id}/section": {
      "get": {
        "operationId": "getMemosIdSection",
        "summary": "GET /api/internal/memos/{id}/section",
        "description": "Read a memo section by heading or index query parameter. Access is checked against the source memo and library scope. Returns section content and metadata; this is a read-only endpoint.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "heading",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "index",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/memos/{id}/suggestions/{suggestionId}": {
      "patch": {
        "operationId": "patchMemosIdSuggestionsSuggestionid",
        "summary": "PATCH /api/internal/memos/{id}/suggestions/{suggestionId}",
        "description": "Review a suggestion with JSON status. Source memo ownership or edit-capable library access is required; returns success and status.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "suggestionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {}
                }
              },
              "description": "Review a suggestion with JSON status. Source memo ownership or edit-capable library access is required; returns success and status."
            }
          }
        }
      }
    },
    "/api/internal/memos/{id}/suggestions": {
      "post": {
        "operationId": "postMemosIdSuggestions",
        "summary": "POST /api/internal/memos/{id}/suggestions",
        "description": "Create an agent suggestion with JSON type, payload and agentId, or list suggestions with optional status. Source memo access and the relevant read/edit grant capability are enforced.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "201": {
            "description": "Created."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {},
                  "payload": {},
                  "agentId": {}
                }
              },
              "description": "Create an agent suggestion with JSON type, payload and agentId, or list suggestions with optional status. Source memo access and the relevant read/edit grant capability are enforced."
            }
          }
        }
      },
      "get": {
        "operationId": "getMemosIdSuggestions",
        "summary": "GET /api/internal/memos/{id}/suggestions",
        "description": "Create an agent suggestion with JSON type, payload and agentId, or list suggestions with optional status. Source memo access and the relevant read/edit grant capability are enforced.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/memos/backlinks": {
      "get": {
        "operationId": "getMemosBacklinks",
        "summary": "GET /api/internal/memos/backlinks",
        "description": "List memos and daily memos mentioning the supplied id query parameter; limit is optional. Source memo library authorization and content scope apply. Returns lean backlink records.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/memos/discover": {
      "get": {
        "operationId": "getMemosDiscover",
        "summary": "GET /api/internal/memos/discover",
        "description": "Search platform-public memos by title or tag with q and optional limit. Empty query returns no rows. Applies the public substance threshold; does not grant access to private libraries.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/memos/history/restore": {
      "post": {
        "operationId": "restoreMemoVersion",
        "summary": "POST /api/internal/memos/history/restore",
        "description": "Restore a memo to one of its version snapshots with JSON memoId, versionId and expectedUpdatedAt; username optional. Only the fields that differ are written, as one CAS write through the shared content core, and a fresh version row keeps the replaced state. Link lint only suggests. Owner or a grant that can edit, inside its content scope; community-owned and daily memos are refused. Returns restored:false when the memo already matches the version.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "409": {
            "description": "STALE_LOCK_TOKEN: the memo changed since it was read; the response carries currentUpdatedAt."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "memoId",
                  "versionId",
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "memoId": {
                    "type": "string"
                  },
                  "versionId": {
                    "type": "string"
                  },
                  "expectedUpdatedAt": {
                    "type": "number"
                  },
                  "username": {
                    "type": "string"
                  }
                }
              },
              "description": "Restore a memo to one of its version snapshots with JSON memoId, versionId and expectedUpdatedAt; username optional. Only the fields that differ are written, as one CAS write through the shared content core, and a fresh version row keeps the replaced state. Link lint only suggests. Owner or a grant that can edit, inside its content scope; community-owned and daily memos are refused. Returns restored:false when the memo already matches the version."
            }
          }
        }
      }
    },
    "/api/internal/memos/trash": {
      "get": {
        "operationId": "listTrashedMemos",
        "summary": "GET /api/internal/memos/trash",
        "description": "List the library's trash newest-first with optional username and limit (default 50, max 100). Each row carries id, path, title, trashedAt, purgeAfter (30 days after trashing) and updatedAt, the token a restore needs. Owner, or a grant that can read, within its content scope.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ]
      },
      "post": {
        "operationId": "restoreTrashedMemo",
        "summary": "POST /api/internal/memos/trash",
        "description": "Restore one trashed memo with JSON memoId and expectedUpdatedAt from the trash listing; username optional. The compare-and-swap rides the write filter. Owner, or a grant that can delete, within its content scope; a memo outside the caller's reach answers 404.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "409": {
            "description": "STALE_LOCK_TOKEN: the memo changed since it was read; the response carries currentUpdatedAt."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "memoId",
                  "expectedUpdatedAt"
                ],
                "properties": {
                  "memoId": {
                    "type": "string"
                  },
                  "expectedUpdatedAt": {
                    "type": "number"
                  },
                  "username": {
                    "type": "string"
                  }
                }
              },
              "description": "Restore one trashed memo with JSON memoId and expectedUpdatedAt from the trash listing; username optional. The compare-and-swap rides the write filter. Owner, or a grant that can delete, within its content scope; a memo outside the caller's reach answers 404."
            }
          }
        }
      }
    },
    "/api/internal/memos/history": {
      "get": {
        "operationId": "getMemosHistory",
        "summary": "GET /api/internal/memos/history",
        "description": "List memo version history with required memoId, optional username, limit and before, or read a single version with versionId. Source memo or daily-memo permissions apply; returns version snapshots or paginated history.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "memoId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "before",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "versionId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/memos/line": {
      "patch": {
        "operationId": "patchMemosLine",
        "summary": "PATCH /api/internal/memos/line",
        "description": "Edit one memo line using query id and JSON zone, anchor, operation, content, expectedUpdatedAt; tags and acknowledgeUnlinked are optional. Applies grant capability, atomic CAS and link lint. Body writes also honor the live editing room gate. A body write that deletes most of the memo returns 422 LARGE_DELETION unless confirmLargeDeletion is true.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "zone": {},
                  "anchor": {},
                  "operation": {},
                  "content": {},
                  "tags": {},
                  "expectedUpdatedAt": {},
                  "acknowledgeUnlinked": {},
                  "confirmLargeDeletion": {}
                }
              },
              "description": "Edit one memo line using query id and JSON zone, anchor, operation, content, expectedUpdatedAt; tags and acknowledgeUnlinked are optional. Applies grant capability, atomic CAS and link lint. Body writes also honor the live editing room gate. A body write that deletes most of the memo returns 422 LARGE_DELETION unless confirmLargeDeletion is true."
            }
          }
        }
      }
    },
    "/api/internal/memos/move": {
      "post": {
        "operationId": "postMemosMove",
        "summary": "POST /api/internal/memos/move",
        "description": "Relocate a memo between owned or authorized libraries. JSON memoId and destinationLibrary are required; addAsCollaborator, createRedirect and dryRun are optional. Real moves require expectedUpdatedAt; dryRun does not write. Returns relocation details including embedding outcome.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "memoId": {},
                  "destinationLibrary": {},
                  "addAsCollaborator": {},
                  "createRedirect": {},
                  "dryRun": {},
                  "expectedUpdatedAt": {}
                }
              },
              "description": "Relocate a memo between owned or authorized libraries. JSON memoId and destinationLibrary are required; addAsCollaborator, createRedirect and dryRun are optional. Real moves require expectedUpdatedAt; dryRun does not write. Returns relocation details including embedding outcome."
            }
          }
        }
      }
    },
    "/api/internal/memos/rebuild-links": {
      "post": {
        "operationId": "postMemosRebuildLinks",
        "summary": "POST /api/internal/memos/rebuild-links",
        "description": "Rebuild the graph link index for username (or the configured default library). Cross-library use requires a grant. Returns rebuild counts; this is a maintenance write, not a content edit.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/memos/reindex": {
      "post": {
        "operationId": "postMemosReindex",
        "summary": "POST /api/internal/memos/reindex",
        "description": "Scan or rebuild missing embeddings in the caller library. JSON dryRun, limit and includeDailies are optional. Uses the owner embedding provider; dryRun reports cost estimates without embedding. Returns batch progress or NO_EMBEDDING_PROVIDER.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "dryRun": {},
                  "limit": {},
                  "includeDailies": {}
                }
              },
              "description": "Scan or rebuild missing embeddings in the caller library. JSON dryRun, limit and includeDailies are optional. Uses the owner embedding provider; dryRun reports cost estimates without embedding. Returns batch progress or NO_EMBEDDING_PROVIDER."
            }
          }
        }
      }
    },
    "/api/internal/memos/section": {
      "patch": {
        "operationId": "patchMemosSection",
        "summary": "PATCH /api/internal/memos/section",
        "description": "Edit one memo section using query id and JSON zone, heading, operation, content and expectedUpdatedAt; tags and acknowledgeUnlinked optional. Applies grant capability, CAS and link lint. Body writes honor live editing rooms. Missing or ambiguous headings return 422. A body write that deletes most of the memo returns 422 LARGE_DELETION unless confirmLargeDeletion is true.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "zone": {},
                  "heading": {},
                  "operation": {},
                  "content": {},
                  "tags": {},
                  "expectedUpdatedAt": {},
                  "acknowledgeUnlinked": {},
                  "confirmLargeDeletion": {}
                }
              },
              "description": "Edit one memo section using query id and JSON zone, heading, operation, content and expectedUpdatedAt; tags and acknowledgeUnlinked optional. Applies grant capability, CAS and link lint. Body writes honor live editing rooms. Missing or ambiguous headings return 422. A body write that deletes most of the memo returns 422 LARGE_DELETION unless confirmLargeDeletion is true."
            }
          }
        }
      }
    },
    "/api/internal/memos/semantic-search": {
      "get": {
        "operationId": "getMemosSemanticSearch",
        "summary": "GET /api/internal/memos/semantic-search",
        "description": "Semantic search within the caller or username library. Requires q; topK, tags, tagMode and includeDailies optional. Two or more tags require explicit tagMode. Grants are resolved before search and content scope is intersected after retrieval. Daily memo search is owner-only. Returns scored memos with chunks.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "topK",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tags",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tagMode",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "includeDailies",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/newsletter/broadcast/send": {
      "post": {
        "operationId": "postNewsletterBroadcastSend",
        "summary": "POST /api/internal/newsletter/broadcast/send",
        "description": "Service-only broadcast delivery, not an owner API-key endpoint. JSON broadcastId is required. Refuses an already-sent broadcast; enforces the creator monthly limit and sends to its stored segment. Returns recipientCount. The service secret may be carried in x-internal-key, x-mythos-key or Bearer.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "broadcastId": {}
                }
              },
              "description": "Service-only broadcast delivery, not an owner API-key endpoint. JSON broadcastId is required. Refuses an already-sent broadcast; enforces the creator monthly limit and sends to its stored segment. Returns recipientCount. The service secret may be carried in x-internal-key, x-mythos-key or Bearer."
            }
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/newsletter/digest": {
      "post": {
        "operationId": "postNewsletterDigest",
        "summary": "POST /api/internal/newsletter/digest",
        "description": "Service-only digest worker. Optional JSON type is daily, weekly or monthly (default daily). Queues eligible subscriber digests and returns counts. The service secret may be carried in x-internal-key, x-mythos-key or Bearer; account API keys are not accepted.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {}
                }
              },
              "description": "Service-only digest worker. Optional JSON type is daily, weekly or monthly (default daily). Queues eligible subscriber digests and returns counts. The service secret may be carried in x-internal-key, x-mythos-key or Bearer; account API keys are not accepted."
            }
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/newsletter/send": {
      "post": {
        "operationId": "postNewsletterSend",
        "summary": "POST /api/internal/newsletter/send",
        "description": "Service-only memo update delivery. JSON memoId and creatorUid are required. Non-public memos, unmatched topics and ineligible recipients are skipped. Returns delivery counts. The service secret may be carried in x-internal-key, x-mythos-key or Bearer; account API keys are not accepted.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "memoId": {},
                  "creatorUid": {}
                }
              },
              "description": "Service-only memo update delivery. JSON memoId and creatorUid are required. Non-public memos, unmatched topics and ineligible recipients are skipped. Returns delivery counts. The service secret may be carried in x-internal-key, x-mythos-key or Bearer; account API keys are not accepted."
            }
          }
        },
        "security": [
          {
            "InternalServiceKey": []
          }
        ]
      }
    },
    "/api/internal/newsletter/subscribers/bulk/{jobId}/error-csv": {
      "get": {
        "operationId": "getNewsletterSubscribersBulkJobidErrorCsv",
        "summary": "GET /api/internal/newsletter/subscribers/bulk/{jobId}/error-csv",
        "description": "Download invalid, duplicate and failed rows of an owned validated import as text/csv using a session JWT. Missing or unvalidated jobs return 404.",
        "responses": {
          "200": {
            "description": "Error rows CSV.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "SessionBearer": []
          }
        ]
      }
    },
    "/api/internal/newsletter/subscribers/bulk/{jobId}/import": {
      "post": {
        "operationId": "postNewsletterSubscribersBulkJobidImport",
        "summary": "POST /api/internal/newsletter/subscribers/bulk/{jobId}/import",
        "description": "Import an owned validated CSV job using a session JWT. Requires creator newsletter access; atomically claims the validated job, then runs the import. Returns completed counts or 503 when paused for watchdog recovery. No request body required.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "SessionBearer": []
          }
        ]
      }
    },
    "/api/internal/newsletter/subscribers/bulk/{jobId}/mapping": {
      "post": {
        "operationId": "postNewsletterSubscribersBulkJobidMapping",
        "summary": "POST /api/internal/newsletter/subscribers/bulk/{jobId}/mapping",
        "description": "Set column mapping for an owned CSV import job using a session JWT. JSON email mapping is required; firstName, lastName and topics are optional. Accepts uploaded, mapped, configured or validated jobs; returns discovered topic values.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {},
                  "firstName": {},
                  "lastName": {},
                  "topics": {}
                }
              },
              "description": "Set column mapping for an owned CSV import job using a session JWT. JSON email mapping is required; firstName, lastName and topics are optional. Accepts uploaded, mapped, configured or validated jobs; returns discovered topic values."
            }
          }
        },
        "security": [
          {
            "SessionBearer": []
          }
        ]
      }
    },
    "/api/internal/newsletter/subscribers/bulk/{jobId}/settings": {
      "post": {
        "operationId": "postNewsletterSubscribersBulkJobidSettings",
        "summary": "POST /api/internal/newsletter/subscribers/bulk/{jobId}/settings",
        "description": "Configure an owned mapped CSV job using a session JWT. Requires defaultScope, frequency and digestMode; accepts defaultTopics, bypassDoubleOptIn, skipWelcomeEmail, topicResolutions and newTopics. Returns topic resolutions and advances the job to configured.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "defaultScope": {},
                  "defaultTopics": {},
                  "frequency": {},
                  "digestMode": {},
                  "bypassDoubleOptIn": {},
                  "skipWelcomeEmail": {},
                  "topicResolutions": {},
                  "newTopics": {}
                }
              },
              "description": "Configure an owned mapped CSV job using a session JWT. Requires defaultScope, frequency and digestMode; accepts defaultTopics, bypassDoubleOptIn, skipWelcomeEmail, topicResolutions and newTopics. Returns topic resolutions and advances the job to configured."
            }
          }
        },
        "security": [
          {
            "SessionBearer": []
          }
        ]
      }
    },
    "/api/internal/newsletter/subscribers/bulk/{jobId}/status": {
      "get": {
        "operationId": "getNewsletterSubscribersBulkJobidStatus",
        "summary": "GET /api/internal/newsletter/subscribers/bulk/{jobId}/status",
        "description": "Read an owned CSV import job using a session JWT. Returns status, validation, result, cursor and configuration/preview metadata. Missing or unowned jobs return 404.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "SessionBearer": []
          }
        ]
      }
    },
    "/api/internal/newsletter/subscribers/bulk/{jobId}/validate": {
      "post": {
        "operationId": "postNewsletterSubscribersBulkJobidValidate",
        "summary": "POST /api/internal/newsletter/subscribers/bulk/{jobId}/validate",
        "description": "Validate an owned configured CSV job using a session JWT. Requires stored mapping/settings and creator newsletter access. Performs address and duplicate checks, stores validated rows and returns validation counts. No request body required.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "SessionBearer": []
          }
        ]
      }
    },
    "/api/internal/newsletter/subscribers/bulk/upload": {
      "post": {
        "operationId": "postNewsletterSubscribersBulkUpload",
        "summary": "POST /api/internal/newsletter/subscribers/bulk/upload",
        "description": "Upload a CSV as multipart file using the creator session JWT. Maximum 5000 rows and a daily upload quota for non-admins. Stores a bulk import job and returns jobId, headers and preview information. Account API keys do not authenticate this browser route.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "SessionBearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/internal/sessions": {
      "post": {
        "operationId": "postSessions",
        "summary": "POST /api/internal/sessions",
        "description": "POST starts an agent session with required JSON agentId and purpose; returns sessionId with 201. GET lists the latest 50 sessions and requires a real admin via API key or session JWT.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "201": {
            "description": "Created."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "agentId": {},
                  "purpose": {}
                }
              },
              "description": "POST starts an agent session with required JSON agentId and purpose; returns sessionId with 201. GET lists the latest 50 sessions and requires a real admin via API key or session JWT."
            }
          }
        }
      },
      "get": {
        "operationId": "getSessions",
        "summary": "GET /api/internal/sessions",
        "description": "POST starts an agent session with required JSON agentId and purpose; returns sessionId with 201. GET lists the latest 50 sessions and requires a real admin via API key or session JWT.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionBearer": []
          }
        ]
      }
    },
    "/api/internal/tag-metadata": {
      "get": {
        "operationId": "getTagMetadata",
        "summary": "GET /api/internal/tag-metadata",
        "description": "Read all tag metadata for the authenticated account. Returns metadata; does not accept a target username.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      }
    },
    "/api/internal/tags/{tag}": {
      "put": {
        "operationId": "putTagsTag",
        "summary": "PUT /api/internal/tags/{tag}",
        "description": "GET returns public memos for the tag plus authenticated-account metadata. PUT and its PATCH alias update description (max 200 chars), displayName, explainerMemoId or generated. Optional username checks grant tags capability and content scope; metadata is stored under the caller uid. Explainer must be owned by the caller.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "tag",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "description": {},
                  "displayName": {},
                  "explainerMemoId": {},
                  "generated": {}
                }
              },
              "description": "GET returns public memos for the tag plus authenticated-account metadata. PUT and its PATCH alias update description (max 200 chars), displayName, explainerMemoId or generated. Optional username checks grant tags capability and content scope; metadata is stored under the caller uid. Explainer must be owned by the caller."
            }
          }
        }
      },
      "patch": {
        "operationId": "patchTagsTag",
        "summary": "PATCH /api/internal/tags/{tag}",
        "description": "GET returns public memos for the tag plus authenticated-account metadata. PUT and its PATCH alias update description (max 200 chars), displayName, explainerMemoId or generated. Optional username checks grant tags capability and content scope; metadata is stored under the caller uid. Explainer must be owned by the caller.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "tag",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "description": {},
                  "displayName": {},
                  "explainerMemoId": {},
                  "generated": {}
                }
              },
              "description": "GET returns public memos for the tag plus authenticated-account metadata. PUT and its PATCH alias update description (max 200 chars), displayName, explainerMemoId or generated. Optional username checks grant tags capability and content scope; metadata is stored under the caller uid. Explainer must be owned by the caller."
            }
          }
        }
      },
      "get": {
        "operationId": "getTagsTag",
        "summary": "GET /api/internal/tags/{tag}",
        "description": "GET returns public memos for the tag plus authenticated-account metadata. PUT and its PATCH alias update description (max 200 chars), displayName, explainerMemoId or generated. Optional username checks grant tags capability and content scope; metadata is stored under the caller uid. Explainer must be owned by the caller.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "tag",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/tags": {
      "get": {
        "operationId": "getTags",
        "summary": "GET /api/internal/tags",
        "description": "GET enumerates target library tags. POST renames (action, oldName, newName) or merges (action, sourceTag, targetTag) with optional username and dryRun. Real rename/merge requires expectedUpdatedAt; dryRun is read-only. Applies library authorization and tags capability.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "post": {
        "operationId": "postTags",
        "summary": "POST /api/internal/tags",
        "description": "GET enumerates target library tags. POST renames (action, oldName, newName) or merges (action, sourceTag, targetTag) with optional username and dryRun. Real rename/merge requires expectedUpdatedAt; dryRun is read-only. Applies library authorization and tags capability.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/tasks": {
      "get": {
        "operationId": "getTasks",
        "summary": "GET /api/internal/tasks",
        "description": "List tasks with optional username, completed, limit and sort; POST creates a task with required text and optional notes, dueDate, dueTime, repeat and username. PATCH requires query id and JSON expectedUpdatedAt, and accepts completed or editable task fields. Standalone tasks support CAS; embedded updates return EMBEDDED_TASK_LOCK_DEFERRED.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "completed",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "post": {
        "operationId": "postTasks",
        "summary": "POST /api/internal/tasks",
        "description": "List tasks with optional username, completed, limit and sort; POST creates a task with required text and optional notes, dueDate, dueTime, repeat and username. PATCH requires query id and JSON expectedUpdatedAt, and accepts completed or editable task fields. Standalone tasks support CAS; embedded updates return EMBEDDED_TASK_LOCK_DEFERRED.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "201": {
            "description": "Created."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      },
      "patch": {
        "operationId": "patchTasks",
        "summary": "PATCH /api/internal/tasks",
        "description": "List tasks with optional username, completed, limit and sort; POST creates a task with required text and optional notes, dueDate, dueTime, repeat and username. PATCH requires query id and JSON expectedUpdatedAt, and accepts completed or editable task fields. Standalone tasks support CAS; embedded updates return EMBEDDED_TASK_LOCK_DEFERRED.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/templates": {
      "get": {
        "operationId": "getTemplates",
        "summary": "GET /api/internal/templates",
        "description": "Read templates using optional username, id or name, type, kind and default filters. POST requires name and content for template kind. PATCH uses query id and JSON expectedUpdatedAt plus changed fields; DELETE uses query id and expectedUpdatedAt. Grants and templates capability apply; system templates cannot be deleted.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "default",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "post": {
        "operationId": "postTemplates",
        "summary": "POST /api/internal/templates",
        "description": "Read templates using optional username, id or name, type, kind and default filters. POST requires name and content for template kind. PATCH uses query id and JSON expectedUpdatedAt plus changed fields; DELETE uses query id and expectedUpdatedAt. Grants and templates capability apply; system templates cannot be deleted.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "201": {
            "description": "Created."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      },
      "patch": {
        "operationId": "patchTemplates",
        "summary": "PATCH /api/internal/templates",
        "description": "Read templates using optional username, id or name, type, kind and default filters. POST requires name and content for template kind. PATCH uses query id and JSON expectedUpdatedAt plus changed fields; DELETE uses query id and expectedUpdatedAt. Grants and templates capability apply; system templates cannot be deleted.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "delete": {
        "operationId": "deleteTemplates",
        "summary": "DELETE /api/internal/templates",
        "description": "Read templates using optional username, id or name, type, kind and default filters. POST requires name and content for template kind. PATCH uses query id and JSON expectedUpdatedAt plus changed fields; DELETE uses query id and expectedUpdatedAt. Grants and templates capability apply; system templates cannot be deleted.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "expectedUpdatedAt",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/internal/username-redirect": {
      "get": {
        "operationId": "getUsernameRedirect",
        "summary": "GET /api/internal/username-redirect",
        "description": "Public lookup of a previous username. Optional username query; returns redirect false, or redirect true with newUsername. Does not require an API key.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": []
      }
    },
    "/api/internal/username-status": {
      "get": {
        "operationId": "getUsernameStatus",
        "summary": "GET /api/internal/username-status",
        "description": "Public, IP-rate-limited username state lookup. Optional username query; missing input returns absent. Returns the username state without requiring an API key.",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": []
      }
    },
    "/api/internal/webhooks": {
      "post": {
        "operationId": "postWebhooks",
        "summary": "POST /api/internal/webhooks",
        "description": "POST registers an owner webhook with JSON url (public HTTPS), events (non-empty supported event list) and secret; returns webhookId with 201. GET lists sanitized registrations and DELETE takes query id. Reads/deletes are owner-scoped for API keys, or platform-wide for real admins (API key or session JWT).",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "201": {
            "description": "Created."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        }
      },
      "get": {
        "operationId": "getWebhooks",
        "summary": "GET /api/internal/webhooks",
        "description": "POST registers an owner webhook with JSON url (public HTTPS), events (non-empty supported event list) and secret; returns webhookId with 201. GET lists sanitized registrations and DELETE takes query id. Reads/deletes are owner-scoped for API keys, or platform-wide for real admins (API key or session JWT).",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionBearer": []
          }
        ]
      },
      "delete": {
        "operationId": "deleteWebhooks",
        "summary": "DELETE /api/internal/webhooks",
        "description": "POST registers an owner webhook with JSON url (public HTTPS), events (non-empty supported event list) and secret; returns webhookId with 201. GET lists sanitized registrations and DELETE takes query id. Reads/deletes are owner-scoped for API keys, or platform-wide for real admins (API key or session JWT).",
        "responses": {
          "200": {
            "description": "Successful operation; response fields are described above."
          },
          "400": {
            "description": "Invalid request."
          },
          "401": {
            "description": "Missing or invalid authentication."
          },
          "403": {
            "description": "Insufficient permission."
          },
          "404": {
            "description": "Resource not found or inaccessible."
          },
          "429": {
            "description": "Rate limited."
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionBearer": []
          }
        ]
      }
    }
  }
}