openapi: 3.0.3
info:
  title: iLoveVideoEditor API
  description: |-
    Cloud video rendering API for iLoveVideoEditor. Submit a `VideoJSON` payload, queue a render job, and download the resulting MP4.

    **Authentication**
    - **API Key** (`x-api-key` header) — accepted by render pipeline and tool endpoints.
    - **Bearer Token** (`Authorization: Bearer <jwt>`) — required for user-scoped project, asset, billing, and webhook endpoints.
    - Many endpoints accept either form of authentication.

    **SDKs**
    Official typed SDKs are available for JavaScript/TypeScript, PHP, Python, Ruby, and Go. See https://ilovevideoeditor.com/docs/sdks

    **Outbound Webhooks**
    Render lifecycle events (`render.completed`, `render.failed`) are delivered to your endpoints as signed HTTP POST requests. See the `Webhooks` tag and the root-level `x-webhook-docs` extension for the payload format (`RenderWebhookPayload`), signature headers, HMAC verification, and the retry policy.
  version: 1.0.0
  contact:
    name: iLoveVideoEditor Support
    url: https://ilovevideoeditor.com/contact
servers:
  - url: https://api.ilovevideoeditor.com
    description: Production
security:
  - ApiKeyAuth: []
tags:
  - name: Health
    description: Service health and diagnostics
  - name: Render
    description: Video rendering pipeline
  - name: Templates
    description: Pre-built video templates
  - name: Tools
    description: Cloud video editing tools (native ffmpeg or full render pipeline)
  - name: Projects
    description: User-scoped video projects
  - name: Assets
    description: Workspace asset uploads and signed URLs (Bunny Storage)
  - name: Renditions
    description: Render history and statistics
  - name: Webhooks
    description: Manage outbound webhook subscriptions and receive signed render events.
  - name: Workflows
    description: Automation workflows for video pipelines
  - name: Billing
    description: Subscriptions, credits, and invoices
  - name: Integrations
    description: Workspace storage integrations and destinations
  - name: API Keys
    description: Per-user API key management
x-webhook-docs:
  summary: Signed outbound webhooks for render lifecycle events
  events:
    - render.completed
    - render.failed
  payloadSchema: '#/components/schemas/RenderWebhookPayload'
  targets:
    - Per-job `webhookUrl` (POST /v1/render, POST /v1/templates/{id}/render) — signed with the user's profile webhook secret; unsigned for anonymous jobs
    - Every active subscription in `webhook_subscriptions` whose `events` includes the event — signed with the subscription's own secret
    - Legacy env-level `WEBHOOK_URL` — unsigned
  signature:
    headers:
      X-ILVE-Event: render.completed or render.failed
      X-ILVE-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of `${t}.${rawBody}` with the target's secret>
    verify: |-
      t, v1 = parse(X-ILVE-Signature)
      expected = hex(HMAC-SHA256(secret, `${t}.${rawBody}`))
      assert timingSafeEqual(expected, v1)
      assert now - t < 300   # optional 5-minute freshness window
  retries:
    attempts: 3
    backoffMs:
      - 5000
      - 15000
      - 45000
    timeoutMs: 5000
    note: Targets are independent; a failed target never blocks the others.
paths:
  /health:
    get:
      operationId: healthCheck
      tags:
        - Health
      summary: Public health check
      security: []
      responses:
        '200':
          description: Service is up
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
  /health/detailed:
    get:
      operationId: healthCheckDetailed
      tags:
        - Health
      summary: Detailed health check
      description: Exposes Redis and Supabase latency metrics. Protected by API key.
      security:
        - ApiKeyAuth: []
      responses:
        '200':
          description: Detailed health status
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    enum:
                      - healthy
                      - degraded
                  timestamp:
                    type: string
                    format: date-time
                  services:
                    type: object
                    properties:
                      redis:
                        $ref: '#/components/schemas/ServiceHealth'
                      supabase:
                        $ref: '#/components/schemas/ServiceHealth'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/render:
    post:
      operationId: queueRender
      tags:
        - Render
      summary: Queue a render job
      description: Submit a `VideoJSON` payload to be rendered to MP4.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: Optional idempotency key (max 128 chars, 24h replay window).
          schema:
            type: string
            maxLength: 128
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - videoJSON
              properties:
                videoJSON:
                  $ref: '#/components/schemas/VideoJSON'
                webhookUrl:
                  type: string
                  format: uri
                projectId:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Job queued successfully
          headers:
            Idempotent-Replay:
              description: Present with value `true` when replayed from cache.
              schema:
                type: string
                enum:
                  - 'true'
          content:
            application/json:
              schema:
                type: object
                required:
                  - jobId
                  - status
                properties:
                  jobId:
                    type: string
                    format: uuid
                  status:
                    type: string
                  stage:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '402':
          description: Insufficient API credits
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Too many active render jobs
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    get:
      operationId: listRenders
      tags:
        - Render
      summary: List recent renders
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 10
            minimum: 1
            maximum: 50
      responses:
        '200':
          description: Recent renders
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/RenderListItem'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/render/cost:
    post:
      operationId: estimateRenderCost
      tags:
        - Render
      summary: Estimate render cost
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - videoJSON
              properties:
                videoJSON:
                  $ref: '#/components/schemas/VideoJSON'
      responses:
        '200':
          description: Cost estimate
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RenderCostEstimate'
        '400':
          $ref: '#/components/responses/BadRequest'
  /v1/render/tier:
    get:
      operationId: getRenderTier
      tags:
        - Render
      summary: Get workspace render tier
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        '200':
          description: Tier details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TierInfo'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/render/{id}:
    get:
      operationId: getRenderStatus
      tags:
        - Render
      summary: Get render job status
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Job status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RenderJob'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/render/{id}/cancel:
    post:
      operationId: cancelRender
      tags:
        - Render
      summary: Cancel a render job
      description: Cancels a render job that has not reached a terminal state.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Cancellation result
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                  - jobId
                  - status
                properties:
                  success:
                    type: boolean
                  jobId:
                    type: string
                  status:
                    type: string
                  previousStatus:
                    type: string
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Job already terminal
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/render/{id}/download:
    get:
      operationId: downloadRender
      tags:
        - Render
      summary: Download rendered video
      description: Redirects to the signed CDN URL for the completed render.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '302':
          description: Redirect to signed download URL
          headers:
            Location:
              schema:
                type: string
                format: uri
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/render/{id}/download-url:
    get:
      operationId: getRenderDownloadUrl
      tags:
        - Render
      summary: Get rendered video download URL
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Download URL and filename
          content:
            application/json:
              schema:
                type: object
                required:
                  - downloadUrl
                  - filename
                properties:
                  downloadUrl:
                    type: string
                    format: uri
                  filename:
                    type: string
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/render/{id}/refresh-url:
    post:
      operationId: refreshRenderUrl
      tags:
        - Render
      summary: Refresh signed download URL
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Fresh signed URL
          content:
            application/json:
              schema:
                type: object
                required:
                  - downloadUrl
                  - expiresInSeconds
                properties:
                  downloadUrl:
                    type: string
                    format: uri
                  expiresInSeconds:
                    type: integer
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/render/{id}/track-bandwidth:
    post:
      operationId: trackRenderBandwidth
      tags:
        - Render
      summary: Track render download bandwidth
      description: Records bandwidth usage for renders streamed directly from the CDN.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Bandwidth tracking result
          content:
            application/json:
              schema:
                type: object
                required:
                  - tracked
                properties:
                  tracked:
                    type: boolean
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/templates:
    get:
      operationId: listTemplates
      tags:
        - Templates
      summary: List all templates
      security: []
      responses:
        '200':
          description: List of available templates
          content:
            application/json:
              schema:
                type: object
                required:
                  - templates
                properties:
                  templates:
                    type: array
                    items:
                      $ref: '#/components/schemas/TemplateSummary'
  /v1/templates/{id}:
    get:
      operationId: getTemplate
      tags:
        - Templates
      summary: Get a single template
      security: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Full template object
          content:
            application/json:
              schema:
                type: object
                required:
                  - template
                properties:
                  template:
                    $ref: '#/components/schemas/Template'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/templates/{id}/render:
    post:
      operationId: renderTemplate
      tags:
        - Templates
      summary: Render a template with variables
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            maxLength: 128
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                variables:
                  type: object
                  additionalProperties: true
                webhookUrl:
                  type: string
                  format: uri
                projectId:
                  type: string
                  format: uuid
      responses:
        '200':
          description: Render job queued
          headers:
            Idempotent-Replay:
              description: Present with value `true` when replayed from cache.
              schema:
                type: string
                enum:
                  - 'true'
          content:
            application/json:
              schema:
                type: object
                required:
                  - jobId
                  - status
                properties:
                  jobId:
                    type: string
                    format: uuid
                  status:
                    type: string
                  stage:
                    type: string
        '400':
          $ref: '#/components/responses/BadRequest'
        '402':
          description: Insufficient API credits
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Template not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Too many active render jobs
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/tools/{toolId}/jobs:
    post:
      operationId: queueToolJob
      tags:
        - Tools
      summary: Queue a cloud tool job
      description: Submit a public or presigned input URL and tool-specific configuration. Simple tools run on a native ffmpeg worker; studio tools fall back to the full render pipeline.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: toolId
          in: path
          required: true
          schema:
            type: string
            example: trim-video
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ToolJobRequest'
      responses:
        '202':
          description: Tool job queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToolJobStatus'
        '400':
          $ref: '#/components/responses/BadRequest'
        '402':
          description: Insufficient API credits
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          description: Too many active jobs
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/tools/jobs/{id}:
    get:
      operationId: getToolJob
      tags:
        - Tools
      summary: Get tool job status
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Current tool job status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ToolJobStatus'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tools/jobs/{id}/download:
    get:
      operationId: downloadToolJob
      tags:
        - Tools
      summary: Download a completed tool job result
      description: Redirects to the CDN or signed URL for the completed output file.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '302':
          description: Redirect to output file
          headers:
            Location:
              schema:
                type: string
                format: uri
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/projects:
    get:
      operationId: listProjects
      tags:
        - Projects
      summary: List user projects
      security:
        - BearerAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 25
        - name: sortBy
          in: query
          schema:
            type: string
            enum:
              - created_at
              - updated_at
              - name
              - duration
            default: updated_at
        - name: sortOrder
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
        - name: search
          in: query
          schema:
            type: string
      responses:
        '200':
          description: User's projects
          content:
            application/json:
              schema:
                type: object
                properties:
                  projects:
                    type: array
                    items:
                      $ref: '#/components/schemas/Project'
                  total:
                    type: integer
                  page:
                    type: integer
                  limit:
                    type: integer
                  totalPages:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createProject
      tags:
        - Projects
      summary: Create a project
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectCreate'
      responses:
        '200':
          description: Project created
          content:
            application/json:
              schema:
                type: object
                required:
                  - project
                properties:
                  project:
                    $ref: '#/components/schemas/Project'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/projects/stats:
    get:
      operationId: getProjectStats
      tags:
        - Projects
      summary: Get total project count
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Project count
          content:
            application/json:
              schema:
                type: object
                required:
                  - total
                properties:
                  total:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/projects/{id}:
    get:
      operationId: getProject
      tags:
        - Projects
      summary: Get a project
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Project details
          content:
            application/json:
              schema:
                type: object
                required:
                  - project
                properties:
                  project:
                    $ref: '#/components/schemas/Project'
                  lastRendition:
                    $ref: '#/components/schemas/Rendition'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      operationId: updateProject
      tags:
        - Projects
      summary: Update a project
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectUpdate'
      responses:
        '200':
          description: Updated project
          content:
            application/json:
              schema:
                type: object
                required:
                  - project
                properties:
                  project:
                    $ref: '#/components/schemas/Project'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      operationId: patchProject
      tags:
        - Projects
      summary: Partially update a project
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProjectUpdate'
      responses:
        '200':
          description: Updated project
          content:
            application/json:
              schema:
                type: object
                required:
                  - project
                properties:
                  project:
                    $ref: '#/components/schemas/Project'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteProject
      tags:
        - Projects
      summary: Delete a project
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Deleted successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - deleted
                properties:
                  id:
                    type: string
                  deleted:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/projects/{id}/duplicate:
    post:
      operationId: duplicateProject
      tags:
        - Projects
      summary: Duplicate a project
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Duplicated project
          content:
            application/json:
              schema:
                type: object
                required:
                  - project
                properties:
                  project:
                    $ref: '#/components/schemas/Project'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/projects/batch-delete:
    post:
      operationId: batchDeleteProjects
      tags:
        - Projects
      summary: Bulk delete projects
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - ids
              properties:
                ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  minItems: 1
                  maxItems: 100
      responses:
        '200':
          description: Deleted projects
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchDeleteResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/assets:
    get:
      operationId: listAssets
      tags:
        - Assets
      summary: List workspace assets
      security:
        - BearerAuth: []
      parameters:
        - name: type
          in: query
          schema:
            type: string
            enum:
              - video
              - audio
              - image
              - font
        - name: search
          in: query
          schema:
            type: string
        - name: sortBy
          in: query
          schema:
            type: string
            enum:
              - created_at
              - name
              - type
              - size_bytes
              - duration
            default: created_at
        - name: sortOrder
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 25
      responses:
        '200':
          description: Paginated list of assets
          content:
            application/json:
              schema:
                type: object
                properties:
                  assets:
                    type: array
                    items:
                      $ref: '#/components/schemas/Asset'
                  total:
                    type: integer
                  page:
                    type: integer
                  limit:
                    type: integer
                  totalPages:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createAsset
      tags:
        - Assets
      summary: Register an uploaded asset
      description: Register an asset after it has been uploaded to Bunny Storage. Use `POST /v1/assets/upload-url` first.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AssetCreate'
      responses:
        '201':
          description: Asset registered
          content:
            application/json:
              schema:
                type: object
                required:
                  - asset
                properties:
                  asset:
                    $ref: '#/components/schemas/Asset'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/assets/upload-url:
    post:
      operationId: getAssetUploadUrl
      tags:
        - Assets
      summary: Get asset upload URL
      description: Returns the upload endpoint and Bunny Storage path for a new asset.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - filename
                - type
              properties:
                filename:
                  type: string
                type:
                  type: string
                  enum:
                    - image
                    - video
                    - audio
                    - font
      responses:
        '200':
          description: Upload URL
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssetUploadUrlResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '503':
          description: Bunny Storage not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/assets/{assetId}/upload:
    post:
      operationId: uploadAsset
      tags:
        - Assets
      summary: Upload an asset file
      description: Receives a multipart/form-data file upload and forwards it to Bunny Storage.
      security:
        - BearerAuth: []
      parameters:
        - name: assetId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: Asset file (up to 2 GB).
      responses:
        '200':
          description: Upload succeeded
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '503':
          description: Bunny Storage not configured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/assets/{id}:
    get:
      operationId: getAsset
      tags:
        - Assets
      summary: Get a single asset
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Asset details
          content:
            application/json:
              schema:
                type: object
                required:
                  - asset
                properties:
                  asset:
                    $ref: '#/components/schemas/Asset'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteAsset
      tags:
        - Assets
      summary: Delete an asset
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Deleted successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - deleted
                properties:
                  id:
                    type: string
                  deleted:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/assets/{id}/signed-url:
    get:
      operationId: getAssetSignedUrl
      tags:
        - Assets
      summary: Get a public asset URL
      description: Returns the public Bunny CDN URL for the asset.
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Signed URL
          content:
            application/json:
              schema:
                type: object
                required:
                  - signedUrl
                properties:
                  signedUrl:
                    type: string
                    format: uri
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/assets/{id}/proxy:
    get:
      operationId: proxyAsset
      tags:
        - Assets
      summary: Proxy an asset through the API
      description: Streams the asset through the API so the browser can fetch it without relying on CDN CORS.
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Asset stream
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/assets/batch-delete:
    post:
      operationId: batchDeleteAssets
      tags:
        - Assets
      summary: Bulk delete assets
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - ids
              properties:
                ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  minItems: 1
                  maxItems: 100
      responses:
        '200':
          description: Deleted assets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchDeleteResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/renditions:
    get:
      operationId: listRenditions
      tags:
        - Renditions
      summary: List render history
      security:
        - BearerAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 25
        - name: sortBy
          in: query
          schema:
            type: string
            enum:
              - created_at
              - duration
              - status
              - cost
              - job_id
            default: created_at
        - name: sortOrder
          in: query
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
        - name: status
          in: query
          schema:
            type: string
            enum:
              - queued
              - processing
              - rendering
              - completed
              - failed
        - name: search
          in: query
          schema:
            type: string
        - name: from
          in: query
          schema:
            type: string
            format: date-time
        - name: to
          in: query
          schema:
            type: string
            format: date-time
        - name: projectId
          in: query
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Paginated render history
          content:
            application/json:
              schema:
                type: object
                properties:
                  renditions:
                    type: array
                    items:
                      $ref: '#/components/schemas/Rendition'
                  total:
                    type: integer
                  page:
                    type: integer
                  limit:
                    type: integer
                  totalPages:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/renditions/stats:
    get:
      operationId: getRenditionStats
      tags:
        - Renditions
      summary: Get render statistics
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Status counts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RenditionStats'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/renditions/{id}:
    get:
      operationId: getRendition
      tags:
        - Renditions
      summary: Get a single rendition
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Rendition details
          content:
            application/json:
              schema:
                type: object
                required:
                  - rendition
                properties:
                  rendition:
                    $ref: '#/components/schemas/Rendition'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteRendition
      tags:
        - Renditions
      summary: Delete a rendition
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Deleted successfully
          content:
            application/json:
              schema:
                type: object
                required:
                  - id
                  - deleted
                properties:
                  id:
                    type: string
                  deleted:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/renditions/{id}/cancel:
    post:
      operationId: cancelRendition
      tags:
        - Renditions
      summary: Cancel a rendition
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Cancellation result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RenditionCancelResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Rendition already terminal
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/renditions/batch-delete:
    post:
      operationId: batchDeleteRenditions
      tags:
        - Renditions
      summary: Bulk delete renditions
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - ids
              properties:
                ids:
                  type: array
                  items:
                    type: string
                    format: uuid
                  minItems: 1
                  maxItems: 100
      responses:
        '200':
          description: Deleted renditions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchDeleteResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/webhooks:
    post:
      operationId: createWebhookSubscription
      tags:
        - Webhooks
      summary: Create a webhook subscription
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebhookSubscriptionCreate'
      responses:
        '201':
          description: Subscription created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookSubscription'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: Maximum subscriptions reached
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
    get:
      operationId: listWebhookSubscriptions
      tags:
        - Webhooks
      summary: List webhook subscriptions
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        '200':
          description: Active subscriptions
          content:
            application/json:
              schema:
                type: object
                required:
                  - subscriptions
                properties:
                  subscriptions:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookSubscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/webhooks/{id}:
    delete:
      operationId: deleteWebhookSubscription
      tags:
        - Webhooks
      summary: Revoke a webhook subscription
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Subscription revoked
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/workflows/step-types:
    get:
      operationId: listWorkflowStepTypes
      tags:
        - Workflows
      summary: List available workflow step types
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        '200':
          description: Step types
          content:
            application/json:
              schema:
                type: object
                required:
                  - stepTypes
                properties:
                  stepTypes:
                    type: array
                    items:
                      type: object
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/workflows:
    get:
      operationId: listWorkflows
      tags:
        - Workflows
      summary: List workflows
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 25
      responses:
        '200':
          description: Workflows
          content:
            application/json:
              schema:
                type: object
                properties:
                  workflows:
                    type: array
                    items:
                      $ref: '#/components/schemas/Workflow'
                  total:
                    type: integer
                  page:
                    type: integer
                  limit:
                    type: integer
                  totalPages:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createWorkflow
      tags:
        - Workflows
      summary: Create a workflow
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - definition
              properties:
                name:
                  type: string
                description:
                  type: string
                definition:
                  $ref: '#/components/schemas/WorkflowDefinition'
                isActive:
                  type: boolean
      responses:
        '201':
          description: Workflow created
          content:
            application/json:
              schema:
                type: object
                required:
                  - workflow
                properties:
                  workflow:
                    $ref: '#/components/schemas/Workflow'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/workflows/{id}:
    get:
      operationId: getWorkflow
      tags:
        - Workflows
      summary: Get a workflow
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Workflow details
          content:
            application/json:
              schema:
                type: object
                required:
                  - workflow
                properties:
                  workflow:
                    $ref: '#/components/schemas/Workflow'
                  recentRuns:
                    type: array
                    items:
                      $ref: '#/components/schemas/WorkflowRun'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      operationId: updateWorkflow
      tags:
        - Workflows
      summary: Update a workflow
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                definition:
                  $ref: '#/components/schemas/WorkflowDefinition'
                isActive:
                  type: boolean
      responses:
        '200':
          description: Workflow updated
          content:
            application/json:
              schema:
                type: object
                required:
                  - workflow
                properties:
                  workflow:
                    $ref: '#/components/schemas/Workflow'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteWorkflow
      tags:
        - Workflows
      summary: Delete a workflow
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Workflow deleted
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/workflows/{id}/run:
    post:
      operationId: runWorkflow
      tags:
        - Workflows
      summary: Run a workflow
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                variables:
                  type: object
                  additionalProperties: true
      responses:
        '202':
          description: Workflow run started
          content:
            application/json:
              schema:
                type: object
                required:
                  - run
                properties:
                  run:
                    $ref: '#/components/schemas/WorkflowRun'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/workflows/{id}/runs:
    get:
      operationId: listWorkflowRuns
      tags:
        - Workflows
      summary: List workflow runs
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 25
      responses:
        '200':
          description: Workflow runs
          content:
            application/json:
              schema:
                type: object
                properties:
                  runs:
                    type: array
                    items:
                      $ref: '#/components/schemas/WorkflowRun'
                  total:
                    type: integer
                  page:
                    type: integer
                  limit:
                    type: integer
                  totalPages:
                    type: integer
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/workflows/runs/{runId}:
    get:
      operationId: getWorkflowRun
      tags:
        - Workflows
      summary: Get a workflow run
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: runId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Workflow run details
          content:
            application/json:
              schema:
                type: object
                required:
                  - run
                properties:
                  run:
                    $ref: '#/components/schemas/WorkflowRun'
                  steps:
                    type: array
                    items:
                      $ref: '#/components/schemas/WorkflowRunStep'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/workflows/runs/{runId}/cancel:
    post:
      operationId: cancelWorkflowRun
      tags:
        - Workflows
      summary: Cancel a workflow run
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: runId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Run cancelled
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Run cannot be cancelled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /v1/workflows/runs/{runId}/retry:
    post:
      operationId: retryWorkflowRun
      tags:
        - Workflows
      summary: Retry a workflow run
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: runId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '202':
          description: New run started
          content:
            application/json:
              schema:
                type: object
                required:
                  - run
                properties:
                  run:
                    $ref: '#/components/schemas/WorkflowRun'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/workflows/runs/{runId}/steps/{stepId}/retry:
    post:
      operationId: retryWorkflowStep
      tags:
        - Workflows
      summary: Retry a workflow step
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: runId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: stepId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Step retry requested
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/workflows/runs/{runId}/steps/{stepId}/skip:
    post:
      operationId: skipWorkflowStep
      tags:
        - Workflows
      summary: Skip a workflow step
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: runId
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - name: stepId
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Step skip requested
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /billing/prices:
    get:
      operationId: listPrices
      tags:
        - Billing
      summary: List available prices
      security: []
      responses:
        '200':
          description: Prices
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingPrices'
  /billing/checkout:
    post:
      operationId: createCheckoutSession
      tags:
        - Billing
      summary: Create checkout session
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                productId:
                  type: string
                tierId:
                  type: string
                  enum:
                    - free
                    - starter
                    - pro
                    - business
                mode:
                  type: string
                  enum:
                    - subscription
                    - payment
                credits:
                  type: integer
      responses:
        '200':
          description: Checkout session created
          content:
            application/json:
              schema:
                type: object
                required:
                  - url
                properties:
                  url:
                    type: string
                    format: uri
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /billing/cancel:
    post:
      operationId: cancelSubscription
      tags:
        - Billing
      summary: Cancel active subscription
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Cancellation scheduled
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /billing/invoices:
    get:
      operationId: listInvoices
      tags:
        - Billing
      summary: List invoices
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Invoices list
          content:
            application/json:
              schema:
                type: object
                properties:
                  invoices:
                    type: array
                    items:
                      type: object
                  subscription:
                    $ref: '#/components/schemas/BillingSubscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /billing/credits:
    get:
      operationId: getCredits
      tags:
        - Billing
      summary: Get credit balance and history
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Credit balance
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreditBalance'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /billing/usage:
    get:
      operationId: getUsage
      tags:
        - Billing
      summary: Get usage log history
      security:
        - BearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 30
        - name: type
          in: query
          schema:
            type: string
            enum:
              - storage
              - bandwidth
      responses:
        '200':
          description: Usage logs
          content:
            application/json:
              schema:
                type: object
                required:
                  - usage
                properties:
                  usage:
                    type: array
                    items:
                      $ref: '#/components/schemas/UsageLog'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /billing/subscription:
    get:
      operationId: getSubscription
      tags:
        - Billing
      summary: Get subscription
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Subscription details
          content:
            application/json:
              schema:
                type: object
                required:
                  - subscription
                properties:
                  subscription:
                    $ref: '#/components/schemas/BillingSubscription'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/integrations:
    get:
      operationId: listIntegrations
      tags:
        - Integrations
      summary: List workspace storage integrations
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Integrations
          content:
            application/json:
              schema:
                type: object
                required:
                  - integrations
                properties:
                  integrations:
                    type: array
                    items:
                      $ref: '#/components/schemas/WorkspaceIntegration'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createIntegration
      tags:
        - Integrations
      summary: Create a workspace integration
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkspaceIntegrationInput'
      responses:
        '201':
          description: Integration created
          content:
            application/json:
              schema:
                type: object
                required:
                  - integration
                properties:
                  integration:
                    $ref: '#/components/schemas/WorkspaceIntegration'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/integrations/{id}:
    put:
      operationId: updateIntegration
      tags:
        - Integrations
      summary: Update a workspace integration
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WorkspaceIntegrationInput'
      responses:
        '200':
          description: Integration updated
          content:
            application/json:
              schema:
                type: object
                required:
                  - integration
                properties:
                  integration:
                    $ref: '#/components/schemas/WorkspaceIntegration'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      operationId: deleteIntegration
      tags:
        - Integrations
      summary: Delete a workspace integration
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Integration deleted
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/integrations/{id}/test:
    post:
      operationId: testIntegration
      tags:
        - Integrations
      summary: Test a workspace integration
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Test result
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
                  error:
                    type: string
                    nullable: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/destination:
    get:
      operationId: getDestination
      tags:
        - Integrations
      summary: Get default destination
      description: Backwards-compatible single-destination endpoint.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Destination
          content:
            application/json:
              schema:
                type: object
                required:
                  - destination
                properties:
                  destination:
                    $ref: '#/components/schemas/Destination'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    put:
      operationId: setDestination
      tags:
        - Integrations
      summary: Set default destination
      description: Backwards-compatible single-destination endpoint.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DestinationInput'
      responses:
        '200':
          description: Destination updated
          content:
            application/json:
              schema:
                type: object
                required:
                  - destination
                properties:
                  destination:
                    $ref: '#/components/schemas/Destination'
        '201':
          description: Destination created
          content:
            application/json:
              schema:
                type: object
                required:
                  - destination
                properties:
                  destination:
                    $ref: '#/components/schemas/Destination'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
    delete:
      operationId: deleteDestination
      tags:
        - Integrations
      summary: Delete default destination
      description: Backwards-compatible single-destination endpoint.
      security:
        - BearerAuth: []
      responses:
        '200':
          description: Destination deleted
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/destination/test:
    post:
      operationId: testDestination
      tags:
        - Integrations
      summary: Test default destination
      description: Backwards-compatible single-destination test endpoint.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DestinationInput'
      responses:
        '200':
          description: Test result
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
                  error:
                    type: string
                    nullable: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/api-keys:
    get:
      operationId: listApiKeys
      tags:
        - API Keys
      summary: List API keys
      security:
        - BearerAuth: []
      responses:
        '200':
          description: API keys
          content:
            application/json:
              schema:
                type: object
                required:
                  - keys
                properties:
                  keys:
                    type: array
                    items:
                      $ref: '#/components/schemas/ApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: createApiKey
      tags:
        - API Keys
      summary: Create an API key
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
      responses:
        '200':
          description: API key created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyWithSecret'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/api-keys/{id}:
    delete:
      operationId: deleteApiKey
      tags:
        - API Keys
      summary: Delete an API key
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: API key deleted
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                properties:
                  success:
                    type: boolean
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
        message:
          type: string
        details:
          type: object
    ServiceHealth:
      type: object
      required:
        - status
      properties:
        status:
          type: string
          enum:
            - ok
            - error
            - not_configured
            - unknown
        latencyMs:
          type: integer
          nullable: true
        error:
          type: string
          nullable: true
    VideoJSON:
      type: object
      description: Portable video scene definition produced by `@ilovevideoeditor/core`. The first layer must be a `composition` layer containing project settings (`width`, `height`, `fps`, `sourceDuration`, …).
      required:
        - name
        - layers
      properties:
        name:
          type: string
          maxLength: 200
        layers:
          type: array
          items:
            type: object
          minItems: 1
          maxItems: 50
        tracks:
          type: array
          items:
            type: object
        destinations:
          type: array
          description: Optional output destinations for this render. Each entry references a workspace integration by ID; the API resolves credentials server-side.
          items:
            $ref: '#/components/schemas/VideoJSONDestination'
      additionalProperties: true
    VideoJSONDestination:
      type: object
      description: Reference to a workspace storage integration where the rendered video should be delivered. Credentials are never stored here.
      required:
        - integrationId
        - provider
      properties:
        integrationId:
          type: string
          description: Workspace integration identifier.
        provider:
          type: string
          enum:
            - s3
            - r2
            - b2
            - wasabi
            - gcs
            - azure
            - drive
          description: Storage provider the integration is configured for.
        pathPrefix:
          type: string
          description: Optional path prefix override for this render.
    RenderJob:
      type: object
      required:
        - jobId
        - status
      properties:
        jobId:
          type: string
          format: uuid
        status:
          type: string
          example: completed
        stage:
          type: string
          description: Granular pipeline stage.
          example: completed
        outputKey:
          type: string
          description: Storage key of the output file when completed.
        progress:
          type: object
          properties:
            done:
              type: integer
            total:
              type: integer
            percent:
              type: integer
        url:
          type: string
          format: uri
          nullable: true
          description: Absolute signed download URL when completed.
        outputs:
          type: array
          description: Per-destination outputs when external integrations were used.
          items:
            $ref: '#/components/schemas/RenditionOutput'
        error:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
          nullable: true
      example:
        jobId: 018f1234-5678-7abc-8def-0123456789ab
        status: completed
        stage: completed
        progress:
          done: 1
          total: 1
          percent: 100
        url: https://pub-….r2.dev/…/render.mp4
        error: null
        createdAt: '2026-06-26T00:00:00.000Z'
        completedAt: '2026-06-26T00:01:00.000Z'
    RenderListItem:
      type: object
      description: Flat render summary as returned by `GET /v1/render`.
      required:
        - id
        - status
        - progress
        - createdAt
      properties:
        id:
          type: string
          format: uuid
          description: Render job ID.
        status:
          type: string
          example: completed
        stage:
          type: string
          description: Granular pipeline stage; omitted when unknown.
        progress:
          type: integer
          minimum: 0
          maximum: 100
        url:
          type: string
          format: uri
          nullable: true
          description: Download URL of the rendered MP4 (completed only).
        error:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
          nullable: true
        spriteUrl:
          type: string
          format: uri
          nullable: true
          description: Hover-preview sprite sheet URL, when generated.
        cost:
          type: number
          nullable: true
          description: Credits charged for the render.
        duration:
          type: number
          nullable: true
          description: Render duration in seconds.
        width:
          type: integer
          nullable: true
        height:
          type: integer
          nullable: true
    RenderCostEstimate:
      type: object
      required:
        - cost
        - estimatedDuration
        - resolution
        - fps
      properties:
        cost:
          type: number
          description: Estimated credit cost.
        estimatedDuration:
          type: number
          description: Duration in seconds.
        resolution:
          type: object
          properties:
            width:
              type: integer
            height:
              type: integer
            label:
              type: string
        fps:
          type: integer
        tier:
          type: object
          nullable: true
          properties:
            tier:
              type: string
            credits_balance:
              type: number
            monthly_credits:
              type: number
      example:
        cost: 2
        estimatedDuration: 10
        resolution:
          width: 1920
          height: 1080
          label: 1080p
        fps: 30
    TierInfo:
      type: object
      required:
        - tier
        - credits
      properties:
        tier:
          type: string
          enum:
            - free
            - starter
            - pro
            - business
            - enterprise
        credits:
          type: object
          properties:
            balance:
              type: number
            monthly:
              type: number
            max:
              type: number
      example:
        tier: pro
        credits:
          balance: 87
          monthly: 100
          max: 100
    ToolJobRequest:
      type: object
      required:
        - inputUrl
        - config
      properties:
        inputUrl:
          type: string
          format: uri
          description: Public or presigned URL of the input media file.
        config:
          type: object
          additionalProperties: true
          description: Tool-specific configuration (start/end, crop box, speed factor, etc.).
        webhookUrl:
          type: string
          format: uri
          description: Optional URL to receive job completion/failure events.
        duration:
          type: number
          minimum: 0
          description: Optional known duration in seconds (skips probing for studio tools).
        dimensions:
          type: object
          required:
            - width
            - height
          properties:
            width:
              type: integer
              minimum: 1
            height:
              type: integer
              minimum: 1
    ToolJobStatus:
      type: object
      required:
        - jobId
        - status
        - stage
      properties:
        jobId:
          type: string
          format: uuid
        status:
          type: string
          example: processing
        stage:
          type: string
          example: processing
        mode:
          type: string
          enum:
            - native
            - render
            - unknown
          description: Whether the job ran on the native ffmpeg worker or the render pipeline.
        outputKey:
          type: string
          description: Storage key of the output file when completed.
        url:
          type: string
          format: uri
          description: Direct download URL when completed.
        error:
          type: string
        progress:
          type: object
          properties:
            done:
              type: integer
            total:
              type: integer
            percent:
              type: integer
        outputs:
          type: array
          description: Per-destination outputs when external integrations were used.
          items:
            $ref: '#/components/schemas/RenditionOutput'
    TemplateSummary:
      type: object
      required:
        - id
        - name
        - platform
        - accentColor
        - icon
        - mode
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        platform:
          type: string
          enum:
            - youtube
            - tiktok
            - instagram
            - general
        accentColor:
          type: string
        icon:
          type: string
        mode:
          type: string
          enum:
            - preset
            - project
        toolId:
          type: string
          nullable: true
        variablesSchema:
          type: array
          items:
            type: object
            additionalProperties: true
    Template:
      type: object
      description: Full template including its preset configuration or project builder metadata.
      required:
        - id
        - name
        - platform
        - accentColor
        - icon
        - mode
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
        platform:
          type: string
          enum:
            - youtube
            - tiktok
            - instagram
            - general
        accentColor:
          type: string
        icon:
          type: string
        mode:
          type: string
          enum:
            - preset
            - project
        toolId:
          type: string
          nullable: true
        variablesSchema:
          type: array
          items:
            type: object
            additionalProperties: true
          nullable: true
      example:
        id: tpl_summer_sale
        name: Summer Sale
        description: Bold vertical promo with animated headline.
        platform: instagram
        accentColor: '#ff3366'
        icon: 🛍️
        mode: preset
        variablesSchema:
          - key: headline
            label: Headline
            type: text
            required: true
            default: SUMMER SALE
          - key: cta
            label: Button text
            type: text
            default: Shop Now
    Asset:
      type: object
      required:
        - id
        - workspace_id
        - name
        - type
        - storage_key
        - size_bytes
        - created_at
      properties:
        id:
          type: string
          format: uuid
        workspace_id:
          type: string
          format: uuid
        name:
          type: string
        type:
          type: string
          enum:
            - image
            - video
            - audio
            - font
        storage_key:
          type: string
          description: Bunny Storage path.
        asset_url:
          type: string
          format: uri
          description: Public Bunny CDN URL for the asset.
        size_bytes:
          type: integer
        duration:
          type: integer
          nullable: true
        width:
          type: integer
          nullable: true
        height:
          type: integer
          nullable: true
        created_by:
          type: string
          format: uuid
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      example:
        id: 4d7d0e0c-8f73-4a2b-9e1c-b1a8e5f9c3d2
        workspace_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
        name: logo.png
        type: image
        storage_key: a1b2c3d4/assets/4d7d0e0c/logo.png
        asset_url: https://cdn.ilovevideoeditor.com/a1b2c3d4/assets/4d7d0e0c/logo.png
        size_bytes: 48291
        width: 512
        height: 512
        created_at: '2025-06-01T12:00:00Z'
    AssetCreate:
      type: object
      required:
        - id
        - name
        - type
        - storage_key
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        type:
          type: string
          enum:
            - image
            - video
            - audio
            - font
        storage_key:
          type: string
          description: Bunny Storage path (must start with workspace_id/).
        size_bytes:
          type: integer
        duration:
          type: integer
        width:
          type: integer
        height:
          type: integer
    AssetUploadUrlResponse:
      type: object
      required:
        - uploadUrl
        - assetUrl
        - path
        - assetId
        - type
      properties:
        uploadUrl:
          type: string
          format: uri
          description: Relative URL to POST the file to (`/v1/assets/{assetId}/upload`).
        assetUrl:
          type: string
          format: uri
          description: Public Bunny CDN URL once uploaded.
        path:
          type: string
          description: Bunny Storage path.
        assetId:
          type: string
          format: uuid
        type:
          type: string
          enum:
            - image
            - video
            - audio
            - font
    Project:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        video_json:
          type: object
          nullable: true
          description: Project VideoJSON payload.
        thumbnail_url:
          type: string
          format: uri
          nullable: true
        sprite_url:
          type: string
          format: uri
          nullable: true
        duration:
          type: integer
          nullable: true
        width:
          type: integer
          nullable: true
        height:
          type: integer
          nullable: true
        status:
          type: string
          nullable: true
        render_job_id:
          type: string
          nullable: true
        output_url:
          type: string
          format: uri
          nullable: true
        processed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        last_opened_at:
          type: string
          format: date-time
          nullable: true
    ProjectCreate:
      type: object
      required:
        - name
      properties:
        name:
          type: string
        description:
          type: string
        video_json:
          type: object
        thumbnail_url:
          type: string
          format: uri
        duration:
          type: integer
        width:
          type: integer
        height:
          type: integer
    ProjectUpdate:
      type: object
      properties:
        name:
          type: string
        description:
          type: string
        video_json:
          type: object
        thumbnail_url:
          type: string
          format: uri
        thumbnail_base64:
          type: string
          description: Base64-encoded thumbnail image. Uploaded to Bunny Storage.
        sprite_url:
          type: string
          format: uri
        sprite_base64:
          type: string
          description: Base64-encoded sprite sheet. Uploaded to Bunny Storage.
        last_opened_at:
          type: string
          format: date-time
        duration:
          type: integer
        width:
          type: integer
        height:
          type: integer
    Rendition:
      type: object
      properties:
        id:
          type: string
          format: uuid
        job_id:
          type: string
          nullable: true
        project_id:
          type: string
          format: uuid
          nullable: true
        status:
          type: string
        stage:
          type: string
        cost:
          type: number
          nullable: true
        duration:
          type: number
          nullable: true
        width:
          type: integer
          nullable: true
        height:
          type: integer
          nullable: true
        fps:
          type: integer
          nullable: true
        output_url:
          type: string
          format: uri
          nullable: true
        output_size_bytes:
          type: integer
          nullable: true
        error:
          type: string
          nullable: true
        progress_percent:
          type: integer
          nullable: true
        sprite_url:
          type: string
          format: uri
          nullable: true
        video_json:
          type: object
          nullable: true
        outputs:
          type: array
          items:
            $ref: '#/components/schemas/RenditionOutput'
        projects:
          type: object
          nullable: true
        created_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
          nullable: true
    RenditionOutput:
      type: object
      properties:
        id:
          type: string
          format: uuid
        integration_id:
          type: string
          format: uuid
          nullable: true
        provider:
          type: string
        output_key:
          type: string
          nullable: true
        output_url:
          type: string
          format: uri
          nullable: true
        output_size_bytes:
          type: integer
          nullable: true
        status:
          type: string
        error:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    RenditionStats:
      type: object
      properties:
        total:
          type: integer
        completed:
          type: integer
        failed:
          type: integer
        processing:
          type: integer
        queued:
          type: integer
        rendering:
          type: integer
    RenditionCancelResult:
      type: object
      required:
        - id
        - status
        - refunded
      properties:
        id:
          type: string
          format: uuid
        jobId:
          type: string
          nullable: true
        status:
          type: string
        refunded:
          type: number
        error:
          type: string
          nullable: true
    BatchDeleteResult:
      type: object
      required:
        - count
      properties:
        deleted:
          type: array
          items:
            type: string
        count:
          type: integer
    WebhookSubscription:
      type: object
      required:
        - id
        - url
        - events
        - secret
        - createdAt
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
            enum:
              - render.completed
              - render.failed
        secret:
          type: string
          description: Per-subscription signing secret (`whsec_…`).
        createdAt:
          type: string
          format: date-time
    WebhookSubscriptionCreate:
      type: object
      required:
        - url
      properties:
        url:
          type: string
          format: uri
          description: Endpoint that receives signed render events.
        events:
          type: array
          minItems: 1
          description: Events to subscribe to; defaults to all events.
          items:
            type: string
            enum:
              - render.completed
              - render.failed
    RenderWebhookPayload:
      type: object
      description: JSON body POSTed to webhook targets on terminal render states.
      required:
        - event
        - id
        - status
      properties:
        event:
          type: string
          enum:
            - render.completed
            - render.failed
        id:
          type: string
          format: uuid
          description: Render job ID.
        status:
          type: string
          enum:
            - completed
            - failed
        stage:
          type: string
        progress:
          type: integer
        url:
          type: string
          format: uri
          nullable: true
        outputs:
          type: array
          items:
            type: object
            additionalProperties: true
        duration:
          type: number
        width:
          type: integer
        height:
          type: integer
        templateId:
          type: string
        cost:
          type: number
        error:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time
        completedAt:
          type: string
          format: date-time
          nullable: true
    Workflow:
      type: object
      required:
        - id
        - workspaceId
        - createdBy
        - name
        - definition
        - isActive
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        workspaceId:
          type: string
          format: uuid
        createdBy:
          type: string
          format: uuid
        name:
          type: string
        description:
          type: string
          nullable: true
        definition:
          $ref: '#/components/schemas/WorkflowDefinition'
        isActive:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    WorkflowDefinition:
      type: object
      required:
        - version
        - variables
        - steps
      properties:
        version:
          type: integer
          enum:
            - 1
        variables:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/WorkflowVariable'
        steps:
          type: array
          minItems: 1
          maxItems: 20
          items:
            $ref: '#/components/schemas/WorkflowStep'
    WorkflowVariable:
      type: object
      required:
        - type
        - label
      properties:
        type:
          type: string
          enum:
            - string
            - number
            - boolean
            - asset_url
            - url
            - text
        label:
          type: string
        required:
          type: boolean
        default: {}
        placeholder:
          type: string
    WorkflowStep:
      type: object
      required:
        - type
        - config
      properties:
        type:
          type: string
          enum:
            - upload_asset
            - crawl_url
            - discover_variables
            - render_template
            - render_videojson
            - apply_tool
            - send_to_destination
        name:
          type: string
        maxRetries:
          type: integer
          minimum: 0
          maximum: 10
        config:
          type: object
          additionalProperties: true
    WorkflowRun:
      type: object
      required:
        - id
        - workflowId
        - workspaceId
        - status
        - trigger
        - variables
      properties:
        id:
          type: string
          format: uuid
        workflowId:
          type: string
          format: uuid
        workspaceId:
          type: string
          format: uuid
        triggeredBy:
          type: string
          format: uuid
          nullable: true
        trigger:
          type: string
        status:
          type: string
        variables:
          type: object
        estimatedCost:
          type: number
          nullable: true
        totalCost:
          type: number
          nullable: true
        error:
          type: string
          nullable: true
        startedAt:
          type: string
          format: date-time
          nullable: true
        completedAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    WorkflowRunStep:
      type: object
      required:
        - id
        - workflowRunId
        - stepIndex
        - stepType
      properties:
        id:
          type: string
          format: uuid
        workflowRunId:
          type: string
          format: uuid
        stepIndex:
          type: integer
        stepType:
          type: string
        name:
          type: string
          nullable: true
        config:
          type: object
          nullable: true
        status:
          type: string
        inputUrl:
          type: string
          format: uri
          nullable: true
        outputUrl:
          type: string
          format: uri
          nullable: true
        outputKey:
          type: string
          nullable: true
        renditionId:
          type: string
          format: uri
          nullable: true
        error:
          type: string
          nullable: true
        responseStatus:
          type: integer
          nullable: true
        metadata:
          type: object
          nullable: true
        retryCount:
          type: integer
        maxRetries:
          type: integer
        logs:
          type: array
          items:
            type: object
          nullable: true
        startedAt:
          type: string
          format: date-time
          nullable: true
        completedAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    WorkspaceIntegration:
      type: object
      required:
        - id
        - workspaceId
        - provider
        - name
        - isActive
        - isDefault
        - config
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
          format: uuid
        workspaceId:
          type: string
          format: uuid
        provider:
          type: string
          enum:
            - s3
            - r2
            - b2
            - wasabi
            - gcs
            - azure
            - drive
        name:
          type: string
        isActive:
          type: boolean
        isDefault:
          type: boolean
        config:
          type: object
          additionalProperties: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    WorkspaceIntegrationInput:
      type: object
      required:
        - provider
        - name
        - config
      properties:
        provider:
          type: string
          enum:
            - s3
            - r2
            - b2
            - wasabi
            - gcs
            - azure
            - drive
        name:
          type: string
        isActive:
          type: boolean
        isDefault:
          type: boolean
        config:
          type: object
          additionalProperties: true
    Destination:
      type: object
      required:
        - id
        - provider
        - bucket
        - accessKeyId
        - secretAccessKey
      properties:
        id:
          type: string
          format: uuid
        provider:
          type: string
          enum:
            - s3
            - r2
            - b2
            - wasabi
        endpoint:
          type: string
          format: uri
          nullable: true
        region:
          type: string
          nullable: true
        bucket:
          type: string
        accessKeyId:
          type: string
        secretAccessKey:
          type: string
        publicUrl:
          type: string
          format: uri
          nullable: true
        pathPrefix:
          type: string
    DestinationInput:
      type: object
      required:
        - provider
        - bucket
        - accessKeyId
        - secretAccessKey
      properties:
        provider:
          type: string
          enum:
            - s3
            - r2
            - b2
            - wasabi
        endpoint:
          type: string
          format: uri
        region:
          type: string
        bucket:
          type: string
        accessKeyId:
          type: string
        secretAccessKey:
          type: string
        publicUrl:
          type: string
          format: uri
        pathPrefix:
          type: string
          default: ''
    ApiKey:
      type: object
      required:
        - id
        - key_prefix
        - created_at
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
          nullable: true
        key_prefix:
          type: string
        last_used_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        revoked_at:
          type: string
          format: date-time
          nullable: true
    ApiKeyWithSecret:
      type: object
      required:
        - key
        - keyData
      properties:
        key:
          type: string
          description: Full API key secret. Shown only once on creation.
        keyData:
          $ref: '#/components/schemas/ApiKey'
    BillingPrices:
      type: object
      properties:
        tiers:
          type: object
          properties:
            starter:
              type: string
              nullable: true
            pro:
              type: string
              nullable: true
            business:
              type: string
              nullable: true
        credits:
          type: object
          additionalProperties:
            type: string
            nullable: true
    BillingSubscription:
      type: object
      required:
        - status
        - currentPeriodEnd
        - creditsBalance
        - creditsTotal
        - tier
      properties:
        status:
          type: string
        currentPeriodEnd:
          type: string
          format: date-time
          nullable: true
        creditsBalance:
          type: number
        creditsTotal:
          type: number
        tier:
          type: string
    CreditBalance:
      type: object
      required:
        - balance
        - total
      properties:
        balance:
          type: number
        total:
          type: number
        summary:
          type: object
          properties:
            purchased:
              type: number
            consumed:
              type: number
            refunded:
              type: number
            consumed30Days:
              type: number
        usage:
          type: object
          properties:
            storage:
              type: object
            bandwidth:
              type: object
        logs:
          type: array
          items:
            type: object
    UsageLog:
      type: object
      required:
        - id
        - type
        - bytes
        - source
        - created_at
      properties:
        id:
          type: string
          format: uuid
        type:
          type: string
          enum:
            - storage
            - bandwidth
        bytes:
          type: integer
        source:
          type: string
        metadata:
          type: object
          nullable: true
        created_at:
          type: string
          format: date-time
  responses:
    BadRequest:
      description: Invalid request body
      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
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
