openapi: 3.1.0
info:
  title: CreatorNode YouTube API — Thumbnail Ranking
  description: >
    Rank thumbnail variants by visual quality signals.


    Upload 2–10 variants, get a data-driven winner scored across six dimensions:

    mobile legibility, contrast, face presence, saliency, text density, and
    semantic alignment.
  version: 1.0.0
  termsOfService: https://creatornode.io/terms
  contact:
    name: CreatorNode Support
    email: support@creatornode.io
    url: https://creatornode.io
  license:
    name: Proprietary
    url: https://creatornode.io/legal
servers:
  - url: https://api.creatornode.io/youtube
    description: Production
tags:
  - name: Thumbnails
    description: Thumbnail analysis and ranking
paths:
  /v1/thumbnail-ranking:
    post:
      operationId: rankThumbnails
      summary: Rank thumbnail variants
      description: Rank thumbnail variants by visual quality signals including mobile
        legibility, contrast robustness, text density, face presence, saliency,
        and semantic alignment with the video title.
      tags:
        - Thumbnails
      security:
        - ApiKeyAuth: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RankRequest"
            examples:
              basic:
                summary: Basic ranking request
                value:
                  variants:
                    - id: variant-a
                      imageBase64: dGh1bWItYS1iYXNlNjQ=
                    - id: variant-b
                      imageBase64: dGh1bWItYi1iYXNlNjQ=
                  title: How I Made $10k in 30 Days
              withScript:
                summary: With script excerpt for better alignment
                value:
                  variants:
                    - id: variant-a
                      imageBase64: dGh1bWItYS1iYXNlNjQ=
                    - id: variant-b
                      imageBase64: dGh1bWItYi1iYXNlNjQ=
                  title: How I Made $10k in 30 Days
                  scriptExcerpt: What's up everyone, so last month I decided to quit my 9-to-5 and
                    go all in on building a SaaS app. In this video I'm going to
                    walk you through exactly how I went from zero to ten
                    thousand dollars in recurring revenue...
          multipart/form-data:
            schema:
              $ref: "#/components/schemas/MultipartRankRequest"
      responses:
        "200":
          description: Successful ranking
          headers:
            X-Credits-Remaining:
              description: Remaining credits (prepaid billing only)
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RankResponse"
              example:
                success: true
                data:
                  winner:
                    id: variant-a
                    score: 87.5
                    reasons:
                      - High contrast and clear focal point
                      - Strong face presence with genuine emotion
                      - Mobile-optimized text legibility
                  variants:
                    - id: variant-a
                      rank: 1
                      score: 87.5
                      isWinner: true
                      subscores:
                        mobileLegibility: 92
                        contrastRobustness: 88
                        facePresence: 95
                        saliencyProxy: 78
                        semanticAlignment: 82
                    - id: variant-b
                      rank: 2
                      score: 71.2
                      isWinner: false
                      subscores:
                        mobileLegibility: 68
                        contrastRobustness: 75
                        facePresence: 80
                        saliencyProxy: 65
                        semanticAlignment: 70
                  confidence:
                    level: high
                    value: 0.92
                meta:
                  requestId: req_rank_123
                  processingTimeMs: 1250
                  variantCount: 2
        "400":
          $ref: "#/components/responses/ValidationError"
        "401":
          $ref: "#/components/responses/AuthError"
        "402":
          $ref: "#/components/responses/PaymentRequired"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/ServerError"
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: APIM subscription key for authenticated access. Without a key,
        requests use free tier limits.
  responses:
    ValidationError:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: VALIDATION_ERROR
              message: Request validation failed
              details:
                issues:
                  - path:
                      - variants
                    message: Required
    AuthError:
      description: Authentication error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: API_KEY_INVALID
              message: Invalid API key
    PaymentRequired:
      description: Credits exhausted
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: INSUFFICIENT_CREDITS
              message: Prepaid credits exhausted
    RateLimited:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: RATE_LIMITED
              message: Rate limit exceeded
              details:
                retryAfter: 60
    ServerError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: INTERNAL_ERROR
              message: An internal error occurred
  schemas:
    RankRequest:
      type: object
      properties:
        variants:
          type: array
          minItems: 1
          maxItems: 20
          items:
            $ref: "#/components/schemas/VariantInput"
          description: Thumbnail variants to rank
        title:
          type: string
          maxLength: 200
          description: Video title (for semantic alignment)
        scriptExcerpt:
          type: string
          minLength: 10
          maxLength: 1500
          description: |
            Optional beginning of the video script (first 10–1500 characters).
            Improves semantic alignment accuracy by giving the AI richer context
            about what the video is actually about.
        locale:
          type: string
          maxLength: 10
          default: en
          description: Locale for text analysis
      required:
        - variants
    VariantInput:
      type: object
      description: Single thumbnail variant (base64 payload)
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 100
          description: Unique identifier
        imageBase64:
          type: string
          description: Base64-encoded image (max 2MB per thumbnail)
      required:
        - id
        - imageBase64
    MultipartRankRequest:
      type: object
      properties:
        metadata:
          type: string
          description: JSON metadata (title, locale, options)
        images:
          type: array
          items:
            type: string
            format: binary
          description: Image files (variant ID from filename, max 2MB per image)
      required:
        - images
    RankResponse:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          type: object
          properties:
            winner:
              $ref: "#/components/schemas/WinnerSummary"
            variants:
              type: array
              items:
                $ref: "#/components/schemas/VariantResult"
            confidence:
              $ref: "#/components/schemas/ConfidenceInfo"
        meta:
          $ref: "#/components/schemas/ResponseMeta"
        recommendations:
          type: array
          items:
            $ref: "#/components/schemas/Recommendation"
    WinnerSummary:
      type: object
      properties:
        id:
          type: string
        score:
          type: number
        reasons:
          type: array
          items:
            type: string
    VariantResult:
      type: object
      properties:
        id:
          type: string
        rank:
          type: integer
          minimum: 1
        score:
          type: number
          minimum: 0
          maximum: 100
        subscores:
          $ref: "#/components/schemas/Subscores"
        diagnostics:
          $ref: "#/components/schemas/Diagnostics"
        reasons:
          type: array
          items:
            type: string
        isWinner:
          type: boolean
    Subscores:
      type: object
      properties:
        mobileLegibility:
          type:
            - number
            - "null"
        contrastRobustness:
          type:
            - number
            - "null"
        textDensity:
          type:
            - number
            - "null"
        facePresence:
          type:
            - number
            - "null"
        saliencyProxy:
          type:
            - number
            - "null"
        semanticAlignment:
          type:
            - number
            - "null"
    Diagnostics:
      type: object
      properties:
        focalPoint:
          type:
            - string
            - "null"
        detectedText:
          type:
            - string
            - "null"
        semanticReason:
          type:
            - string
            - "null"
        faceCount:
          type:
            - integer
            - "null"
        misalignmentWarning:
          type:
            - string
            - "null"
          description: >
            Set when the thumbnail has a strong semantic mismatch with the video

            title/script (semantic score ≤ 35). Indicates the thumbnail may have

            been included by mistake. A 20-point penalty is applied to the final
            score.
    ConfidenceInfo:
      type: object
      properties:
        level:
          type: string
          enum:
            - high
            - medium
            - low
        value:
          type: number
          minimum: 0
          maximum: 1
        factors:
          type: object
          properties:
            signalCoverage:
              type: number
            scoreStability:
              type: number
            marginStrength:
              type: number
    Recommendation:
      type: object
      properties:
        type:
          type: string
          enum:
            - upgrade
            - top_up
            - feature
            - tip
            - warning
            - fix
            - strength
        title:
          type: string
        message:
          type: string
        action:
          type: object
          properties:
            label:
              type: string
            url:
              type: string
              format: uri
        priority:
          type: string
          enum:
            - low
            - medium
            - high
    ResponseMeta:
      type: object
      properties:
        requestId:
          type: string
        processingTimeMs:
          type: integer
        variantCount:
          type: integer
        aiUsed:
          type: boolean
        cached:
          type: boolean
    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          const: false
        error:
          $ref: "#/components/schemas/ErrorDetail"
        meta:
          $ref: "#/components/schemas/ResponseMeta"
        recommendations:
          type: array
          items:
            $ref: "#/components/schemas/Recommendation"
    ErrorDetail:
      type: object
      properties:
        code:
          type: string
          description: Machine-readable error code
        message:
          type: string
          description: Human-readable message
        details:
          type: object
          additionalProperties: true
    RankDemoRequest:
      type: object
      properties:
        variants:
          type: array
          minItems: 1
          maxItems: 20
          items:
            type: object
            properties:
              id:
                type: string
                minLength: 1
                maxLength: 100
            required:
              - id
        title:
          type: string
          maxLength: 200
      required:
        - variants
    RankDemoResponse:
      allOf:
        - $ref: "#/components/schemas/RankResponse"
        - type: object
          properties:
            demoMode:
              type: boolean
              enum:
                - true
            warning:
              type: string
          required:
            - demoMode
            - warning
