ClipOnIt Videos API

Import videos (file or URL), processing status, detected clips.

GitHub OpenAPI

Operations 11

GET /api/videos List my videos (newest first) #
POST /api/videos Upload a video file (multipart) #
POST /api/videos/url Import a video from a public URL (YouTube, etc.) #
GET /api/videos/{id} Video with its detected clips #
DELETE /api/videos/{id} Delete a video, its clips, exports and B-roll files #
POST /api/videos/{id}/retry Re-run processing of a failed video #
POST /api/videos/{id}/regenerate Delete the clips and re-run clip detection #
GET /api/videos/{id}/filmstrip Thumbnail sheet of the whole video (timeline) #
GET /api/videos/{id}/clips.csv Clips list as CSV #
POST /api/videos/{id}/clips Create a clip by hand from exact bounds #
GET /api/videos/{id}/transcript Full transcript of a video #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/cliponit-app:cliponit-app-videos-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

cliponit-app-videos-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Cliponit App Videos API
  version: 1.0.0
  contact:
    name: ClipOnIt
    url: https://cliponit.app
  license:
    name: Proprietary
    url: https://cliponit.app
  description: 'Operations tagged videos across 2 of this provider''s published API definitions: cliponit-app-openapi.json, cliponit-app-openapi.yml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://cliponit.app
  description: Production
- url: http://localhost:3000
  description: Local dev (cargo run)
security:
- ApiKey: []
- SessionCookie: []
- OAuth2:
  - clips
tags:
- name: Videos
  description: Import videos (file or URL), processing status, detected clips.
paths:
  /api/videos:
    get:
      tags:
      - Videos
      operationId: listVideos
      summary: List my videos (newest first)
      description: Each item includes `status`, `progress`, `duration_s`, `clips_count`. Poll this or `getVideo` every ~10 s while `status == "processing"`.
      responses:
        '200':
          description: Videos
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Video'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      tags:
      - Videos
      operationId: uploadVideo
      summary: Upload a video file (multipart)
      description: 'Field `file`: mp4/mov/webm, streamed to disk, ≤ `quota.max_bytes` of the plan (3 GB free … 20 GB pro). Checks **before** receiving the body: 402 if the monthly video quota is reached or `Content-Length` exceeds the plan; free plan also refuses videos longer than 1200 s (20 min). Returns the video with `status: processing`; processing = probing → transcribing → detecting, usually 1-5 min for a 20 min video. Then call `getVideo` to read the clips. The plan byte limit is also checked before writing each received chunk, including requests without Content-Length. Video insertion, quota check and job enqueue commit atomically; a queue failure leaves no new video or consumed quota.'
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required:
              - file
              properties:
                file:
                  type: string
                  format: binary
      responses:
        '200':
          description: Created video (processing)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Video'
        '400':
          $ref: '#/components/responses/BadRequest'
        '402':
          $ref: '#/components/responses/PaymentRequired'
    servers:
    - url: https://cliponit.app
      description: Production
    - url: http://localhost:3000
      description: Local dev (cargo run)
  /api/videos/url:
    post:
      tags:
      - Videos
      operationId: importVideoUrl
      summary: Import a video from a public URL (YouTube, etc.)
      description: 'Downloads with yt-dlp (≤ 1080p). Same quota checks as upload (402). Returns the video immediately with `status: processing`, `progress: downloading`, `title` = the URL until metadata is known. Duration/size limits are enforced after download: the video becomes `failed` with a clear `error`. Poll `getVideo` until `status` is `ready` or `failed`. URLs with credentials, local hostnames or literal private/reserved IPs are rejected. Downloads have a one-hour deadline, a 30-second socket timeout and partial-file size monitoring. Initial URL validation does not replace egress protection against redirects or DNS rebinding. Video insertion, quota check and job enqueue commit atomically.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - url
              properties:
                url:
                  type: string
                  format: uri
                  example: https://www.youtube.com/watch?v=e7Zl5VWejQc
      responses:
        '200':
          description: Created video (processing)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Video'
        '400':
          $ref: '#/components/responses/BadRequest'
        '402':
          $ref: '#/components/responses/PaymentRequired'
    servers:
    - url: https://cliponit.app
      description: Production
    - url: http://localhost:3000
      description: Local dev (cargo run)
  /api/videos/{id}:
    parameters:
    - $ref: '#/components/parameters/VideoId'
    get:
      tags:
      - Videos
      operationId: getVideo
      summary: Video with its detected clips
      description: '`state` (queued|running|ready|failed), `queue_position` and `eta_s` tell how long to wait (poll every ~10 s, or use the MCP `wait_video` tool). Clips are sorted chronologically (`position`). Pick by `score` (0-100, estimated viral potential) and read `reason`. `description`/`hashtags` are null until `describeClip` is called. `thumbnail_url` may be null while thumbnails are generated.'
      responses:
        '200':
          description: Video + clips
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoDetail'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags:
      - Videos
      operationId: deleteVideo
      summary: Delete a video, its clips, exports and B-roll files
      responses:
        '200':
          $ref: '#/components/responses/Ok'
        '404':
          $ref: '#/components/responses/NotFound'
    servers:
    - url: https://cliponit.app
      description: Production
    - url: http://localhost:3000
      description: Local dev (cargo run)
  /api/videos/{id}/retry:
    post:
      tags:
      - Videos
      operationId: retryVideo
      summary: Re-run processing of a failed video
      description: 'Only for `status: failed` (400 otherwise). Re-downloads if the source came from a URL and the file is gone. 402 if the user is above quota.'
      parameters:
      - $ref: '#/components/parameters/VideoId'
      responses:
        '200':
          description: Video (processing)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Video'
        '400':
          $ref: '#/components/responses/BadRequest'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '404':
          $ref: '#/components/responses/NotFound'
    servers:
    - url: https://cliponit.app
      description: Production
    - url: http://localhost:3000
      description: Local dev (cargo run)
  /api/videos/{id}/regenerate:
    post:
      tags:
      - Videos
      operationId: regenerateClips
      summary: Delete the clips and re-run clip detection
      description: Reuses the existing transcript and queues detection atomically with the status change. Existing clips, edits and files remain available until the new detection replaces them successfully in one transaction. Concurrent requeues cannot create duplicate active jobs. 400 when already processing; 402 when above the plan quota.
      parameters:
      - $ref: '#/components/parameters/VideoId'
      responses:
        '200':
          description: Video (processing)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Video'
        '400':
          $ref: '#/components/responses/BadRequest'
        '402':
          $ref: '#/components/responses/PaymentRequired'
    servers:
    - url: https://cliponit.app
      description: Production
    - url: http://localhost:3000
      description: Local dev (cargo run)
  /api/videos/{id}/filmstrip:
    get:
      tags:
      - Videos
      operationId: getVideoFilmstrip
      summary: Thumbnail sheet of the whole video (timeline)
      description: One JPEG with ~120 frames tiled in `cols` columns. Frame n is at column `n % cols`, row `n / cols`, time `n * interval_s`. 400 while the duration is unknown.
      parameters:
      - $ref: '#/components/parameters/VideoId'
      responses:
        '200':
          description: Filmstrip
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Filmstrip'
        '400':
          $ref: '#/components/responses/BadRequest'
    servers:
    - url: https://cliponit.app
      description: Production
    - url: http://localhost:3000
      description: Local dev (cargo run)
  /api/videos/{id}/clips.csv:
    get:
      tags:
      - Videos
      operationId: exportClipsCsv
      summary: Clips list as CSV
      description: 'Columns: position,title,start,end,duration_s (start/end as m:ss).'
      parameters:
      - $ref: '#/components/parameters/VideoId'
      responses:
        '200':
          description: CSV file
          content:
            text/csv:
              schema:
                type: string
        '404':
          $ref: '#/components/responses/NotFound'
    servers:
    - url: https://cliponit.app
      description: Production
    - url: http://localhost:3000
      description: Local dev (cargo run)
  /api/videos/{id}/clips:
    post:
      tags:
      - Videos
      operationId: createClip
      summary: Create a clip by hand from exact bounds
      description: Owner only, video `ready` (400 otherwise). `0 ≤ start_s < end_s ≤ duration`, 3 ≤ duration ≤ 180 s. With `snap` (default true) each bound within 0.6 s of a transcript sentence start/end is snapped to it, else to the nearest word boundary within 0.6 s (never further), then rounded to 0.01 s. Without `title`, the clip is returned at once with its first words as title, and an AI title (and `reason`) is written in the background a few seconds later. Inserted at its chronological position (later clips move down one rank), returned with `thumbnail_url`; `score` is null. Does not count against the monthly video quota.
      parameters:
      - $ref: '#/components/parameters/VideoId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - start_s
              - end_s
              properties:
                start_s:
                  type: number
                  minimum: 0
                  example: 62.4
                end_s:
                  type: number
                  example: 104.9
                title:
                  type: string
                  maxLength: 80
                  example: Why the crew flies Orion by hand
                snap:
                  type: boolean
                  default: true
      responses:
        '200':
          description: New clip
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Clip'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
    servers:
    - url: https://cliponit.app
      description: Production
    - url: http://localhost:3000
      description: Local dev (cargo run)
  /api/videos/{id}/transcript:
    get:
      tags:
      - Videos
      operationId: getVideoTranscript
      summary: Full transcript of a video
      description: Segments `{start, end, text, words:[{start, end, word}]}` in absolute seconds; `[]` before transcription.
      parameters:
      - $ref: '#/components/parameters/VideoId'
      responses:
        '200':
          description: Segments
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    start:
                      type: number
                    end:
                      type: number
                    text:
                      type: string
                    words:
                      type: array
                      items:
                        type: object
                        properties:
                          start:
                            type: number
                          end:
                            type: number
                          word:
                            type: string
    servers:
    - url: https://cliponit.app
      description: Production
    - url: http://localhost:3000
      description: Local dev (cargo run)
components:
  schemas:
    Error:
      type: object
      required:
      - error
      properties:
        error:
          type: string
          description: Human-readable message (French or English)
    ClipBase:
      type: object
      required:
      - id
      - video_id
      - position
      - title
      - start_s
      - end_s
      - created_at
      properties:
        id:
          $ref: '#/components/schemas/Uuid'
        video_id:
          $ref: '#/components/schemas/Uuid'
        position:
          type: integer
          description: Chronological order
        title:
          type: string
        start_s:
          type: number
          description: Absolute start in the source video (seconds)
        end_s:
          type: number
          description: Absolute end (seconds)
        created_at:
          type: string
    VideoDetail:
      allOf:
      - $ref: '#/components/schemas/Video'
      - type: object
        required:
        - clips
        properties:
          clips:
            type: array
            items:
              $ref: '#/components/schemas/Clip'
          heatmap:
            type: array
            description: YouTube "Most replayed" curve (URL imports only, about 100 slices). Absent when YouTube does not provide it (upload, other site, recent or little-watched video). `value` is relative to the most replayed slice of the video (1 = max).
            items:
              type: object
              required:
              - start_s
              - end_s
              - value
              properties:
                start_s:
                  type: number
                end_s:
                  type: number
                value:
                  type: number
                  minimum: 0
                  maximum: 1
    Filmstrip:
      type: object
      required:
      - url
      - cols
      - rows
      - interval_s
      - frame_w
      - frame_h
      properties:
        url:
          type: string
          description: JPEG sheet under /files/thumbs/
        cols:
          type: integer
        rows:
          type: integer
        interval_s:
          type: number
          description: Seconds between two frames
        frame_w:
          type: integer
        frame_h:
          type: integer
    Video:
      type: object
      required:
      - id
      - title
      - size_bytes
      - status
      - state
      - is_demo
      - created_at
      - clips_count
      - url
      properties:
        id:
          $ref: '#/components/schemas/Uuid'
        title:
          type: string
        path:
          type: string
          description: Server-side file path (informational)
        url:
          type: string
          description: Source file, relative to the server, e.g. `/files/uploads/<id>.mp4`
        thumbnail_url:
          type:
          - string
          - 'null'
        thumbnail_path:
          type:
          - string
          - 'null'
        size_bytes:
          type: integer
        duration_s:
          type:
          - number
          - 'null'
          description: Seconds, null until probed
        width:
          type:
          - integer
          - 'null'
        height:
          type:
          - integer
          - 'null'
        status:
          type: string
          enum:
          - processing
          - ready
          - failed
        state:
          type: string
          enum:
          - queued
          - running
          - ready
          - failed
          description: '`processing` split by the job queue: `queued` (waiting behind other videos) or `running`; otherwise = `status`'
        queue_position:
          type:
          - integer
          - 'null'
          description: 1-based position in the processing queue while `state` is `queued`
        eta_s:
          type:
          - number
          - 'null'
          description: Estimated seconds until `ready` (0.25 × (duration + durations of the videos ahead)); null when not processing
        progress:
          type:
          - string
          - 'null'
          enum:
          - downloading
          - probing
          - transcribing
          - detecting
          - demo
          - null
          description: Current step while processing; null when ready/failed. `demo` = demo copy waiting for the master
        error:
          type:
          - string
          - 'null'
          description: Why it failed
        is_demo:
          type: integer
          enum:
          - 0
          - 1
          description: 1 = the sample video seeded at signup (cannot be exported on the free plan)
        created_at:
          type: string
        clips_count:
          type: integer
    Uuid:
      type: string
      format: uuid
      example: 6f1c2d6e-3b1a-4c0f-9a0e-2f5d1c7b8e90
    Clip:
      allOf:
      - $ref: '#/components/schemas/ClipBase'
      - type: object
        properties:
          score:
            type:
            - integer
            - 'null'
            minimum: 0
            maximum: 100
            description: Estimated viral potential (AI)
          reason:
            type:
            - string
            - 'null'
            description: One sentence explaining the score, in the transcript language
          score_detail:
            type:
            - object
            - 'null'
            description: 'Sub-scores (0-10) the score is computed from: hook, standalone, payoff, emotion, value, shareability (weights 0.25/0.20/0.20/0.15/0.10/0.10). Null for older clips and search results'
            additionalProperties:
              type: integer
          description:
            type:
            - string
            - 'null'
            description: Ready-to-post caption (null until `describeClip`)
          hashtags:
            type:
            - array
            - 'null'
            items:
              type: string
            description: Without `#`
          thumbnail_url:
            type:
            - string
            - 'null'
            description: '`/files/thumbs/clips/<clip_id>.jpg`, 320 px frame at start_s + 1'
  responses:
    BadRequest:
      description: 'Validation error — the message names the field, e.g. `champ subtitles.size invalide : attendu 1..3`'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    PaymentRequired:
      description: Plan quota reached (monthly videos, daily searches, file size/duration on free)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: 'Quota mensuel atteint : passe au plan supérieur'
    Unauthorized:
      description: Missing or invalid credentials
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: unauthorized
    NotFound:
      description: Resource not found or not owned by the user
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: clip 6f1c2d6e-… introuvable (ou n'appartient pas à ce compte)
    Ok:
      description: Done
      content:
        application/json:
          schema:
            type: object
            properties:
              ok:
                type: boolean
                const: true
  parameters:
    VideoId:
      name: id
      in: path
      required: true
      description: Video id (UUID)
      schema:
        $ref: '#/components/schemas/Uuid'
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      bearerFormat: ck_<64 hex>
      description: 'Personal API key from `POST /api/me/api-key` (Starter plan and above). Also accepted: the session `token` returned by `login`/`register`. Send `Authorization: Bearer ck_…`.'
    SessionCookie:
      type: apiKey
      in: cookie
      name: session
      description: HttpOnly cookie set by `register`/`login` (browser clients).
    OAuth2:
      type: oauth2
      description: 'OAuth 2.1 for MCP connectors (Claude.ai / Claude Desktop / Claude Code / ChatGPT): discovery at `/.well-known/oauth-protected-resource` and `/.well-known/oauth-authorization-server`, dynamic client registration (RFC 7591) at `/oauth/register`, PKCE S256 required, public clients only. Access tokens (`cko_…`, 1 h) are accepted everywhere a `ck_…` key is; refresh tokens (`ckr_…`, 90 days) rotate on use.'
      flows:
        authorizationCode:
          authorizationUrl: https://cliponit.app/oauth/authorize
          tokenUrl: https://cliponit.app/oauth/token
          refreshUrl: https://cliponit.app/oauth/token
          scopes:
            clips: Import videos, read/edit clips, run exports (everything the API key allows)
externalDocs:
  description: Agent guide (llms-api.txt)
  url: https://cliponit.app/llms-api.txt
x-refined-from:
- cliponit-app-openapi.json
- cliponit-app-openapi.yml