openapi: 3.1.0
info:
  title: Videco API
  version: 0.1.0
  summary: Manage videos and folders in your Videco library
  description: |
    Customer-facing HTTP API for Videco.

    **Authentication (pick one):**

    1. **API key (recommended for servers)** — Generate a key on the Developers
       page (Pro / Business), then send:
       `Authorization: Bearer vd_live_…`
    2. **Session cookie** — Browser requests from a logged-in Videco session
       also work (same-origin app usage).

    **Plan limits:** Uploads are subject to your plan’s storage and duration
    quotas and may return `402 Payment Required` when exceeded.
  contact:
    name: Videco Support
servers:
  - url: "{origin}"
    description: Your Videco deployment
    variables:
      origin:
        default: http://localhost:3000

tags:
  - name: Videos
    description: List, create, update, and delete library videos
  - name: Folders
    description: Organize videos into folders

paths:
  /api/videos:
    get:
      tags: [Videos]
      operationId: listVideos
      summary: List your videos
      description: |
        Returns videos owned by the authenticated account only (resolved from
        the API key or session). Other users’ videos are never included.
      security:
        - SessionCookie: []
        - ApiKey: []
      parameters:
        - name: folderId
          in: query
          required: false
          description: |
            Filter by folder UUID. Pass `null` (string) for videos with no folder.
          schema:
            type: string
        - name: status
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/VideoStatus"
        - name: visibility
          in: query
          required: false
          schema:
            $ref: "#/components/schemas/VideoVisibility"
        - name: limit
          in: query
          required: false
          description: Page size (1–100, default 50)
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: offset
          in: query
          required: false
          description: Number of rows to skip (default 0)
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        "200":
          description: Paginated list of videos owned by the caller
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ListVideosResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      tags: [Videos]
      operationId: createVideoUpload
      summary: Create a video and TUS upload credentials
      description: |
        Creates a video record and returns TUS credentials for uploading the
        binary to Bunny Stream. Call this before starting a TUS upload.
      security:
        - SessionCookie: []
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateVideoRequest"
            example:
              title: Product demo
              description: Optional description
              folderId: null
              fileSizeBytes: 10485760
      responses:
        "200":
          description: Video created; upload with the returned TUS credentials
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateVideoResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "402":
          $ref: "#/components/responses/PlanLimit"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/videos/{id}:
    parameters:
      - $ref: "#/components/parameters/VideoId"
    get:
      tags: [Videos]
      operationId: getVideo
      summary: Get a video by id
      description: |
        Returns a video only if it belongs to the authenticated account.
        Missing or non-owned ids both return 404.
      security:
        - SessionCookie: []
        - ApiKey: []
      responses:
        "200":
          description: Video owned by the caller
          content:
            application/json:
              schema:
                type: object
                required: [video]
                properties:
                  video:
                    $ref: "#/components/schemas/Video"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      tags: [Videos]
      operationId: updateVideo
      summary: Update video metadata or player settings
      security:
        - SessionCookie: []
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateVideoRequest"
      responses:
        "200":
          description: Updated video
          content:
            application/json:
              schema:
                type: object
                required: [video]
                properties:
                  video:
                    $ref: "#/components/schemas/Video"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      tags: [Videos]
      operationId: deleteVideo
      summary: Delete a video
      security:
        - SessionCookie: []
        - ApiKey: []
      responses:
        "200":
          description: Video deleted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OkResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /api/folders:
    post:
      tags: [Folders]
      operationId: createFolder
      summary: Create a folder
      security:
        - SessionCookie: []
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateFolderRequest"
            example:
              name: Campaigns
              parentId: null
      responses:
        "200":
          description: Folder created
          content:
            application/json:
              schema:
                type: object
                required: [folder]
                properties:
                  folder:
                    $ref: "#/components/schemas/Folder"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: A folder with the same name already exists in this parent
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /api/folders/{id}:
    parameters:
      - $ref: "#/components/parameters/FolderId"
    patch:
      tags: [Folders]
      operationId: updateFolder
      summary: Rename or move a folder
      security:
        - SessionCookie: []
        - ApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateFolderRequest"
      responses:
        "200":
          description: Updated folder
          content:
            application/json:
              schema:
                type: object
                required: [folder]
                properties:
                  folder:
                    $ref: "#/components/schemas/Folder"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
    delete:
      tags: [Folders]
      operationId: deleteFolder
      summary: Delete an empty folder
      description: The folder must contain no child folders or videos.
      security:
        - SessionCookie: []
        - ApiKey: []
      responses:
        "200":
          description: Folder deleted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OkResponse"
        "400":
          description: Folder is not empty
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

components:
  securitySchemes:
    SessionCookie:
      type: apiKey
      in: cookie
      name: sb-access-token
      description: |
        Browser session from Videco login (Supabase Auth). Exact cookie names
        depend on your Supabase project; authenticated browser requests work
        automatically when calling the API from the app.
    ApiKey:
      type: http
      scheme: bearer
      bearerFormat: vd_live_…
      description: |
        Secret API key from Developers → Generate API key.
        Example header: `Authorization: Bearer vd_live_…`

  parameters:
    VideoId:
      name: id
      in: path
      required: true
      description: Video UUID
      schema:
        type: string
        format: uuid
    FolderId:
      name: id
      in: path
      required: true
      description: Folder UUID
      schema:
        type: string
        format: uuid

  responses:
    BadRequest:
      description: Invalid request body or parameters
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    Unauthorized:
      description: Missing or invalid authentication
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    NotFound:
      description: Resource not found or not owned by the caller
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
    PlanLimit:
      description: Upload blocked by plan storage or duration limits
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/PlanLimitError"

  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string

    PlanLimitError:
      type: object
      required: [error, code]
      properties:
        error:
          type: string
        code:
          type: string
          enum: [storage, duration, feature]

    OkResponse:
      type: object
      required: [ok]
      properties:
        ok:
          type: boolean
          const: true

    VideoStatus:
      type: string
      enum: [created, uploading, processing, ready, failed]

    VideoVisibility:
      type: string
      enum: [private, unlisted, public]

    Video:
      type: object
      required:
        - id
        - userId
        - bunnyVideoId
        - title
        - status
        - visibility
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        userId:
          type: string
          format: uuid
        folderId:
          type: ["string", "null"]
          format: uuid
        bunnyVideoId:
          type: string
          description: Bunny Stream video GUID
        title:
          type: string
        description:
          type: ["string", "null"]
        status:
          $ref: "#/components/schemas/VideoStatus"
        visibility:
          $ref: "#/components/schemas/VideoVisibility"
        shareSlug:
          type: ["string", "null"]
        durationSeconds:
          type: ["string", "null"]
          description: Duration in seconds (numeric string from the database)
        storageBytes:
          type: ["string", "null"]
          description: Stored size in bytes (numeric string from the database)
        thumbnailUrl:
          type: ["string", "null"]
          format: uri
        playerResponsive:
          type: boolean
        playerAutoplay:
          type: boolean
        playerPreload:
          type: boolean
        playerLoop:
          type: boolean
        playerMuted:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    TusUploadCredentials:
      type: object
      required:
        - libraryId
        - videoId
        - expirationTime
        - signature
        - endpoint
      properties:
        libraryId:
          type: string
        videoId:
          type: string
          description: Bunny video GUID used as the TUS VideoId header
        expirationTime:
          type: integer
          description: Unix timestamp when the TUS signature expires
        signature:
          type: string
        endpoint:
          type: string
          format: uri
          example: https://video.bunnycdn.com/tusupload

    CreateVideoRequest:
      type: object
      required: [title, fileSizeBytes]
      properties:
        title:
          type: string
          minLength: 1
        description:
          type: string
        folderId:
          type: ["string", "null"]
          format: uuid
        fileSizeBytes:
          type: number
          exclusiveMinimum: 0
          description: Exact byte size of the file you will upload (used for quota checks)

    CreateVideoResponse:
      type: object
      required: [video, upload]
      properties:
        video:
          $ref: "#/components/schemas/Video"
        upload:
          $ref: "#/components/schemas/TusUploadCredentials"

    Pagination:
      type: object
      required: [limit, offset, total, hasMore]
      properties:
        limit:
          type: integer
        offset:
          type: integer
        total:
          type: integer
        hasMore:
          type: boolean

    ListVideosResponse:
      type: object
      required: [videos, pagination]
      properties:
        videos:
          type: array
          items:
            $ref: "#/components/schemas/Video"
        pagination:
          $ref: "#/components/schemas/Pagination"

    UpdateVideoRequest:
      type: object
      properties:
        title:
          type: string
        description:
          type: ["string", "null"]
        visibility:
          $ref: "#/components/schemas/VideoVisibility"
        folderId:
          type: ["string", "null"]
          format: uuid
        status:
          type: string
          enum: [uploading, processing, ready, failed]
        playerResponsive:
          type: boolean
        playerAutoplay:
          type: boolean
        playerPreload:
          type: boolean
        playerLoop:
          type: boolean
        playerMuted:
          type: boolean

    Folder:
      type: object
      required: [id, userId, name, createdAt, updatedAt]
      properties:
        id:
          type: string
          format: uuid
        userId:
          type: string
          format: uuid
        parentId:
          type: ["string", "null"]
          format: uuid
        name:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    CreateFolderRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
          minLength: 1
        parentId:
          type: ["string", "null"]
          format: uuid

    UpdateFolderRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
        parentId:
          type: ["string", "null"]
          format: uuid
