{
  "openapi": "3.0.3",
  "info": {
    "title": "Voila API",
    "version": "0.1.0",
    "description": "Public API for uploading, sharing, and reviewing agent-created videos. Authenticate API requests with a Voila bearer token. The current stable namespace is /api; breaking changes will use an explicit versioned namespace such as /api/v2, while additive changes remain backward compatible. See https://voila.show/auth.md and https://voila.show/developers for guidance.",
    "x-api-versioning": {
      "strategy": "path",
      "current": "/api",
      "next-breaking": "/api/v2",
      "compatibility": "Additive fields and endpoints remain backward compatible; breaking changes use a versioned path."
    }
  },
  "servers": [
    {
      "url": "https://voila.show",
      "description": "Voila production API"
    }
  ],
  "security": [],
  "paths": {
    "/api/uploads": {
      "post": {
        "operationId": "createUpload",
        "summary": "Create a video upload URL",
        "description": "Requires an account API bearer token or browser session. Browser mutations require X-CSRF-Token. Upload IDs remain Mux IDs. Status is accessible only to the owner.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUploadRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Owned upload and grants saved. Delivery warnings do not invalidate the upload.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                }
              }
            }
          },
          "502": {
            "description": "Mux request failed or returned an invalid response.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Mux credentials are not configured.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Invalid CSRF token",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid title, access, invitations, or revised_from UUID",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Source video missing or not owned",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ]
      }
    },
    "/api/uploads/{id}": {
      "get": {
        "operationId": "getUpload",
        "summary": "Get owned upload status and viewing link",
        "description": "Requires an account API bearer token or browser session. Browser mutations require X-CSRF-Token. Upload IDs remain Mux IDs. Status is accessible only to the owner.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "description": "Mux direct upload ID."
          }
        ],
        "responses": {
          "200": {
            "description": "Persisted upload processing state.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UploadStatus"
                }
              }
            }
          },
          "404": {
            "description": "Upload not found.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Mux lookup failed (including IDs rejected by Mux as invalid or belonging to another environment), returned invalid data, or referenced an unavailable asset.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Mux credentials are not configured.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ]
      }
    },
    "/api/auth/session": {
      "get": {
        "operationId": "getAuthSession",
        "summary": "Current browser session and CSRF token",
        "description": "Browser cookie authentication. Fetch GET /api/auth/session first, retain cookies, and send its csrf_token as X-CSRF-Token for mutations. Authentication tokens are never returned in JSON. Session and CSRF state may rotate after login/logout. ",
        "responses": {
          "200": {
            "description": "Current browser session and CSRF token",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthSession"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createAuthSession",
        "summary": "Sign in with a magic link or email and password",
        "description": "Browser cookie authentication. Fetch GET /api/auth/session first, retain cookies, and send its csrf_token as X-CSRF-Token for mutations. Authentication tokens are never returned in JSON. Session and CSRF state may rotate after login/logout. ",
        "responses": {
          "200": {
            "description": "Sign in with a magic link or email and password",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthSession"
                }
              }
            }
          },
          "403": {
            "description": "Invalid CSRF token, or recent authentication required for password changes",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid request or field validation failed",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "429": {
            "description": "Attempt limit exceeded",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until retry"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials/link or authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-CSRF-Token",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSessionRequest"
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteAuthSession",
        "summary": "Revoke current session and clear cookies",
        "description": "Browser cookie authentication. Fetch GET /api/auth/session first, retain cookies, and send its csrf_token as X-CSRF-Token for mutations. Authentication tokens are never returned in JSON. Session and CSRF state may rotate after login/logout. ",
        "responses": {
          "200": {
            "description": "Revoke current session and clear cookies",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthSession"
                }
              }
            }
          },
          "403": {
            "description": "Invalid CSRF token, or recent authentication required for password changes",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-CSRF-Token",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/auth/register": {
      "post": {
        "operationId": "registerAccount",
        "summary": "Register by email and send a confirmation link",
        "description": "Browser cookie authentication. Fetch GET /api/auth/session first, retain cookies, and send its csrf_token as X-CSRF-Token for mutations. Authentication tokens are never returned in JSON. Session and CSRF state may rotate after login/logout. Email requests acknowledge known and unknown addresses identically; registration can resend to an existing account. No session is created until a link is consumed.",
        "responses": {
          "202": {
            "description": "Register by email and send a confirmation link",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthAcknowledgment"
                }
              }
            }
          },
          "403": {
            "description": "Invalid CSRF token, or recent authentication required for password changes",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid request or field validation failed",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "429": {
            "description": "Attempt limit exceeded",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until retry"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "503": {
            "description": "Email provider unavailable; request can be retried",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-CSRF-Token",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailLoginRequest"
              }
            }
          }
        }
      }
    },
    "/api/auth/magic-link": {
      "post": {
        "operationId": "requestMagicLink",
        "summary": "Request a single-use 15-minute login link",
        "description": "Browser cookie authentication. Fetch GET /api/auth/session first, retain cookies, and send its csrf_token as X-CSRF-Token for mutations. Authentication tokens are never returned in JSON. Session and CSRF state may rotate after login/logout. Email requests acknowledge known and unknown addresses identically; registration can resend to an existing account. No session is created until a link is consumed.",
        "responses": {
          "202": {
            "description": "Request a single-use 15-minute login link",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthAcknowledgment"
                }
              }
            }
          },
          "403": {
            "description": "Invalid CSRF token, or recent authentication required for password changes",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid request or field validation failed",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "429": {
            "description": "Attempt limit exceeded",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until retry"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "503": {
            "description": "Email provider unavailable; request can be retried",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-CSRF-Token",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailLoginRequest"
              }
            }
          }
        }
      }
    },
    "/api/auth/password": {
      "put": {
        "operationId": "updatePassword",
        "summary": "Set or change password and revoke previous sessions and links",
        "description": "Browser cookie authentication. Fetch GET /api/auth/session first, retain cookies, and send its csrf_token as X-CSRF-Token for mutations. Authentication tokens are never returned in JSON. Session and CSRF state may rotate after login/logout. Requires a confirmed account and authentication within the last 20 minutes. Other sessions are revoked; this browser receives a fresh session.",
        "responses": {
          "200": {
            "description": "Set or change password and revoke previous sessions and links",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthSession"
                }
              }
            }
          },
          "403": {
            "description": "Invalid CSRF token, or recent authentication required for password changes",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid request or field validation failed",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "429": {
            "description": "Attempt limit exceeded",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              },
              "Retry-After": {
                "schema": {
                  "type": "string"
                },
                "description": "Seconds until retry"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials/link or authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-CSRF-Token",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordRequest"
              }
            }
          }
        }
      }
    },
    "/api/videos": {
      "get": {
        "operationId": "listVideos",
        "summary": "List your videos newest first",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "before",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Exclusive video ID cursor. Pages contain at most 50 videos."
          },
          {
            "name": "group_by",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "revision_group"
              ]
            },
            "description": "Highest-ID member per owned group, selected before pagination."
          }
        ],
        "responses": {
          "200": {
            "description": "Owned videos and next cursor",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoPage"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "422": {
            "description": "Invalid cursor",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/api-token": {
      "get": {
        "operationId": "getApiToken",
        "summary": "getApiToken",
        "description": "Browser session only. Mutations require CSRF. Replacement requires authentication within 20 minutes and immediately invalidates the previous token. API tokens are invalidated by password changes, but survive logout.",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Token result",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiTokenMetadata"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid CSRF or recent authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "replaceApiToken",
        "summary": "replaceApiToken",
        "description": "Browser session only. Mutations require CSRF. Replacement requires authentication within 20 minutes and immediately invalidates the previous token. API tokens are invalidated by password changes, but survive logout.",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "201": {
            "description": "Token result",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiTokenCreated"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid CSRF or recent authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "503": {
            "description": "Token unavailable",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-CSRF-Token",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "delete": {
        "operationId": "revokeApiToken",
        "summary": "revokeApiToken",
        "description": "Browser session only. Mutations require CSRF. Replacement requires authentication within 20 minutes and immediately invalidates the previous token. API tokens are invalidated by password changes, but survive logout.",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Token result",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiTokenMetadata"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "403": {
            "description": "Invalid CSRF or recent authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "X-CSRF-Token",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/videos/{id}": {
      "delete": {
        "operationId": "deleteVideo",
        "summary": "Permanently delete an owned video",
        "description": "Use the Voila UUID from the video listing, not an upload or playback ID. Deletes the row inside a database transaction, then deletes Mux media or cancels a pending upload. Commits on success and rolls back the database deletion on failure. Browser sessions require X-CSRF-Token. Share links stop playing as Mux deletion propagates.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "502": {
            "description": "Cleanup could not be confirmed; database row retained. Retry deletion.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Mux not configured; database row retained.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Invalid CSRF token",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "204": {
            "description": "Mux media removed or confirmed absent, and database row deleted. No response body.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Invalid, missing, or non-owned video UUID. Repeated deletion returns 404.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "getVideo",
        "summary": "getVideo",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ViewingVideo"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or invalid CSRF",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Video not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid access or email batch",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateVideo",
        "summary": "Update an owned video title",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Video"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or invalid CSRF",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Video not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid title",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Update unavailable",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateVideoRequest"
              }
            }
          }
        }
      }
    },
    "/api/videos/{id}/playback": {
      "get": {
        "operationId": "getPlayback",
        "summary": "getPlayback",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Playback"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or invalid CSRF",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Video not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid access or email batch",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Playback not ready",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/videos/{id}/access": {
      "get": {
        "operationId": "getVideoAccess",
        "summary": "getVideoAccess",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoAccess"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or invalid CSRF",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Video not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid access or email batch",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "changeVideoAccess",
        "summary": "changeVideoAccess",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoAccess"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or invalid CSRF",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Video not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid access or email batch",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangeAccessRequest"
              }
            }
          }
        },
        "description": "Changing mode preserves all invitations. Browser mutations require X-CSRF-Token."
      }
    },
    "/api/videos/{id}/invitations": {
      "post": {
        "operationId": "inviteVideoPeople",
        "summary": "inviteVideoPeople",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvitationResult"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or invalid CSRF",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Video not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid access or email batch",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteRequest"
              }
            }
          }
        },
        "description": "Validate the entire batch (1–100 addresses), normalize and deduplicate, save grants atomically, then attempt delivery per recipient. Repeating a grant resends email; owner is skipped. Delivery statuses: sent, failed, rate_limited, owner. Browser mutations require X-CSRF-Token."
      }
    },
    "/api/videos/{id}/comments": {
      "get": {
        "operationId": "listComments",
        "summary": "listComments",
        "description": "Current video access is enforced on every request. Anyone-with-link videos allow anonymous reads. Invalid bearer tokens are rejected. Lists are ordered by creation time then ID.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "resolved",
                "all"
              ],
              "default": "all"
            },
            "description": "Filter entire threads by root status, including their replies."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommentList"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or invalid bearer credential",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Current video access denied or invalid CSRF token",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid status filter",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createComment",
        "summary": "createComment",
        "description": "Current video access is enforced on every request. Requires login; browser sessions require X-CSRF-Token. All fields and the PNG or JPEG bytes are saved atomically.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Comment"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or invalid bearer credential",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Current video access denied or invalid CSRF token",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid comment text, timestamp_ms, box or PNG/JPEG capture; nothing saved",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCommentRequest"
              }
            }
          }
        }
      }
    },
    "/api/comments/{id}": {
      "get": {
        "operationId": "getComment",
        "summary": "getComment",
        "description": "Current video access is enforced on every request. Anyone-with-link videos allow anonymous reads. Invalid bearer tokens are rejected. Lists are ordered by creation time then ID.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Comment"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or invalid bearer credential",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Current video access denied or invalid CSRF token",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteComment",
        "summary": "Delete your own comment",
        "description": "Permanently delete your own comment and all its replies. Requires current video access.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Comment, optional box and screenshot deleted. No response body.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or invalid bearer credential",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Current video access denied or invalid CSRF token",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateComment",
        "summary": "Edit your own comment text",
        "description": "Requires authorship and current video access. Only text can change; captured context and comment identity remain intact. Identical text does not change edited_at. Concurrent edits use last successful write wins.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Authentication required or invalid bearer credential",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Current video access denied or invalid CSRF token",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Comment"
                }
              }
            }
          },
          "422": {
            "description": "Invalid comment text, timestamp_ms, box or PNG capture; nothing saved",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCommentRequest"
              }
            }
          }
        }
      }
    },
    "/api/comments/{id}/screenshot": {
      "get": {
        "operationId": "getCommentScreenshot",
        "summary": "getCommentScreenshot",
        "description": "Current video access is enforced on every request. Anyone-with-link videos allow anonymous reads. Invalid bearer tokens are rejected. Lists are ordered by creation time then ID.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success. Frames never change after creation, so the response may be cached privately and revalidated with If-None-Match.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "private, max-age=3600"
                  ]
                }
              },
              "ETag": {
                "description": "Strong validator for If-None-Match.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not modified; the cached frame is still valid and video access is still granted.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "private, max-age=3600"
                  ]
                }
              },
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required or invalid bearer credential",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Current video access denied or invalid CSRF token",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/videos/{id}/analytics": {
      "get": {
        "operationId": "getVideoAnalytics",
        "summary": "Read lifetime analytics for an owned video",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VideoAnalytics"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or invalid CSRF",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Video not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/videos/{id}/analytics/sessions": {
      "post": {
        "operationId": "collectVideoPlayback",
        "summary": "Collect browser playback progress",
        "security": [
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "X-CSRF-Token",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Required with browser-session CSRF; anonymous cookie-free collection uses X-Analytics-CSRF-Token instead."
          },
          {
            "name": "X-Analytics-CSRF-Token",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Anonymous cookie-free CSRF alternative from GET /api/auth/session; requires same-origin Origin and application/json. Signed-in writes still require standard session CSRF."
          }
        ],
        "responses": {
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or invalid CSRF",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Video not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "204": {
            "description": "Accepted, ignored retry, or excluded owner; no body"
          },
          "409": {
            "description": "Session identity conflict or playback not ready",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid or implausible playback report",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Browser CSRF protection required for every write. Signed-in collection uses session cookies and X-CSRF-Token. Anonymous collection can instead use the same-origin X-Analytics-CSRF-Token issued by GET /api/auth/session when cookies are unavailable. Authenticated owners are excluded. Session UUIDv7 binds video and opaque viewer. Send first advancing sample immediately, then cumulative progress every 10 seconds and on pause/end/page hide. Increasing sequence numbers ignore duplicates and older reports; non-advancing reports do not refresh activity. Maximum 512 merged intervals and 7-day media duration. No bearer collection.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PlaybackReport"
              }
            }
          }
        }
      }
    },
    "/api/comments/{id}/resolve": {
      "post": {
        "operationId": "resolveComment",
        "summary": "Resolve a comment thread",
        "description": "Accepts a root or reply ID and returns the updated root. Requires current video access and either video ownership or root authorship. Browser sessions require X-CSRF-Token. Idempotent: resolving preserves an existing timestamp; reopening clears it. Replies do not automatically reopen threads.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Authentication required or invalid bearer credential",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Video access denied, not video owner or root author, or invalid CSRF token",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Comment"
                }
              }
            }
          }
        }
      }
    },
    "/api/comments/{id}/reopen": {
      "post": {
        "operationId": "reopenComment",
        "summary": "Reopen a comment thread",
        "description": "Accepts a root or reply ID and returns the updated root. Requires current video access and either video ownership or root authorship. Browser sessions require X-CSRF-Token. Idempotent: resolving preserves an existing timestamp; reopening clears it. Replies do not automatically reopen threads.",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "401": {
            "description": "Authentication required or invalid bearer credential",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Video access denied, not video owner or root author, or invalid CSRF token",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Comment"
                }
              }
            }
          }
        }
      }
    },
    "/api/videos/{id}/revisions": {
      "get": {
        "operationId": "listVideoRevisions",
        "summary": "Accessible revisions newest first, including the referenced video",
        "security": [
          {
            "bearerAuth": []
          },
          {
            "cookieAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Exclusive video ID cursor. Pages contain at most 50 videos."
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevisionPage"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or invalid CSRF",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Video not found",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid cursor",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "no-store"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "Authorizes the referenced video, then filters every group member by viewing permissions before 50-item cursor pagination."
      }
    }
  },
  "components": {
    "schemas": {
      "Upload": {
        "type": "object",
        "required": [
          "id",
          "upload_url",
          "expires_at",
          "video_id",
          "viewing_url",
          "deliveries",
          "title",
          "revision_group_id"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "description": "Mux direct upload ID.",
            "minLength": 1
          },
          "upload_url": {
            "type": "string",
            "format": "uri",
            "description": "Signed HTTPS URL. PUT video bytes directly here. Do not log this URL."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Conservative upload deadline in UTC (ISO 8601 with Z), calculated from request start plus Mux timeout."
          },
          "video_id": {
            "type": "string",
            "format": "uuid"
          },
          "viewing_url": {
            "type": "string",
            "format": "uri"
          },
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Delivery"
            }
          },
          "title": {
            "type": "string"
          },
          "revision_group_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code"
            ],
            "additionalProperties": false,
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "upload_unavailable",
                  "upload_creation_failed",
                  "upload_not_found",
                  "upload_lookup_failed",
                  "unauthenticated",
                  "invalid_csrf",
                  "video_not_found",
                  "comment_not_found",
                  "invalid_request",
                  "video_deletion_unavailable",
                  "video_deletion_failed",
                  "invalid_access",
                  "invalid_emails",
                  "access_unavailable",
                  "access_denied",
                  "playback_not_ready",
                  "playback_unavailable",
                  "invalid_title",
                  "video_update_failed"
                ]
              }
            }
          }
        }
      },
      "UploadStatus": {
        "type": "object",
        "required": [
          "id",
          "upload_status",
          "asset_status",
          "viewing_url",
          "upload_error",
          "asset_errors",
          "access",
          "video_id",
          "title",
          "duration",
          "thumbnail_url",
          "revision_group_id"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1
          },
          "upload_status": {
            "type": "string",
            "enum": [
              "waiting",
              "asset_created",
              "errored",
              "cancelled",
              "timed_out"
            ]
          },
          "asset_status": {
            "type": "string",
            "nullable": true,
            "enum": [
              "preparing",
              "ready",
              "errored",
              null
            ]
          },
          "upload_error": {
            "type": "object",
            "required": [
              "type",
              "message"
            ],
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "nullable": true
          },
          "asset_errors": {
            "type": "object",
            "required": [
              "type",
              "messages"
            ],
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string"
              },
              "messages": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "nullable": true
          },
          "viewing_url": {
            "type": "string",
            "format": "uri",
            "description": "Stable Voila viewing URL using the video UUID, available before processing finishes."
          },
          "access": {
            "$ref": "#/components/schemas/AccessMode"
          },
          "video_id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "description": "The video title."
          },
          "duration": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "nullable": true,
            "description": "Whole playback seconds, rounded up, once the asset is ready."
          },
          "thumbnail_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Signed Mux poster frame, valid for about an hour. Null until the asset is ready. Do not log this URL."
          },
          "revision_group_id": {
            "type": "string",
            "format": "uuid"
          }
        }
      },
      "AuthSession": {
        "type": "object",
        "required": [
          "user",
          "csrf_token"
        ],
        "additionalProperties": false,
        "properties": {
          "user": {
            "type": "object",
            "required": [
              "id",
              "email",
              "has_password"
            ],
            "additionalProperties": false,
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "has_password": {
                "type": "boolean"
              }
            },
            "nullable": true
          },
          "csrf_token": {
            "type": "string"
          },
          "anonymous_analytics_token": {
            "type": "string",
            "description": "One-hour anonymous analytics CSRF token for cookie-disabled browsers. Only valid for anonymous collection from this origin; does not grant viewing access."
          }
        }
      },
      "AuthAcknowledgment": {
        "type": "object",
        "required": [
          "message"
        ],
        "additionalProperties": false,
        "properties": {
          "message": {
            "type": "string"
          }
        }
      },
      "AuthError": {
        "type": "object",
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code"
            ],
            "additionalProperties": false,
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_csrf",
                  "invalid_request",
                  "invalid_credentials",
                  "invalid_link",
                  "unauthenticated",
                  "reauthentication_required",
                  "validation_failed",
                  "email_unavailable",
                  "rate_limited",
                  "token_unavailable"
                ]
              },
              "fields": {
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      },
      "EmailLoginRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 160
          },
          "return_to": {
            "type": "string",
            "default": "/",
            "description": "Internal /, /settings, or /videos/{uuid} path. Other destinations fall back to /."
          }
        }
      },
      "CreateSessionRequest": {
        "oneOf": [
          {
            "type": "object",
            "required": [
              "token"
            ],
            "additionalProperties": false,
            "properties": {
              "token": {
                "type": "string",
                "maxLength": 256
              }
            }
          },
          {
            "type": "object",
            "required": [
              "email",
              "password"
            ],
            "additionalProperties": false,
            "properties": {
              "email": {
                "type": "string",
                "format": "email",
                "maxLength": 160
              },
              "password": {
                "type": "string",
                "minLength": 1,
                "maxLength": 128
              }
            }
          }
        ]
      },
      "PasswordRequest": {
        "type": "object",
        "required": [
          "password"
        ],
        "additionalProperties": false,
        "properties": {
          "password": {
            "type": "string",
            "minLength": 12,
            "maxLength": 128
          },
          "password_confirmation": {
            "type": "string",
            "minLength": 12,
            "maxLength": 128
          }
        }
      },
      "Video": {
        "type": "object",
        "required": [
          "id",
          "upload_status",
          "asset_status",
          "upload_error",
          "asset_errors",
          "viewing_url",
          "upload_id",
          "created_at",
          "access",
          "video_id",
          "title",
          "duration",
          "views",
          "comments",
          "thumbnail_url",
          "revision_group_id"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "upload_status": {
            "type": "string",
            "enum": [
              "waiting",
              "asset_created",
              "errored",
              "cancelled",
              "timed_out"
            ]
          },
          "asset_status": {
            "type": "string",
            "nullable": true,
            "enum": [
              "preparing",
              "ready",
              "errored",
              null
            ]
          },
          "upload_error": {
            "type": "object",
            "required": [
              "type",
              "message"
            ],
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "nullable": true
          },
          "asset_errors": {
            "type": "object",
            "required": [
              "type",
              "messages"
            ],
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string"
              },
              "messages": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "nullable": true
          },
          "viewing_url": {
            "type": "string",
            "format": "uri",
            "description": "Stable Voila viewing URL using the video UUID, available before processing finishes."
          },
          "upload_id": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "access": {
            "$ref": "#/components/schemas/AccessMode"
          },
          "video_id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "description": "The video title."
          },
          "duration": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "nullable": true,
            "description": "Whole playback seconds, rounded up, once the asset is ready."
          },
          "views": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "Recorded playback sessions, matching the analytics views count."
          },
          "comments": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "Comments and replies on the video."
          },
          "thumbnail_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Signed Mux poster frame, valid for about an hour. Null until the asset is ready. Do not log this URL."
          },
          "revision_group_id": {
            "type": "string",
            "format": "uuid"
          },
          "revision_count": {
            "type": "integer",
            "minimum": 1,
            "description": "Present when group_by=revision_group; counts owned group members."
          }
        }
      },
      "VideoPage": {
        "type": "object",
        "required": [
          "videos",
          "next_cursor"
        ],
        "additionalProperties": false,
        "properties": {
          "videos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Video"
            }
          },
          "next_cursor": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        }
      },
      "ApiTokenMetadata": {
        "type": "object",
        "required": [
          "created_at"
        ],
        "additionalProperties": false,
        "properties": {
          "created_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "ApiTokenCreated": {
        "type": "object",
        "required": [
          "token",
          "created_at"
        ],
        "additionalProperties": false,
        "properties": {
          "token": {
            "type": "string",
            "description": "Shown only once. Store securely; never log."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AccessMode": {
        "type": "string",
        "enum": [
          "invite_only",
          "anyone_with_link"
        ]
      },
      "CreateUploadRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "access": {
            "$ref": "#/components/schemas/AccessMode"
          },
          "invite_emails": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "string",
              "format": "email",
              "maxLength": 160
            }
          },
          "title": {
            "type": "string",
            "description": "Trimmed title, 1–200 grapheme clusters. Defaults to Untitled video when omitted."
          },
          "revised_from": {
            "type": "string",
            "format": "uuid",
            "description": "Owned source video. Inherits title, access and invitations unless explicitly supplied."
          }
        },
        "required": []
      },
      "ChangeAccessRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "access": {
            "$ref": "#/components/schemas/AccessMode"
          }
        },
        "required": [
          "access"
        ]
      },
      "InviteRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "emails": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "string",
              "format": "email",
              "maxLength": 160
            },
            "minItems": 1
          }
        },
        "required": [
          "emails"
        ]
      },
      "Delivery": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "sent",
              "failed",
              "rate_limited",
              "owner"
            ]
          }
        },
        "required": [
          "email",
          "status"
        ]
      },
      "VideoAccess": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "video_id": {
            "type": "string",
            "format": "uuid"
          },
          "access": {
            "$ref": "#/components/schemas/AccessMode"
          },
          "emails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "video_id",
          "access",
          "emails"
        ]
      },
      "InvitationResult": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "video_id": {
            "type": "string",
            "format": "uuid"
          },
          "access": {
            "$ref": "#/components/schemas/AccessMode"
          },
          "emails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Delivery"
            }
          }
        },
        "required": [
          "video_id",
          "access",
          "emails",
          "deliveries"
        ]
      },
      "Playback": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "playback_id": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "tokens": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "playback": {
                "type": "string"
              },
              "thumbnail": {
                "type": "string"
              },
              "storyboard": {
                "type": "string"
              }
            },
            "required": [
              "playback",
              "thumbnail",
              "storyboard"
            ]
          }
        },
        "required": [
          "playback_id",
          "expires_at",
          "tokens"
        ]
      },
      "ViewingVideo": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "upload_status": {
            "type": "string",
            "enum": [
              "waiting",
              "asset_created",
              "errored",
              "cancelled",
              "timed_out"
            ]
          },
          "asset_status": {
            "type": "string",
            "nullable": true,
            "enum": [
              "preparing",
              "ready",
              "errored",
              null
            ]
          },
          "is_owner": {
            "type": "boolean"
          },
          "title": {
            "type": "string",
            "description": "The video title."
          },
          "author": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "email"
            ],
            "properties": {
              "email": {
                "type": "string"
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the video was created in Voila."
          },
          "thumbnail_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Signed Mux poster frame, valid for about an hour. Null until the asset is ready. Do not log this URL."
          },
          "revision_group_id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "id",
          "upload_status",
          "asset_status",
          "is_owner",
          "title",
          "author",
          "created_at",
          "thumbnail_url",
          "revision_group_id"
        ]
      },
      "CommentBox": {
        "type": "object",
        "nullable": true,
        "additionalProperties": false,
        "required": [
          "x",
          "y",
          "width",
          "height"
        ],
        "properties": {
          "x": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "y": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "width": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "exclusiveMinimum": true
          },
          "height": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "exclusiveMinimum": true
          }
        },
        "description": "One frame-relative rectangle. x + width and y + height must be at most 1."
      },
      "CreateCommentRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 10000,
            "description": "Must contain non-whitespace text."
          },
          "timestamp_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "maximum": 9007199254740991,
            "description": "Milliseconds from the start of the video, not a Unix timestamp. Browser capture rounds to the nearest millisecond."
          },
          "box": {
            "$ref": "#/components/schemas/CommentBox"
          },
          "screenshot": {
            "type": "string",
            "maxLength": 5333359,
            "description": "PNG or JPEG data URL (data:image/png;base64,... or data:image/jpeg;base64,...), at most 4,000,000 decoded bytes and 8192 pixels per dimension. Full captured frame including the optional box."
          },
          "parent_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        },
        "description": "With parent_id, creates a text-only reply to a top-level comment in this video; timestamp_ms, screenshot and box must be omitted. Without parent_id (or null), timestamp_ms and screenshot are required."
      },
      "Comment": {
        "type": "object",
        "required": [
          "id",
          "video_id",
          "author",
          "text",
          "timestamp_ms",
          "box",
          "screenshot_url",
          "comment_url",
          "edited_at",
          "parent_id",
          "resolved_at",
          "inserted_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "video_id": {
            "type": "string",
            "format": "uuid"
          },
          "author": {
            "type": "object",
            "required": [
              "id",
              "email"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "email": {
                "type": "string"
              }
            }
          },
          "text": {
            "type": "string"
          },
          "timestamp_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "maximum": 9007199254740991,
            "description": "Milliseconds from the start of the video, not a Unix timestamp. Browser capture rounds to the nearest millisecond.",
            "nullable": true
          },
          "box": {
            "$ref": "#/components/schemas/CommentBox"
          },
          "screenshot_url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL of the saved PNG or JPEG, including any bounding box. Fetch directly; private videos require an authorized API bearer token or browser session. Every request checks current video access.",
            "nullable": true
          },
          "comment_url": {
            "type": "string",
            "format": "uri",
            "description": "Shareable viewing URL for this comment within its video."
          },
          "edited_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Last text edit time in UTC; null until the text changes. Previous text is not retained."
          },
          "parent_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Parent comment ID for a text-only reply; null for an original comment."
          },
          "resolved_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Root thread resolution time in UTC; null for open roots and all replies. Reopening clears it. Replies inherit their root thread status."
          },
          "inserted_at": {
            "type": "string",
            "format": "date-time",
            "description": "Creation time in UTC."
          }
        }
      },
      "CommentList": {
        "type": "object",
        "required": [
          "comments"
        ],
        "properties": {
          "comments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Comment"
            }
          }
        }
      },
      "VideoAnalytics": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "views",
          "tracking_started_at",
          "unique_viewers",
          "total_watch_time_seconds",
          "average_watch_time_seconds",
          "average_percent_watched",
          "last_viewed_at"
        ],
        "properties": {
          "views": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          },
          "tracking_started_at": {
            "type": "string",
            "format": "date-time"
          },
          "unique_viewers": {
            "type": "integer",
            "format": "int64",
            "minimum": 0,
            "description": "Distinct opaque viewer identities across recorded sessions. Anonymous counts are approximate; signed-out owners cannot reliably be excluded."
          },
          "total_watch_time_seconds": {
            "type": "number",
            "minimum": 0,
            "description": "Total real advancing playback seconds, including background playback and repeated sections; excludes pauses, buffering and seeks."
          },
          "average_watch_time_seconds": {
            "type": "number",
            "minimum": 0,
            "nullable": true,
            "description": "Total watch time divided by recorded views, including active sessions. Null with no views."
          },
          "average_percent_watched": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "nullable": true,
            "description": "Mean per-session union of played intervals divided by precise duration, as0–100. Includes active sessions with valid duration; null if none qualify. Replays count once and skipped sections do not count. This is average coverage, not completion rate."
          },
          "last_viewed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Latest UTC server receipt time of new accepted playback progress, including ongoing sessions. Page opens, pauses, owners, duplicate and older reports do not refresh it. Null without eligible playback."
          }
        }
      },
      "PlaybackReport": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "session_id",
          "anonymous_id",
          "account_id",
          "sequence",
          "active_ms",
          "duration",
          "intervals",
          "elapsed_ms"
        ],
        "properties": {
          "session_id": {
            "type": "string",
            "format": "uuid"
          },
          "anonymous_id": {
            "type": "string",
            "format": "uuid"
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "sequence": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "active_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "duration": {
            "type": "number",
            "nullable": true,
            "minimum": 0,
            "maximum": 604800
          },
          "intervals": {
            "type": "array",
            "maxItems": 512,
            "items": {
              "type": "array",
              "minItems": 2,
              "maxItems": 2,
              "items": {
                "type": "number",
                "minimum": 0
              }
            }
          },
          "elapsed_ms": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "maximum": 9007199254740991,
            "description": "Monotonic wall milliseconds since measurement began, including pauses. Must be at least active_ms and bounded by tracked video lifetime."
          }
        }
      },
      "UpdateCommentRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 10000,
            "description": "Must contain non-whitespace text."
          }
        }
      },
      "UpdateVideoRequest": {
        "type": "object",
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "Trimmed title, 1–200 grapheme clusters."
          }
        }
      },
      "Revision": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "title",
          "created_at",
          "viewing_url",
          "upload_status",
          "asset_status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string",
            "description": "The video title."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "viewing_url": {
            "type": "string",
            "format": "uri",
            "description": "Stable Voila viewing URL using the video UUID, available before processing finishes."
          },
          "upload_status": {
            "type": "string",
            "enum": [
              "waiting",
              "asset_created",
              "errored",
              "cancelled",
              "timed_out"
            ]
          },
          "asset_status": {
            "type": "string",
            "nullable": true,
            "enum": [
              "preparing",
              "ready",
              "errored",
              null
            ]
          }
        }
      },
      "RevisionPage": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "videos",
          "next_cursor"
        ],
        "properties": {
          "videos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Revision"
            }
          },
          "next_cursor": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      },
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "_voila_key"
      }
    }
  }
}
