ClipOnIt Videos API
Import videos (file or URL), processing status, detected clips.
Import videos (file or URL), processing status, detected clips.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
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.curl "https://apis.io/api/v1/apis/cliponit-app:cliponit-app-videos-api"
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.
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: 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