openapi: 3.0.3
info:
  title: Threads Public Video Resolver API
  version: 1.2.0
  license:
    name: Proprietary
  termsOfService: https://clipmagic.ru/terms
  contact:
    email: support@clipmagic.ru
  description: >
    Resolve public Threads post URLs into video metadata, quality variants and
    temporary proxy download links.


    Compliance notice: this API supports only public or otherwise authorized
    content. It does not bypass login,

    CAPTCHA, DRM, private content restrictions, platform controls or use
    authorized user cookies. Videos are not

    stored permanently; proxy mode streams bytes through temporary tokens.
servers:
  - url: https://api.clipmagic.ru
security:
  - ApiKeyAuth: []
tags:
  - name: Threads
    description: Resolve public Threads post URLs.
  - name: Download
    description: Temporary redirect-safe proxy downloads.
  - name: Health
    description: Service health and readiness.
  - name: Docs
    description: Public developer documentation.
paths:
  /v1/threads/video:
    post:
      tags:
        - Threads
      summary: Resolve Threads video
      description: >
        Resolves a public Threads post URL into post metadata and video media
        variants.


        Only public or authorized content is supported.
        Private/login/CAPTCHA/DRM bypass is not supported.
      operationId: resolveThreadsVideo
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: "#/components/parameters/XRequestID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ThreadsVideoRequest"
            examples:
              urlMode:
                summary: URL mode
                value:
                  url: https://www.threads.net/@example/post/ABC123
                  download_mode: url
                  include_metadata: true
                  include_thumbnail: true
              metadataOnly:
                summary: Metadata only mode
                value:
                  url: https://www.threads.net/@example/post/ABC123
                  download_mode: metadata_only
              proxyMode:
                summary: Proxy mode
                value:
                  url: https://www.threads.net/@example/post/ABC123
                  download_mode: proxy
                  include_metadata: true
                  include_thumbnail: true
      responses:
        "200":
          description: Resolve succeeded
          headers:
            X-Request-ID:
              $ref: "#/components/headers/XRequestID"
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ThreadsVideoResponse"
              examples:
                successUrlMode:
                  summary: Success URL mode
                  value:
                    success: true
                    source: threads
                    input_url: https://www.threads.net/@example/post/ABC123?utm=demo
                    normalized_url: https://www.threads.net/@example/post/ABC123
                    post:
                      id: "123"
                      username: example
                      permalink: https://www.threads.net/@example/post/ABC123
                      text: Public video post
                      created_at: null
                    media:
                      - type: video
                        url: https://cdn.example/video.mp4
                        download_url: https://cdn.example/video.mp4
                        thumbnail_url: https://cdn.example/thumb.jpg
                        mime_type: video/mp4
                        width: 720
                        height: 1280
                        duration_seconds: 12.4
                        size_bytes: 3456789
                        quality: 720p
                        variant_id: hd
                        is_default: true
                        bitrate_kbps: 1200
                        source_rank: 0
                    meta:
                      processing_time_ms: 184
                      request_id: req_example
                successMetadataOnly:
                  summary: Success metadata_only mode
                  value:
                    success: true
                    source: threads
                    input_url: https://www.threads.net/@example/post/ABC123
                    normalized_url: https://www.threads.net/@example/post/ABC123
                    post:
                      id: "123"
                      username: example
                      permalink: https://www.threads.net/@example/post/ABC123
                      text: Public video post
                      created_at: null
                    media:
                      - type: video
                        url: null
                        download_url: null
                        thumbnail_url: null
                        mime_type: video/mp4
                        width: null
                        height: null
                        duration_seconds: null
                        size_bytes: null
                        quality: unknown
                    meta:
                      processing_time_ms: 91
                      request_id: req_example
                successProxy:
                  summary: Success proxy mode
                  value:
                    success: true
                    source: threads
                    input_url: https://www.threads.net/@example/post/ABC123
                    normalized_url: https://www.threads.net/@example/post/ABC123
                    post:
                      id: "123"
                      username: example
                      permalink: https://www.threads.net/@example/post/ABC123
                      text: Public video post
                      created_at: null
                    media:
                      - type: video
                        url: null
                        download_url: http://localhost:3000/v1/download/dlt_example
                        thumbnail_url: http://localhost:3000/v1/download/dlt_thumb
                        mime_type: video/mp4
                        width: 720
                        height: 1280
                        duration_seconds: 12.4
                        size_bytes: 3456789
                        quality: 720p
                    expires_at: 2026-07-09T13:00:00.000Z
                    meta:
                      processing_time_ms: 204
                      request_id: req_example
                multipleQualityVariants:
                  summary: Multiple quality variants
                  value:
                    success: true
                    source: threads
                    input_url: https://www.threads.net/@example/post/ABC123
                    normalized_url: https://www.threads.net/@example/post/ABC123
                    post:
                      id: "123"
                      username: example
                      permalink: https://www.threads.net/@example/post/ABC123
                      text: Public video post
                      created_at: null
                    media:
                      - type: video
                        download_url: http://localhost:3000/v1/download/dlt_hd
                        quality: 720p
                        variant_id: hd
                        is_default: true
                        bitrate_kbps: 1200
                        source_rank: 0
                      - type: video
                        download_url: http://localhost:3000/v1/download/dlt_sd
                        quality: 360p
                        variant_id: sd
                        is_default: false
                        bitrate_kbps: 450
                        source_rank: 1
                    meta:
                      processing_time_ms: 211
                      request_id: req_example
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                invalidUrl:
                  $ref: "#/components/examples/InvalidUrl"
                unsupportedDomain:
                  $ref: "#/components/examples/UnsupportedDomain"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Content inaccessible or key disabled
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                contentNotAccessible:
                  $ref: "#/components/examples/ContentNotAccessible"
                apiKeyDisabled:
                  value:
                    success: false
                    error:
                      code: API_KEY_DISABLED
                      message: API key is disabled.
                    meta:
                      request_id: req_example
                      processing_time_ms: 2
        "422":
          description: Video not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                videoNotFound:
                  $ref: "#/components/examples/VideoNotFound"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                rateLimitExceeded:
                  $ref: "#/components/examples/RateLimitExceeded"
        "504":
          description: Source timeout
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                sourceTimeout:
                  $ref: "#/components/examples/SourceTimeout"
  /v1/download/{token}:
    head:
      tags:
        - Download
      summary: Check temporary proxy download
      description: >
        Returns upstream media headers for a temporary proxy token. HEAD does
        not consume single-use tokens.

        Range requests are supported when upstream supports them.
      operationId: headDownloadToken
      parameters:
        - $ref: "#/components/parameters/DownloadToken"
        - $ref: "#/components/parameters/Range"
        - $ref: "#/components/parameters/XRequestID"
      responses:
        "200":
          description: Download headers
          headers:
            Accept-Ranges:
              $ref: "#/components/headers/AcceptRanges"
            Content-Length:
              $ref: "#/components/headers/ContentLength"
            Content-Type:
              $ref: "#/components/headers/ContentType"
            Content-Range:
              $ref: "#/components/headers/ContentRange"
        "410":
          description: Expired or consumed token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DownloadErrorResponse"
              examples:
                expiredToken:
                  $ref: "#/components/examples/DownloadTokenExpired"
                consumedToken:
                  $ref: "#/components/examples/DownloadTokenConsumed"
    get:
      tags:
        - Download
      summary: Stream temporary proxy download
      description: >
        Streams media bytes through a redirect-safe proxy. Tokens expire after a
        short TTL. When single-use mode

        is enabled, GET consumes the token after download starts. If upstream is
        unavailable or oversized before

        streaming starts, the token is not consumed. The proxy enforces
        DOWNLOAD_MAX_BYTES during streaming even

        when Content-Length is missing or incorrect.
      operationId: getDownloadToken
      parameters:
        - $ref: "#/components/parameters/DownloadToken"
        - $ref: "#/components/parameters/Range"
        - $ref: "#/components/parameters/XRequestID"
      responses:
        "200":
          description: Streamed media
          headers:
            Accept-Ranges:
              $ref: "#/components/headers/AcceptRanges"
            Content-Length:
              $ref: "#/components/headers/ContentLength"
            Content-Type:
              $ref: "#/components/headers/ContentType"
            Content-Range:
              $ref: "#/components/headers/ContentRange"
          content:
            video/mp4:
              schema:
                type: string
                format: binary
        "206":
          description: Partial streamed media
        "410":
          description: Expired or consumed token
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DownloadErrorResponse"
              examples:
                expiredToken:
                  $ref: "#/components/examples/DownloadTokenExpired"
                consumedToken:
                  $ref: "#/components/examples/DownloadTokenConsumed"
        "413":
          description: Download too large
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DownloadErrorResponse"
              examples:
                oversizedBeforeStream:
                  $ref: "#/components/examples/DownloadTooLarge"
                oversizedDuringStream:
                  value:
                    success: false
                    error:
                      code: DOWNLOAD_TOO_LARGE
                      message: The media file is too large to proxy.
                    meta:
                      request_id: req_example
                      processing_time_ms: 190
        "499":
          description: Client aborted download
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DownloadErrorResponse"
              examples:
                downloadAborted:
                  $ref: "#/components/examples/DownloadAborted"
  /health:
    get:
      tags:
        - Health
      operationId: getHealth
      summary: Health check
      security: []
      responses:
        "200":
          description: Healthy
  /live:
    get:
      tags:
        - Health
      operationId: getLiveness
      summary: Liveness check
      security: []
      responses:
        "200":
          description: Live
  /ready:
    get:
      tags:
        - Health
      operationId: getReadiness
      summary: Readiness check
      security: []
      responses:
        "200":
          description: Ready
        "503":
          description: Not ready
  /docs:
    get:
      tags:
        - Docs
      operationId: getSwaggerUi
      summary: Interactive Swagger UI documentation
      security: []
      responses:
        "200":
          description: HTML documentation
  /openapi.json:
    get:
      tags:
        - Docs
      operationId: getOpenApiJson
      summary: OpenAPI JSON
      security: []
      responses:
        "200":
          description: OpenAPI document
  /openapi.yaml:
    get:
      tags:
        - Docs
      operationId: getOpenApiYaml
      summary: OpenAPI YAML
      security: []
      responses:
        "200":
          description: OpenAPI YAML
  /developers:
    get:
      tags:
        - Docs
      operationId: getDeveloperGuide
      summary: Developer guide
      security: []
      responses:
        "200":
          description: HTML developer guide
  /guide:
    get:
      tags:
        - Docs
      operationId: getDeveloperGuideAlias
      summary: Developer guide alias
      security: []
      responses:
        "200":
          description: HTML developer guide
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
  parameters:
    DownloadToken:
      name: token
      in: path
      required: true
      schema:
        type: string
      description: Temporary proxy download token.
    XRequestID:
      name: X-Request-ID
      in: header
      required: false
      schema:
        type: string
      description: Optional request ID for tracing.
    Range:
      name: Range
      in: header
      required: false
      schema:
        type: string
        example: bytes=0-1048575
      description: Optional HTTP Range header.
  headers:
    XRequestID:
      schema:
        type: string
    XRateLimitLimit:
      schema:
        oneOf:
          - type: integer
          - type: string
            enum:
              - unlimited
      description: Per-key cap, or `unlimited` for a privileged key.
    XRateLimitRemaining:
      schema:
        oneOf:
          - type: integer
          - type: string
            enum:
              - unlimited
      description: Remaining requests, or `unlimited` for a privileged key.
    XRateLimitReset:
      schema:
        type: integer
    AcceptRanges:
      schema:
        type: string
        example: bytes
    ContentRange:
      schema:
        type: string
        example: bytes 0-1023/4096
    ContentLength:
      schema:
        type: integer
    ContentType:
      schema:
        type: string
        example: video/mp4
  schemas:
    ThreadsVideoRequest:
      type: object
      required:
        - url
      properties:
        url:
          type: string
          format: uri
        download_mode:
          type: string
          enum:
            - url
            - metadata_only
            - proxy
          default: url
        include_metadata:
          type: boolean
          default: true
        include_thumbnail:
          type: boolean
          default: true
    ThreadsVideoResponse:
      type: object
      required:
        - success
        - source
        - input_url
        - normalized_url
        - post
        - media
        - meta
      properties:
        success:
          type: boolean
          enum:
            - true
        source:
          type: string
          enum:
            - threads
        input_url:
          type: string
        normalized_url:
          type: string
        post:
          $ref: "#/components/schemas/Post"
        media:
          type: array
          items:
            $ref: "#/components/schemas/Media"
        expires_at:
          type: string
          format: date-time
          nullable: true
        meta:
          $ref: "#/components/schemas/Meta"
    Post:
      type: object
      properties:
        id:
          type: string
          nullable: true
        username:
          type: string
          nullable: true
        permalink:
          type: string
        text:
          type: string
          nullable: true
        created_at:
          type: string
          nullable: true
        metrics:
          type: object
          nullable: true
    Media:
      allOf:
        - $ref: "#/components/schemas/MediaVariant"
        - type: object
          properties:
            type:
              type: string
              enum:
                - video
                - image
                - audio
                - unknown
            url:
              type: string
              nullable: true
            download_url:
              type: string
              nullable: true
            thumbnail_url:
              type: string
              nullable: true
            mime_type:
              type: string
              nullable: true
            width:
              type: integer
              nullable: true
            height:
              type: integer
              nullable: true
            duration_seconds:
              type: number
              nullable: true
            size_bytes:
              type: integer
              nullable: true
            quality:
              type: string
              nullable: true
    MediaVariant:
      type: object
      properties:
        variant_id:
          type: string
          nullable: true
        is_default:
          type: boolean
          nullable: true
        bitrate_kbps:
          type: integer
          nullable: true
        source_rank:
          type: integer
          nullable: true
    Meta:
      type: object
      properties:
        processing_time_ms:
          type: integer
        cached:
          type: boolean
        cache_ttl_seconds:
          type: integer
        request_id:
          type: string
    ErrorResponse:
      type: object
      required:
        - success
        - error
        - meta
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          $ref: "#/components/schemas/ErrorObject"
        meta:
          $ref: "#/components/schemas/Meta"
    DownloadErrorResponse:
      allOf:
        - $ref: "#/components/schemas/ErrorResponse"
    ErrorObject:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          enum:
            - INVALID_JSON
            - INVALID_URL
            - UNSUPPORTED_DOMAIN
            - UNSUPPORTED_URL_FORMAT
            - UNAUTHORIZED
            - API_KEY_DISABLED
            - API_KEY_EXPIRED
            - API_KEY_REVOKED
            - POST_NOT_FOUND
            - CONTENT_NOT_ACCESSIBLE
            - VIDEO_NOT_FOUND
            - DOWNLOAD_MODE_NOT_SUPPORTED
            - SOURCE_UNAVAILABLE
            - SOURCE_TIMEOUT
            - SOURCE_RESPONSE_TOO_LARGE
            - DOWNLOAD_TOKEN_NOT_FOUND
            - DOWNLOAD_TOKEN_EXPIRED
            - DOWNLOAD_TOKEN_CONSUMED
            - DOWNLOAD_SOURCE_UNAVAILABLE
            - DOWNLOAD_TOO_LARGE
            - DOWNLOAD_ABORTED
            - RATE_LIMIT_EXCEEDED
            - INTERNAL_ERROR
        message:
          type: string
        details:
          type: object
          additionalProperties: true
  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            unauthorized:
              value:
                success: false
                error:
                  code: UNAUTHORIZED
                  message: Missing or invalid API key.
                meta:
                  request_id: req_example
                  processing_time_ms: 1
  examples:
    InvalidUrl:
      value:
        success: false
        error:
          code: INVALID_URL
          message: The provided URL is not a valid URL.
        meta:
          request_id: req_example
          processing_time_ms: 1
    UnsupportedDomain:
      value:
        success: false
        error:
          code: UNSUPPORTED_DOMAIN
          message: Only Threads URLs are supported.
        meta:
          request_id: req_example
          processing_time_ms: 1
    VideoNotFound:
      value:
        success: false
        error:
          code: VIDEO_NOT_FOUND
          message: No video was found in the provided Threads post.
        meta:
          request_id: req_example
          processing_time_ms: 310
    ContentNotAccessible:
      value:
        success: false
        error:
          code: CONTENT_NOT_ACCESSIBLE
          message: The Threads post is not publicly accessible or cannot be processed
            without authorization.
        meta:
          request_id: req_example
          processing_time_ms: 288
    SourceTimeout:
      value:
        success: false
        error:
          code: SOURCE_TIMEOUT
          message: Threads source did not respond in time.
        meta:
          request_id: req_example
          processing_time_ms: 7000
    RateLimitExceeded:
      value:
        success: false
        error:
          code: RATE_LIMIT_EXCEEDED
          message: Rate limit exceeded. Please try again later.
        meta:
          request_id: req_example
          processing_time_ms: 3
    DownloadTokenExpired:
      value:
        success: false
        error:
          code: DOWNLOAD_TOKEN_EXPIRED
          message: The download token has expired.
        meta:
          request_id: req_example
          processing_time_ms: 1
    DownloadTokenConsumed:
      value:
        success: false
        error:
          code: DOWNLOAD_TOKEN_CONSUMED
          message: The download token has already been used.
        meta:
          request_id: req_example
          processing_time_ms: 1
    DownloadTooLarge:
      value:
        success: false
        error:
          code: DOWNLOAD_TOO_LARGE
          message: The media file is too large to proxy.
        meta:
          request_id: req_example
          processing_time_ms: 20
    DownloadAborted:
      value:
        success: false
        error:
          code: DOWNLOAD_ABORTED
          message: The download was aborted.
        meta:
          request_id: req_example
          processing_time_ms: 120
