openapi: 3.1.0
info:
  title: Shotmatic External API
  version: 1.0.0-draft
  summary: Public API for scripted video creation, clip extraction, and workflow execution.
  description: |
    Proposed external API for Shotmatic.

    This contract intentionally differs from the current internal `app/api/*` routes.
    The public surface is resource-oriented, provider-blind, versioned under `/v1`,
    and designed to support three creation modes:

    1. Interactive creation through `video-plans`
       - create a draft plan
       - review and edit script, scenes, voice, and render options
       - approve the plan to start rendering

    2. Fully automated creation through `videos`
       - submit a one-shot request
       - let the system plan and render without a human approval step

    3. Reusable headless creation through `workflow-profiles`
       - author a reusable workflow profile from a long description or explicit blocks
       - bind team, brand, or presenter defaults through workflow-profile bindings
       - run each job through `sessions` with `source.kind=workflow_profile` or `source.kind=workflow_profile_binding`

    Voice identifiers are canonical entity IDs and do not expose whether the
    underlying TTS engine is Fish Audio, ElevenLabs, or another provider.

    When `webhook_secret` is provided on video creation or plan approval,
    Shotmatic signs outbound webhook deliveries with:
    - `X-Shotmatic-Timestamp`
    - `X-Shotmatic-Signature`

    Consumers should verify `v1=<hex_hmac_sha256>` over
    `${timestamp}.${raw_json_body}` and reject timestamps older than 5 minutes.

    This API is also the intended substrate for a future MCP server. MCP tools
    should map onto these resources instead of calling UI-shaped internal routes.
servers:
  - url: https://api.shotmatic.com/v1
    description: Production
  - url: https://staging-api.shotmatic.com/v1
    description: Staging
  - url: http://localhost:3000/v1
    description: Local development
tags:
  - name: Capabilities
  - name: API Keys
  - name: Voices
  - name: Media
  - name: Workflow Profiles
  - name: Workflow Profile Bindings
  - name: Presenter Profiles
  - name: Video Plans
  - name: Videos
  - name: Clip Tasks
  - name: Workflows
  - name: Webhooks
  - name: Usage
security:
  - bearerAuth: []
paths:
  /capabilities:
    get:
      tags:
        - Capabilities
      operationId: getCapabilities
      summary: Discover supported creation surfaces and option sets
      security: []
      responses:
        '200':
          description: Capability catalog
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /api-keys:
    get:
      tags: [API Keys]
      operationId: listApiKeys
      summary: List managed API keys
      description: |
        Lists database-backed API keys for the currently authenticated Shotmatic app session.

        This management surface is session-authenticated today. Managed API keys created
        here are intended to be used against the other `/v1` resources.
      responses:
        '200':
          description: API key registry
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyCollection'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags: [API Keys]
      operationId: createApiKey
      summary: Create a managed API key
      description: |
        Creates a new database-backed API key and returns the plaintext token once.

        This operation requires an authenticated Shotmatic app session and does not
        accept an existing managed API key for self-service key creation.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateApiKeyRequest'
            examples:
              scopedKey:
                summary: Create a scoped key for video automation
                value:
                  name: Automation key
                  scopes: ['voices:read', 'videos:read', 'videos:write']
                  expires_at: '2026-12-31T23:59:59Z'
                  metadata:
                    environment: production
      responses:
        '201':
          description: API key created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKeyWithToken'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationError'
  /api-keys/{keyId}:
    get:
      tags: [API Keys]
      operationId: getApiKey
      summary: Get one managed API key
      description: Returns metadata for a single managed API key owned by the current app session user.
      parameters:
        - $ref: '#/components/parameters/KeyId'
      responses:
        '200':
          description: API key metadata
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [API Keys]
      operationId: updateApiKey
      summary: Update one managed API key
      description: Updates the name, scopes, expiry, or metadata for a key owned by the current app session user.
      parameters:
        - $ref: '#/components/parameters/KeyId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateApiKeyRequest'
      responses:
        '200':
          description: Updated API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'
    delete:
      tags: [API Keys]
      operationId: revokeApiKey
      summary: Revoke one managed API key
      description: Revokes a key owned by the current app session user. Revoked keys can no longer be used for bearer-authenticated `/v1` access.
      parameters:
        - $ref: '#/components/parameters/KeyId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Revoked API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiKey'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /voices:
    get:
      tags: [Voices]
      operationId: listVoices
      summary: List available voices
      description: Returns provider-blind canonical voice entities available to the account.
      parameters:
        - in: query
          name: language
          schema:
            type: string
          description: Filter by language or locale, for example `en` or `en-US`.
        - in: query
          name: active_only
          schema:
            type: boolean
            default: true
      responses:
        '200':
          description: Voice catalog
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Voice'
                required: [data]
  /voices/{voiceId}:
    get:
      tags: [Voices]
      operationId: getVoice
      summary: Get one voice
      parameters:
        - $ref: '#/components/parameters/VoiceId'
      responses:
        '200':
          description: Voice details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Voice'
        '404':
          $ref: '#/components/responses/NotFound'
  /voices/{voiceId}/preview:
    get:
      tags: [Voices]
      operationId: previewVoice
      summary: Generate a short voice preview
      parameters:
        - $ref: '#/components/parameters/VoiceId'
        - in: query
          name: text
          description: Optional short preview text. Server may truncate to a safe preview length.
          schema:
            type: string
            maxLength: 220
      responses:
        '200':
          description: Preview audio
          content:
            audio/mpeg:
              schema:
                type: string
                format: binary
        '404':
          $ref: '#/components/responses/NotFound'

  /workflow-profiles:
    get:
      tags: [Workflow Profiles]
      operationId: listWorkflowProfiles
      summary: List published workflow profiles
      responses:
        '200':
          description: Workflow profile collection
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: './components/schemas.yaml#/WorkflowProfile'
                required: [data]
    post:
      tags: [Workflow Profiles]
      operationId: createWorkflowProfileDraft
      summary: Create a workflow profile draft from explicit authored blocks
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: './components/schemas.yaml#/WorkflowProfileDraftCreateRequest'
      responses:
        '201':
          description: Workflow profile draft created
          content:
            application/json:
              schema:
                $ref: './components/schemas.yaml#/WorkflowProfileDraft'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationError'

  /workflow-profiles/generate:
    post:
      tags: [Workflow Profiles]
      operationId: generateWorkflowProfileDraft
      summary: Generate a workflow profile draft from a long description
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: './components/schemas.yaml#/WorkflowProfileGenerateRequest'
      responses:
        '201':
          description: Generated workflow profile draft
          content:
            application/json:
              schema:
                $ref: './components/schemas.yaml#/WorkflowProfileDraft'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationError'

  /workflow-profiles/{profileKey}:
    get:
      tags: [Workflow Profiles]
      operationId: getWorkflowProfile
      summary: Get the latest draft and published version for a workflow profile
      parameters:
        - $ref: '#/components/parameters/ProfileKey'
        - in: query
          name: version
          required: false
          schema:
            type: string
          description: |
            When provided, returns that immutable published workflow profile version
            instead of the latest draft/published detail wrapper.
      responses:
        '200':
          description: Workflow profile detail or explicit version
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: './components/schemas.yaml#/WorkflowProfileDetail'
                  - $ref: './components/schemas.yaml#/WorkflowProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Workflow Profiles]
      operationId: updateWorkflowProfileDraft
      summary: Update the latest editable workflow profile draft
      parameters:
        - $ref: '#/components/parameters/ProfileKey'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: './components/schemas.yaml#/WorkflowProfileDraftUpdateRequest'
      responses:
        '200':
          description: Updated workflow profile draft
          content:
            application/json:
              schema:
                $ref: './components/schemas.yaml#/WorkflowProfileDraft'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /workflow-profiles/{profileKey}/preview:
    post:
      tags: [Workflow Profiles]
      operationId: previewWorkflowProfile
      summary: Preview an authored workflow profile against optional sample inputs
      parameters:
        - $ref: '#/components/parameters/ProfileKey'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                sample_creative_package:
                  $ref: './components/schemas.yaml#/CreativePackage'
      responses:
        '200':
          description: Workflow profile preview
          content:
            application/json:
              schema:
                $ref: './components/schemas.yaml#/WorkflowProfilePreview'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'

  /workflow-profiles/{profileKey}/publish:
    post:
      tags: [Workflow Profiles]
      operationId: publishWorkflowProfile
      summary: Publish the latest reviewed draft into an immutable version
      parameters:
        - $ref: '#/components/parameters/ProfileKey'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: './components/schemas.yaml#/WorkflowProfilePublishRequest'
      responses:
        '201':
          description: Published workflow profile version
          content:
            application/json:
              schema:
                $ref: './components/schemas.yaml#/WorkflowProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /workflow-profile-bindings:
    get:
      tags: [Workflow Profile Bindings]
      operationId: listWorkflowProfileBindings
      summary: List reusable workflow-profile bindings
      parameters:
        - in: query
          name: profile_key
          schema:
            type: string
        - in: query
          name: scope_level
          schema:
            type: string
            enum: [organization, team, user]
        - in: query
          name: external_id
          schema:
            type: string
      responses:
        '200':
          description: Workflow profile binding collection
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: './components/schemas.yaml#/WorkflowProfileBinding'
                required: [data]
    post:
      tags: [Workflow Profile Bindings]
      operationId: createWorkflowProfileBinding
      summary: Create a reusable workflow-profile binding
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: './components/schemas.yaml#/WorkflowProfileBindingRequest'
      responses:
        '201':
          description: Workflow profile binding created
          content:
            application/json:
              schema:
                $ref: './components/schemas.yaml#/WorkflowProfileBinding'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationError'

  /workflow-profile-bindings/{bindingId}:
    get:
      tags: [Workflow Profile Bindings]
      operationId: getWorkflowProfileBinding
      summary: Get one workflow-profile binding
      parameters:
        - $ref: '#/components/parameters/BindingId'
      responses:
        '200':
          description: Workflow profile binding
          content:
            application/json:
              schema:
                $ref: './components/schemas.yaml#/WorkflowProfileBinding'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Workflow Profile Bindings]
      operationId: updateWorkflowProfileBinding
      summary: Update one workflow-profile binding
      parameters:
        - $ref: '#/components/parameters/BindingId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: './components/schemas.yaml#/WorkflowProfileBindingRequest'
      responses:
        '200':
          description: Updated workflow profile binding
          content:
            application/json:
              schema:
                $ref: './components/schemas.yaml#/WorkflowProfileBinding'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /presenter-profiles:
    get:
      tags: [Presenter Profiles]
      operationId: listPresenterProfiles
      summary: List reusable presenter profiles
      responses:
        '200':
          description: Presenter profile collection
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: './components/schemas.yaml#/PresenterProfile'
                required: [data]
    post:
      tags: [Presenter Profiles]
      operationId: createPresenterProfile
      summary: Create a reusable presenter profile
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: './components/schemas.yaml#/PresenterProfileRequest'
      responses:
        '201':
          description: Presenter profile created
          content:
            application/json:
              schema:
                $ref: './components/schemas.yaml#/PresenterProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '422':
          $ref: '#/components/responses/ValidationError'

  /presenter-profiles/{presenterProfileId}:
    get:
      tags: [Presenter Profiles]
      operationId: getPresenterProfile
      summary: Get one presenter profile
      parameters:
        - $ref: '#/components/parameters/PresenterProfileId'
      responses:
        '200':
          description: Presenter profile
          content:
            application/json:
              schema:
                $ref: './components/schemas.yaml#/PresenterProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Presenter Profiles]
      operationId: updatePresenterProfile
      summary: Update one presenter profile
      parameters:
        - $ref: '#/components/parameters/PresenterProfileId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: './components/schemas.yaml#/PresenterProfileRequest'
      responses:
        '200':
          description: Updated presenter profile
          content:
            application/json:
              schema:
                $ref: './components/schemas.yaml#/PresenterProfile'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '422':
          $ref: '#/components/responses/ValidationError'

  /media:
    get:
      tags: [Media]
      operationId: listMedia
      summary: List uploaded media
      parameters:
        - in: query
          name: type
          schema:
            type: string
            enum: [video, image, audio]
        - in: query
          name: sort
          schema:
            type: string
            enum: [recent, name, size]
            default: recent
      responses:
        '200':
          description: Media library
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/MediaAsset'
                  total:
                    type: integer
                    minimum: 0
                required: [data, total]
    post:
      tags: [Media]
      operationId: uploadMedia
      summary: Upload source media
      description: |
        Uploads a source asset that can later be used in a clip task or video plan.
        This endpoint is intended for public API consumers and replaces the current
        UI-specific upload flow.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                kind:
                  type: string
                  enum: [video, image, audio]
                title:
                  type: string
                metadata:
                  type: string
                  description: JSON-encoded metadata blob.
              required: [file, kind]
      responses:
        '201':
          description: Uploaded media asset
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MediaAsset'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '413':
          description: File too large
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /media/{mediaId}:
    get:
      tags: [Media]
      operationId: getMedia
      summary: Get one media asset
      parameters:
        - $ref: '#/components/parameters/MediaId'
      responses:
        '200':
          description: Media details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MediaAsset'
        '404':
          $ref: '#/components/responses/NotFound'

  /video-plans:
    get:
      tags: [Video Plans]
      operationId: listVideoPlans
      summary: List video plans
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
        - in: query
          name: status
          schema:
            $ref: '#/components/schemas/VideoPlanStatus'
      responses:
        '200':
          description: Video plans
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/VideoPlan'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
                required: [data, pagination]
    post:
      tags: [Video Plans]
      operationId: createVideoPlan
      summary: Create an interactive video plan
      description: |
        Accepts a new interactive plan request and starts draft generation in the background.

        Use this mode when the client wants a human-in-the-loop workflow:
        script generation, scene planning, voice selection, and render settings
        can all be adjusted before approval.

        The initial response returns a persisted `drafting` plan immediately.
        Clients should poll `GET /video-plans/{planId}` until the plan reaches
        `ready_for_review` or `failed`.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateVideoPlanRequest'
            examples:
              articlePlan:
                summary: Draft a plan from an article
                value:
                  source:
                    kind: article_url
                    article_url: https://example.com/viral-story
                  goal:
                    title: How this product launch went viral
                    language: en-US
                    duration_seconds: 45
                    visual_style: ugc
                    target_platform: tiktok
                  defaults:
                    voice:
                      id: 728f6ff2240d49308e8137ffe66008e2
                    render:
                      aspect_ratio: 9:16
                      media_type: images_only
                      image_quality: standard
                      include_soundtrack: true
      responses:
        '202':
          description: Draft generation accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoPlan'
        '422':
          $ref: '#/components/responses/ValidationError'
  /video-plans/{planId}:
    get:
      tags: [Video Plans]
      operationId: getVideoPlan
      summary: Get one video plan
      parameters:
        - $ref: '#/components/parameters/PlanId'
      responses:
        '200':
          description: Video plan
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoPlan'
        '404':
          $ref: '#/components/responses/NotFound'
    patch:
      tags: [Video Plans]
      operationId: updateVideoPlan
      summary: Update an interactive video plan
      description: |
        Applies user edits before approval. This is the primary API for the
        interactive creation flow.
      parameters:
        - $ref: '#/components/parameters/PlanId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateVideoPlanRequest'
      responses:
        '200':
          description: Updated plan
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoPlan'
        '409':
          description: Plan is no longer editable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /video-plans/{planId}/approve:
    post:
      tags: [Video Plans]
      operationId: approveVideoPlan
      summary: Approve a video plan and start rendering
      parameters:
        - $ref: '#/components/parameters/PlanId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ApproveVideoPlanRequest'
      responses:
        '202':
          description: Rendering accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Video'
        '409':
          description: Plan already approved or no longer valid
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /videos:
    get:
      tags: [Videos]
      operationId: listVideos
      summary: List videos
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
        - in: query
          name: status
          schema:
            $ref: '#/components/schemas/VideoStatus'
      responses:
        '200':
          description: Videos
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Video'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
                required: [data, pagination]
    post:
      tags: [Videos]
      operationId: createVideo
      summary: Create a video in fully automated mode
      description: |
        One-shot API for clients that do not need a review step.
        Shotmatic will plan, generate, and render immediately.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateVideoRequest'
            examples:
              oneShotScript:
                summary: Generate a video directly from a script
                value:
                  source:
                    kind: script
                    script: |
                      Three hooks that stop the scroll.
                      First, start with a surprising claim.
                      Second, show the proof immediately.
                      Third, end with one simple CTA.
                  title: Three hooks that stop the scroll
                  language: en-US
                  voice:
                    id: 728f6ff2240d49308e8137ffe66008e2
                  render:
                    aspect_ratio: 9:16
                    duration_seconds: 30
                    visual_style: ugc
                    media_type: images_only
                    image_quality: standard
                    include_soundtrack: true
                  webhook_url: https://client.example.com/hooks/shotmatic
                  webhook_secret: whsec_client_shared_secret
      responses:
        '202':
          description: Video queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Video'
  /videos/{videoId}:
    get:
      tags: [Videos]
      operationId: getVideo
      summary: Get one video job
      parameters:
        - $ref: '#/components/parameters/VideoId'
      responses:
        '200':
          description: Video details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Video'
        '404':
          $ref: '#/components/responses/NotFound'
  /videos/{videoId}/cancel:
    post:
      tags: [Videos]
      operationId: cancelVideo
      summary: Cancel a queued or running video
      parameters:
        - $ref: '#/components/parameters/VideoId'
      responses:
        '202':
          description: Cancellation requested
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  status:
                    $ref: '#/components/schemas/VideoStatus'
                required: [id, status]

  /clip-tasks:
    post:
      tags: [Clip Tasks]
      operationId: createClipTask
      summary: Create a clip extraction task
      description: |
        Starts transcription and clip sectioning for either an uploaded file
        or a source URL.
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateClipTaskRequest'
          multipart/form-data:
            schema:
              allOf:
                - $ref: '#/components/schemas/CreateClipTaskUploadRequest'
      responses:
        '202':
          description: Clip task queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClipTask'
  /clip-tasks/{clipTaskId}:
    get:
      tags: [Clip Tasks]
      operationId: getClipTask
      summary: Get one clip task
      parameters:
        - $ref: '#/components/parameters/ClipTaskId'
      responses:
        '200':
          description: Clip task
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ClipTask'
        '404':
          $ref: '#/components/responses/NotFound'
  /clip-tasks/{clipTaskId}/render:
    post:
      tags: [Clip Tasks]
      operationId: renderClipSelection
      summary: Render one or more selected clips into videos
      parameters:
        - $ref: '#/components/parameters/ClipTaskId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RenderClipSelectionRequest'
      responses:
        '202':
          description: Render accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Video'
                  video_id:
                    type: string
                    description: Present when the render request creates a single video.
                  video:
                    $ref: '#/components/schemas/Video'
                required: [data]

  /workflows:
    get:
      tags: [Workflows]
      operationId: listWorkflows
      summary: List workflows available to the caller
      responses:
        '200':
          description: Workflow catalog
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Workflow'
                required: [data]
  /workflows/{workflowId}/executions:
    get:
      tags: [Workflows]
      operationId: listWorkflowExecutions
      summary: List executions for one workflow
      parameters:
        - $ref: '#/components/parameters/WorkflowId'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      responses:
        '200':
          description: Workflow executions
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WorkflowExecution'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
                required: [data, pagination]
    post:
      tags: [Workflows]
      operationId: executeWorkflow
      summary: Start a workflow execution
      parameters:
        - $ref: '#/components/parameters/WorkflowId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWorkflowExecutionRequest'
      responses:
        '202':
          description: Workflow execution queued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowExecution'
  /workflow-executions/{executionId}:
    get:
      tags: [Workflows]
      operationId: getWorkflowExecution
      summary: Get one workflow execution
      parameters:
        - $ref: '#/components/parameters/ExecutionId'
      responses:
        '200':
          description: Workflow execution
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkflowExecution'
        '404':
          $ref: '#/components/responses/NotFound'

  /webhooks:
    get:
      tags: [Webhooks]
      operationId: listWebhookEndpoints
      summary: List webhook endpoints
      responses:
        '200':
          description: Webhook endpoints
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/WebhookEndpoint'
                required: [data]
    post:
      tags: [Webhooks]
      operationId: createWebhookEndpoint
      summary: Register a webhook endpoint
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWebhookRequest'
      responses:
        '201':
          description: Webhook endpoint created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookEndpoint'

  /webhooks/deliveries:
    get:
      tags: [Webhooks]
      operationId: listWebhookDeliveries
      summary: List webhook deliveries
      description: Lists outbound webhook delivery attempts, retry state, and the last failure details for the current account.
      parameters:
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
        - in: query
          name: status
          schema:
            $ref: '#/components/schemas/WebhookDeliveryStatus'
        - in: query
          name: event_type
          schema:
            $ref: '#/components/schemas/WebhookEventType'
        - in: query
          name: video_id
          schema:
            type: string
      responses:
        '200':
          description: Webhook delivery attempts
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDeliveryCollection'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '422':
          $ref: '#/components/responses/ValidationError'

  /webhooks/deliveries/{deliveryId}:
    get:
      tags: [Webhooks]
      operationId: getWebhookDelivery
      summary: Get one webhook delivery
      description: Returns a single outbound webhook delivery with debug fields such as the response body and payload.
      parameters:
        - $ref: '#/components/parameters/DeliveryId'
      responses:
        '200':
          description: Webhook delivery
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WebhookDeliveryDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'

  /usage:
    get:
      tags: [Usage]
      operationId: getUsage
      summary: Get current account usage
      responses:
        '200':
          description: Current usage snapshot
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageSummary'

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: APIKey

  parameters:
    Page:
      in: query
      name: page
      schema:
        type: integer
        minimum: 1
        default: 1
    PageSize:
      in: query
      name: page_size
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    IdempotencyKey:
      in: header
      name: Idempotency-Key
      required: false
      schema:
        type: string
        maxLength: 255
      description: Recommended for all mutating requests.
    VoiceId:
      in: path
      name: voiceId
      required: true
      schema:
        type: string
    MediaId:
      in: path
      name: mediaId
      required: true
      schema:
        type: string
    PlanId:
      in: path
      name: planId
      required: true
      schema:
        type: string
    KeyId:
      in: path
      name: keyId
      required: true
      schema:
        type: string
    ProfileKey:
      in: path
      name: profileKey
      required: true
      schema:
        type: string
    BindingId:
      in: path
      name: bindingId
      required: true
      schema:
        type: string
    PresenterProfileId:
      in: path
      name: presenterProfileId
      required: true
      schema:
        type: string
    DeliveryId:
      in: path
      name: deliveryId
      required: true
      schema:
        type: string
    VideoId:
      in: path
      name: videoId
      required: true
      schema:
        type: string
    ClipTaskId:
      in: path
      name: clipTaskId
      required: true
      schema:
        type: string
    WorkflowId:
      in: path
      name: workflowId
      required: true
      schema:
        type: string
    ExecutionId:
      in: path
      name: executionId
      required: true
      schema:
        type: string

  responses:
    Unauthorized:
      description: Missing or invalid credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ValidationError:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: Authenticated but not allowed to access the resource or scope
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
        code:
          type: string
        details:
          oneOf:
            - type: object
              additionalProperties: true
            - type: array
              items:
                type: object
                additionalProperties: true
            - type: string
      required: [error]

    Pagination:
      type: object
      properties:
        page:
          type: integer
        page_size:
          type: integer
        total:
          type: integer
        total_pages:
          type: integer
      required: [page, page_size, total, total_pages]

    Voice:
      type: object
      properties:
        id:
          type: string
          description: Canonical provider-blind voice entity ID.
        name:
          type: string
        language:
          type: string
        locale:
          type: string
        gender:
          type: string
          nullable: true
        description:
          type: string
          nullable: true
        preview_url:
          type: string
          format: uri
          nullable: true
        is_active:
          type: boolean
      required: [id, name, locale, is_active]

    VoiceRef:
      type: object
      properties:
        id:
          type: string
          description: Canonical voice entity ID.
      required: [id]

    MediaAsset:
      type: object
      properties:
        id:
          type: string
        kind:
          type: string
          enum: [video, image, audio]
        title:
          type: string
        file_name:
          type: string
        mime_type:
          type: string
        file_size:
          type: integer
          minimum: 0
        url:
          type: string
          format: uri
        duration_seconds:
          type: number
          nullable: true
        created_at:
          type: string
          format: date-time
        metadata:
          type: object
          additionalProperties: true
      required:
        [id, kind, file_name, mime_type, file_size, url, created_at, metadata]

    ApiKeyScope:
      type: string
      enum: ['*', 'voices:read', 'media:read', 'media:write', 'videos:read', 'videos:write', 'video_plans:read', 'video_plans:write', 'clip_tasks:read', 'clip_tasks:write', 'workflows:read', 'workflow_executions:read', 'workflow_executions:write', 'webhooks:read']

    ApiKey:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        key_prefix:
          type: string
        token_preview:
          type: string
        scopes:
          type: array
          items:
            $ref: '#/components/schemas/ApiKeyScope'
        expires_at:
          type: string
          format: date-time
          nullable: true
        last_used_at:
          type: string
          format: date-time
          nullable: true
        revoked_at:
          type: string
          format: date-time
          nullable: true
        metadata:
          type: object
          additionalProperties: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required: [id, name, key_prefix, token_preview, scopes, metadata, created_at, updated_at]

    ApiKeyWithToken:
      allOf:
        - $ref: '#/components/schemas/ApiKey'
        - type: object
          properties:
            token:
              type: string
              description: Returned only once at creation time.
          required: [token]

    ApiKeyCollection:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/ApiKey'
      required: [data]

    CreateApiKeyRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 120
        scopes:
          type: array
          maxItems: 16
          items:
            $ref: '#/components/schemas/ApiKeyScope'
        expires_at:
          type: string
          format: date-time
        metadata:
          type: object
          additionalProperties: true
      required: [name]
      additionalProperties: false

    UpdateApiKeyRequest:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 120
        scopes:
          type: array
          maxItems: 16
          items:
            $ref: '#/components/schemas/ApiKeyScope'
        expires_at:
          anyOf:
            - type: string
              format: date-time
            - type: 'null'
        metadata:
          type: object
          additionalProperties: true
      additionalProperties: false

    SourceInput:
      oneOf:
        - $ref: '#/components/schemas/PromptSourceInput'
        - $ref: '#/components/schemas/ScriptSourceInput'
        - $ref: '#/components/schemas/ArticleUrlSourceInput'
      discriminator:
        propertyName: kind
        mapping:
          prompt: '#/components/schemas/PromptSourceInput'
          script: '#/components/schemas/ScriptSourceInput'
          article_url: '#/components/schemas/ArticleUrlSourceInput'

    PromptSourceInput:
      type: object
      properties:
        kind:
          const: prompt
        prompt:
          type: string
      required: [kind, prompt]

    ScriptSourceInput:
      type: object
      properties:
        kind:
          const: script
        script:
          type: string
      required: [kind, script]

    ArticleUrlSourceInput:
      type: object
      properties:
        kind:
          const: article_url
        article_url:
          type: string
          format: uri
      required: [kind, article_url]

    MediaSourceInput:
      type: object
      properties:
        kind:
          const: media
        media_id:
          type: string
      required: [kind, media_id]

    ClipSourceInput:
      type: object
      properties:
        kind:
          const: clip
        clip_task_id:
          type: string
        section_index:
          type: integer
          minimum: 0
      required: [kind, clip_task_id, section_index]

    RenderOptions:
      type: object
      properties:
        aspect_ratio:
          type: string
          enum: ['9:16', '1:1', '16:9']
          default: '9:16'
        duration_seconds:
          type: integer
          minimum: 15
          maximum: 180
        visual_style:
          type: string
          description: Public-facing visual style token.
        media_type:
          type: string
          enum: [images_only, first_only, strong_start, odds, full]
          default: images_only
        image_quality:
          type: string
          enum: [standard, ultra]
          default: standard
        include_soundtrack:
          type: boolean
          default: true
        use_voice_over:
          type: boolean
          default: true
      additionalProperties: false

    GoalSettings:
      type: object
      properties:
        title:
          type: string
        language:
          type: string
          default: en-US
        duration_seconds:
          type: integer
          minimum: 15
          maximum: 180
        visual_style:
          type: string
        target_platform:
          type: string
          enum: [tiktok, instagram_reels, youtube_shorts, linkedin, generic]
          default: generic
      additionalProperties: false

    CreateVideoPlanRequest:
      type: object
      properties:
        source:
          $ref: '#/components/schemas/SourceInput'
        goal:
          $ref: '#/components/schemas/GoalSettings'
        defaults:
          type: object
          properties:
            voice:
              $ref: '#/components/schemas/VoiceRef'
            render:
              $ref: '#/components/schemas/RenderOptions'
          additionalProperties: false
        metadata:
          type: object
          additionalProperties: true
      required: [source, goal]

    UpdateVideoPlanRequest:
      type: object
      properties:
        title:
          type: string
        script:
          type: string
        voice:
          $ref: '#/components/schemas/VoiceRef'
        render:
          $ref: '#/components/schemas/RenderOptions'
        scenes:
          type: array
          items:
            $ref: '#/components/schemas/ScenePlan'
        metadata:
          type: object
          additionalProperties: true
      additionalProperties: false

    ApproveVideoPlanRequest:
      type: object
      properties:
        voice:
          $ref: '#/components/schemas/VoiceRef'
        render:
          $ref: '#/components/schemas/RenderOptions'
        webhook_url:
          type: string
          format: uri
        webhook_secret:
          type: string
          description: Optional HMAC secret used to sign outbound Shotmatic webhook deliveries. Signature format is `v1=<hex_hmac_sha256>` over `${timestamp}.${raw_json_body}`.
      additionalProperties: false

  VideoPlanStatus:
    type: string
    enum:
      - drafting
      - draft
      - ready_for_review
      - approved
        - queued
        - rendering
        - completed
        - failed
        - expired

    ScenePlan:
      type: object
      properties:
        index:
          type: integer
          minimum: 0
        narration:
          type: string
        visual_prompt:
          type: string
        duration_seconds:
          type: number
        start_time:
          type: number
          nullable: true
        end_time:
          type: number
          nullable: true
      required: [index, narration, visual_prompt, duration_seconds]

    TimestampWord:
      type: object
      properties:
        word:
          type: string
        start:
          type: number
        end:
          type: number
      required: [word, start, end]

    Transcript:
      type: object
      properties:
        text:
          type: string
        words:
          type: array
          items:
            $ref: '#/components/schemas/TimestampWord'
      required: [text, words]

    CostEstimate:
      type: object
      properties:
        credits:
          type: integer
          minimum: 0
        currency:
          type: string
          default: credits
        explanation:
          type: string
      required: [credits, currency]

    VideoPlan:
      type: object
      properties:
        id:
          type: string
        status:
          $ref: '#/components/schemas/VideoPlanStatus'
        source:
          $ref: '#/components/schemas/SourceInput'
        title:
          type: string
        language:
          type: string
        script:
          type: string
        voice:
          $ref: '#/components/schemas/VoiceRef'
        render:
          $ref: '#/components/schemas/RenderOptions'
        scenes:
          type: array
          items:
            $ref: '#/components/schemas/ScenePlan'
        transcript:
          anyOf:
            - $ref: '#/components/schemas/Transcript'
            - type: 'null'
        cost_estimate:
          $ref: '#/components/schemas/CostEstimate'
        editable_fields:
          type: array
          items:
            type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        approved_at:
          type: string
          format: date-time
          nullable: true
        video_id:
          type: string
          nullable: true
      required:
        - id
        - status
        - source
        - title
        - language
        - script
        - render
        - scenes
        - cost_estimate
        - editable_fields
        - created_at
        - updated_at

    CreateVideoRequest:
      type: object
      properties:
        source:
          $ref: '#/components/schemas/SourceInput'
        title:
          type: string
        language:
          type: string
          default: en-US
        voice:
          $ref: '#/components/schemas/VoiceRef'
        render:
          $ref: '#/components/schemas/RenderOptions'
        webhook_url:
          type: string
          format: uri
        webhook_secret:
          type: string
          description: Optional HMAC secret used to sign outbound Shotmatic webhook deliveries. Signature format is `v1=<hex_hmac_sha256>` over `${timestamp}.${raw_json_body}`.
        metadata:
          type: object
          additionalProperties: true
      required: [source, voice, render]

    VideoStatus:
      type: string
      enum:
        - queued
        - planning
        - rendering
        - completed
        - failed
        - cancelled

    Video:
      type: object
      properties:
        id:
          type: string
        status:
          $ref: '#/components/schemas/VideoStatus'
        title:
          type: string
        source_kind:
          type: string
        plan_id:
          type: string
          nullable: true
        voice:
          $ref: '#/components/schemas/VoiceRef'
        render:
          $ref: '#/components/schemas/RenderOptions'
        output_url:
          type: string
          format: uri
          nullable: true
        thumbnail_url:
          type: string
          format: uri
          nullable: true
        transcript:
          anyOf:
            - $ref: '#/components/schemas/Transcript'
            - type: 'null'
        error:
          type: string
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
        completed_at:
          type: string
          format: date-time
          nullable: true
      required:
        - id
        - status
        - title
        - source_kind
        - render
        - created_at
        - updated_at

    CreateClipTaskRequest:
      type: object
      properties:
        source_type:
          type: string
          enum: [url]
        url:
          type: string
          format: uri
        aspect_ratio:
          type: string
          enum: ['9:16', '1:1', '16:9']
          default: '9:16'
        number_of_sections:
          type: integer
          minimum: 1
          default: 5
        min_section_duration:
          type: integer
          minimum: 1
          default: 5
        max_section_duration:
          type: integer
          minimum: 1
          default: 60
        generate_voiceover:
          type: boolean
          default: false
        include_background_music:
          type: boolean
          default: false
        language:
          type: string
          default: en-US
        focus_topic:
          type: string
      required: [source_type, url]

    CreateClipTaskUploadRequest:
      type: object
      properties:
        source_type:
          type: string
          enum: [upload]
        file:
          type: string
          format: binary
        aspect_ratio:
          type: string
          enum: ['9:16', '1:1', '16:9']
        number_of_sections:
          type: integer
          minimum: 1
        min_section_duration:
          type: integer
          minimum: 1
        max_section_duration:
          type: integer
          minimum: 1
        generate_voiceover:
          type: boolean
        include_background_music:
          type: boolean
        language:
          type: string
        focus_topic:
          type: string
      required: [source_type, file]

    ClipTaskStatus:
      type: string
      enum:
        - queued
        - processing
        - complete
        - complete_partial
        - failed

    ClipSection:
      type: object
      properties:
        index:
          type: integer
          minimum: 0
        title:
          type: string
          nullable: true
        start_time:
          type: number
        end_time:
          type: number
        duration_seconds:
          type: number
        transcript_excerpt:
          type: string
          nullable: true
        virality_score:
          type: number
          nullable: true
        virality_reason:
          type: string
          nullable: true
      required: [index, start_time, end_time, duration_seconds]

    ClipTask:
      type: object
      properties:
        id:
          type: string
        status:
          $ref: '#/components/schemas/ClipTaskStatus'
        source_type:
          type: string
          enum: [upload, url]
        source_url:
          type: string
          format: uri
          nullable: true
        source_media_id:
          type: string
          nullable: true
        aspect_ratio:
          type: string
          enum: ['9:16', '1:1', '16:9']
        sections:
          type: array
          items:
            $ref: '#/components/schemas/ClipSection'
        transcript:
          anyOf:
            - $ref: '#/components/schemas/Transcript'
            - type: 'null'
        processed_video_url:
          type: string
          format: uri
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - status
        - source_type
        - aspect_ratio
        - sections
        - created_at
        - updated_at

    RenderClipSelectionRequest:
      type: object
      properties:
        section_index:
          type: integer
          minimum: 0
        section_indices:
          type: array
          items:
            type: integer
            minimum: 0
        title:
          type: string
        use_voice_over:
          type: boolean
          default: false
        voice:
          $ref: '#/components/schemas/VoiceRef'
        aspect_ratio:
          type: string
          enum: ['9:16', '1:1', '16:9']
          default: '9:16'
        clip_mode:
          type: string
          enum: [standard, commentary]
          default: standard
        commentary:
          type: object
          properties:
            style:
              type: string
              enum:
                [funny, fact_check, oppositional, educational, roast, hype, react, story, debate, cringe]
            avatar_id:
              type: string
            avatar_matted:
              type: boolean
              default: false
            avatar_position:
              type: string
              enum: [split, overlay, corner, pip]
              default: corner
          additionalProperties: false
      additionalProperties: false

    Workflow:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        description:
          type: string
          nullable: true
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required: [id, name, is_active, created_at, updated_at]

    CreateWorkflowExecutionRequest:
      type: object
      properties:
        inputs:
          type: object
          additionalProperties: true
        start_from_node_id:
          type: string
          nullable: true
        cached_outputs:
          type: object
          additionalProperties: true
      additionalProperties: false

    WorkflowExecutionStatus:
      type: string
      enum: [PENDING, RUNNING, COMPLETED, FAILED, CANCELLED]

    WorkflowExecution:
      type: object
      properties:
        id:
          type: string
        workflow_id:
          type: string
        status:
          $ref: '#/components/schemas/WorkflowExecutionStatus'
        current_node_id:
          type: string
          nullable: true
        state:
          type: object
          additionalProperties: true
        node_stats:
          type: object
          properties:
            total:
              type: integer
            success:
              type: integer
            error:
              type: integer
          required: [total, success, error]
        duration_ms:
          type: integer
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - workflow_id
        - status
        - created_at
        - updated_at

    WebhookEventType:
      type: string
      enum:
        - video.queued
        - video.started
        - video.completed
        - video.failed
        - clip_task.completed
        - clip_task.failed
        - workflow_execution.completed
        - workflow_execution.failed

    CreateWebhookRequest:
      type: object
      properties:
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventType'
        secret:
          type: string
      required: [url, events]

    WebhookEndpoint:
      type: object
      properties:
        id:
          type: string
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            $ref: '#/components/schemas/WebhookEventType'
        created_at:
          type: string
          format: date-time
      required: [id, url, events, created_at]

    WebhookDeliveryStatus:
      type: string
      enum:
        - pending
        - processing
        - retry_due
        - retry_scheduled
        - delivered
        - exhausted

    WebhookDeliverySummary:
      type: object
      properties:
        id:
          type: string
        video_id:
          type: string
        event_type:
          $ref: '#/components/schemas/WebhookEventType'
        status:
          $ref: '#/components/schemas/WebhookDeliveryStatus'
        target_url:
          type: string
          format: uri
        attempt_count:
          type: integer
          minimum: 0
        response_status:
          type: integer
          nullable: true
        last_error:
          type: string
          nullable: true
        last_attempt_at:
          type: string
          format: date-time
          nullable: true
        next_attempt_at:
          type: string
          format: date-time
          nullable: true
        processing_at:
          type: string
          format: date-time
          nullable: true
        delivered_at:
          type: string
          format: date-time
          nullable: true
        failed_at:
          type: string
          format: date-time
          nullable: true
        exhausted_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
      required:
        - id
        - video_id
        - event_type
        - status
        - target_url
        - attempt_count
        - created_at
        - updated_at

    WebhookDeliveryDetail:
      allOf:
        - $ref: '#/components/schemas/WebhookDeliverySummary'
        - type: object
          properties:
            payload:
              type: object
              additionalProperties: true
            response_body:
              type: string
              nullable: true
            dedupe_key:
              type: string
            processing_by:
              type: string
              nullable: true
          required:
            - payload
            - dedupe_key

    WebhookDeliveryCollection:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/WebhookDeliverySummary'
        pagination:
          $ref: '#/components/schemas/Pagination'
      required:
        - data
        - pagination

    UsageSummary:
      type: object
      properties:
        credits_remaining:
          type: integer
          minimum: 0
        active_sequences:
          type: integer
          minimum: 0
        sequence_quota:
          type: integer
          minimum: 0
        max_frequency_per_week:
          type: integer
          minimum: 0
        videos_created_this_period:
          type: integer
          minimum: 0
        clip_tasks_created_this_period:
          type: integer
          minimum: 0
      required:
        - credits_remaining
        - active_sequences
        - sequence_quota
        - max_frequency_per_week
        - videos_created_this_period
        - clip_tasks_created_this_period
