openapi: 3.0.0
info:
  title: YouTube Transcript API (V1, legacy)
  version: 1.0.0
  description: |
    **Legacy API.** New integrations should use V2
    (https://www.youtubetranscript.dev/api/v2). POST /transcriptions is
    deprecated and returns 410 Gone; use POST /api/v2/transcribe instead.

    RESTful API for extracting YouTube video transcripts.

    This API provides multiple transcription methods:
    - **Native YouTube captions** (fastest, 1 credit, 5-10 seconds)
    - **Google Translate** for language conversion (1 credit per 2,500 chars, 5-10 seconds)
    - **AI-powered ASR** for audio transcription (1 credit per 90 seconds, 2-20 minutes)

    ## Usage Modes

    - **Sync Mode (Default)**: Returns results immediately. Use for caption-based transcripts.
    - **Async Mode (Webhook)**: Returns batch_id immediately, sends results to your webhook when complete. Required for ASR.

    ## Credit Costs

    | Method | Cost | Speed | Notes |
    |--------|------|-------|-------|
    | Native Captions | 1 credit | 5-10 seconds | Best quality, exact language |
    | Translation | 1 credit per 2,500 chars | 5-10 seconds | ~4 credits for 10-min video |
    | ASR (Audio) | 1 credit per 90 seconds | 2-20 minutes | ~7 credits for 10-min video |

    ## Rate Limits

    | Plan | Limit |
    |------|-------|
    | Free | 1 request per second |
    | Basic | 25 requests per second |
    | Pro | 50 requests per second |
    | Business | 100 requests per second |

  contact:
    name: API Support
    email: support@youtubetranscript.dev
    url: https://www.youtubetranscript.dev
  x-logo:
    url: https://www.youtubetranscript.dev/logo.svg

externalDocs:
  description: Full API Documentation
  url: https://www.youtubetranscript.dev/api-docs

servers:
  - url: https://www.youtubetranscript.dev/api/v1
    description: Production server

tags:
  - name: Transcriptions
    description: Video transcription operations - submit videos for transcription
  - name: Jobs
    description: Batch job status and polling for async operations
  - name: Playlists
    description: Resolve playlist URLs or IDs into video lists and inspect playlist batches
  - name: Channels
    description: Resolve channel URLs, handles, or IDs into upload video lists and inspect channel batches

paths:
  /transcriptions:
    post:
      summary: Process transcription request (deprecated)
      description: Deprecated - returns 410 Gone. Use POST /api/v2/transcribe.
      operationId: createTranscription
      deprecated: true
      tags: [Transcriptions]
      security: [{ApiKeyAuth: []}]
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/TranscriptionRequest'}
      responses:
        '200': {description: Transcription processed successfully (sync mode), content: {application/json: {schema: {$ref: '#/components/schemas/TranscriptionResponse'}}}}
        '202': {description: Processing started (async mode with webhook), content: {application/json: {schema: {$ref: '#/components/schemas/AsyncResponse'}}}}
        '400': {description: Invalid request, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}
        '401': {description: Invalid or missing API key, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}
        '402': {description: Insufficient credits, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}
        '404': {description: Video not found or no captions available, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}
        '429': {description: Rate limit exceeded, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}
        '500': {description: Internal server error, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}

  /jobs/{batch_id}:
    get:
      summary: Get batch job status
      operationId: getBatchJob
      tags: [Jobs]
      security: [{ApiKeyAuth: []}]
      parameters:
        - {name: batch_id, in: path, required: true, schema: {type: string, format: uuid}}
      responses:
        '200': {description: Batch job completed, content: {application/json: {schema: {$ref: '#/components/schemas/BatchJobCompleted'}}}}
        '202': {description: Batch job still processing, content: {application/json: {schema: {$ref: '#/components/schemas/BatchJobProcessing'}}}}
        '404': {description: Batch job not found, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}

  /playlists/resolve:
    post:
      summary: Resolve playlist videos
      operationId: resolvePlaylist
      tags: [Playlists]
      security: [{ApiKeyAuth: []}]
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/PlaylistResolveRequest'}
      responses:
        '200': {description: Playlist resolved successfully, content: {application/json: {schema: {$ref: '#/components/schemas/PlaylistResolveResponse'}}}}
        '400': {description: Invalid playlist URL or ID, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}
        '404': {description: Playlist not found or empty, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}

  /playlists/{playlist_id}:
    get:
      summary: Get playlist batch details
      operationId: getPlaylistBatch
      tags: [Playlists]
      security: [{ApiKeyAuth: []}]
      parameters:
        - {name: playlist_id, in: path, required: true, schema: {type: string, format: uuid}}
      responses:
        '200': {description: Playlist batch details, content: {application/json: {schema: {$ref: '#/components/schemas/PlaylistBatchResponse'}}}}
        '404': {description: Playlist batch not found, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}

  /playlists/history:
    get:
      summary: List playlist batches
      operationId: listPlaylistHistory
      tags: [Playlists]
      security: [{ApiKeyAuth: []}]
      parameters:
        - {name: limit, in: query, schema: {type: integer, maximum: 100, default: 50}}
      responses:
        '200': {description: Playlist batch history, content: {application/json: {schema: {$ref: '#/components/schemas/PlaylistHistoryResponse'}}}}

  /channels/resolve:
    post:
      summary: Resolve channel uploads
      operationId: resolveChannel
      tags: [Channels]
      security: [{ApiKeyAuth: []}]
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/ChannelResolveRequest'}
      responses:
        '200': {description: Channel resolved successfully, content: {application/json: {schema: {$ref: '#/components/schemas/ChannelResolveResponse'}}}}
        '400': {description: Invalid channel URL, handle, or ID, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}
        '404': {description: Channel not found or has no uploads, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}

  /channels/{channel_id}:
    get:
      summary: Get channel batch details
      operationId: getChannelBatch
      tags: [Channels]
      security: [{ApiKeyAuth: []}]
      parameters:
        - {name: channel_id, in: path, required: true, schema: {type: string, format: uuid}}
      responses:
        '200': {description: Channel batch details, content: {application/json: {schema: {$ref: '#/components/schemas/ChannelBatchResponse'}}}}
        '404': {description: Channel batch not found, content: {application/json: {schema: {$ref: '#/components/schemas/ErrorResponse'}}}}

  /channels/history:
    get:
      summary: List channel batches
      operationId: listChannelHistory
      tags: [Channels]
      security: [{ApiKeyAuth: []}]
      parameters:
        - {name: limit, in: query, schema: {type: integer, maximum: 100, default: 50}}
      responses:
        '200': {description: Channel batch history, content: {application/json: {schema: {$ref: '#/components/schemas/ChannelHistoryResponse'}}}}

components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      description: 'API key auth via Authorization: Bearer YOUR_API_KEY'

  schemas:
    TranscriptionRequest:
      type: object
      properties:
        video_ids: {type: array, items: {type: string}, maxItems: 10}
        youtube_url: {type: string, format: uri}
        language: {type: string, pattern: '^[a-z]{2}$', nullable: true}
        include_timestamps: {type: boolean, default: true}
        text_only: {type: boolean, default: false}
        include_paragraphs: {type: boolean, default: false}
        include_words: {type: boolean, default: false}
        source: {type: string, enum: [manual, auto, asr], default: auto}
        webhook_url: {type: string, format: uri, nullable: true}

    TranscriptionResponse:
      type: object
      properties:
        results: {type: array, items: {$ref: '#/components/schemas/TranscriptResult'}}
        credits_used: {type: integer}

    AsyncResponse:
      type: object
      properties:
        status: {type: string, enum: [processing]}
        batch_id: {type: string, format: uuid}
        summary:
          type: object
          properties:
            total: {type: integer}
            queued: {type: integer}
            processing: {type: integer}
            completed: {type: integer}
            failed: {type: integer}
          additionalProperties: true
        jobs: {type: array, items: {$ref: '#/components/schemas/JobStatus'}}
        message: {type: string}

    JobStatus:
      type: object
      properties:
        video_id: {type: string}
        status: {type: string, enum: [queued, processing, completed, failed]}
        job_id: {type: string}
        estimated_time: {type: string}

    TranscriptResult:
      type: object
      properties:
        video_id: {type: string}
        status: {type: string, enum: [success, error]}
        transcript: {$ref: '#/components/schemas/FormattedTranscript'}
        error: {type: string}

    FormattedTranscript:
      type: object
      properties:
        text: {type: string}
        segments: {type: array, items: {$ref: '#/components/schemas/TranscriptSegment'}}
        source_kind: {type: string, enum: [manual, auto, asr]}
        language: {type: string}

    TranscriptSegment:
      type: object
      properties:
        text: {type: string}
        start: {type: integer}
        end: {type: integer}

    BatchJobCompleted:
      type: object
      properties:
        batch_id: {type: string, format: uuid}
        status: {type: string, enum: [completed]}
        completed_count: {type: integer}
        failed_count: {type: integer}
        total_videos: {type: integer}
        results: {type: array, items: {$ref: '#/components/schemas/TranscriptResult'}}
        webhook_delivered: {type: boolean}
        created_at: {type: string, format: date-time}
        completed_at: {type: string, format: date-time}

    BatchJobProcessing:
      type: object
      properties:
        batch_id: {type: string, format: uuid}
        status: {type: string, enum: [processing]}
        progress_percentage: {type: integer}
        completed_count: {type: integer}
        failed_count: {type: integer}
        total_videos: {type: integer}
        jobs: {type: array, items: {$ref: '#/components/schemas/JobStatus'}}
        created_at: {type: string, format: date-time}

    PlaylistResolveRequest:
      type: object
      properties:
        playlist_url: {type: string}
        playlist_id: {type: string}
        limit: {type: integer, minimum: 1, maximum: 100}

    PlaylistResolveResponse:
      type: object
      properties:
        playlist_id: {type: string}
        title: {type: string, nullable: true}
        total: {type: integer}
        truncated: {type: boolean}
        items: {type: array, items: {$ref: '#/components/schemas/CollectionVideoRef'}}

    ChannelResolveRequest:
      type: object
      properties:
        channel_url: {type: string}
        channel_id: {type: string}
        handle: {type: string}
        limit: {type: integer, minimum: 1}

    ChannelResolveResponse:
      type: object
      properties:
        channel_id: {type: string}
        channel_title: {type: string}
        total: {type: integer}
        truncated: {type: boolean}
        items: {type: array, items: {$ref: '#/components/schemas/CollectionVideoRef'}}

    CollectionVideoRef:
      type: object
      description: A video listed by a playlist/channel resolve call (metadata only).
      properties:
        id: {type: string}
        video_id: {type: string}
        title: {type: string, nullable: true}
        url: {type: string, nullable: true}
        published_at: {type: string, nullable: true}

    CollectionJobVideo:
      type: object
      description: Per-video transcription job status inside a playlist/channel batch.
      properties:
        id: {type: string}
        video_id: {type: string}
        video_title: {type: string, nullable: true}
        status: {type: string, nullable: true}
        created_at: {type: string, format: date-time}
        finished_at: {type: string, format: date-time, nullable: true}
        error: {type: string, nullable: true}

    CollectionBatchInfo:
      type: object
      description: Playlist/channel batch summary.
      properties:
        id: {type: string}
        playlist_id: {type: string, nullable: true}
        playlist_title: {type: string, nullable: true}
        playlist_url: {type: string, nullable: true}
        channel_id: {type: string, nullable: true}
        channel_title: {type: string, nullable: true}
        channel_url: {type: string, nullable: true}
        total_videos: {type: integer}
        completed_count: {type: integer}
        failed_count: {type: integer}
        status: {type: string}
        created_at: {type: string, format: date-time}
        started_at: {type: string, format: date-time, nullable: true}
        completed_at: {type: string, format: date-time, nullable: true}
        metadata: {type: object, nullable: true, additionalProperties: true}

    PlaylistBatchResponse:
      type: object
      properties:
        playlist: {$ref: '#/components/schemas/CollectionBatchInfo'}
        videos: {type: array, items: {$ref: '#/components/schemas/CollectionJobVideo'}}

    ChannelBatchResponse:
      type: object
      properties:
        channel: {$ref: '#/components/schemas/CollectionBatchInfo'}
        videos: {type: array, items: {$ref: '#/components/schemas/CollectionJobVideo'}}

    PlaylistHistoryResponse:
      type: object
      properties:
        history: {type: array, items: {$ref: '#/components/schemas/CollectionBatchInfo'}}
        count: {type: integer}

    ChannelHistoryResponse:
      type: object
      properties:
        history: {type: array, items: {$ref: '#/components/schemas/CollectionBatchInfo'}}
        count: {type: integer}

    ErrorResponse:
      type: object
      properties:
        error:
          type: object
          properties:
            code: {type: string}
            message: {type: string}
            details: {type: object, additionalProperties: true}
