{"openapi":"3.1.0","info":{"title":"VideoM8 Public API","version":"1.0.0","description":"Programmatic access to your VideoM8 account: create, list, and manage share links, plus analytics and leads. Authenticate with an API key as a bearer token.","contact":{"name":"VideoM8","url":"https://videom8.com"}},"servers":[{"url":"https://videom8.com/api/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"msk_live_*","description":"Send your API key as `Authorization: Bearer msk_live_…`. Keys are created via POST /account/api-keys while signed in."}},"schemas":{"Link":{"type":"object","properties":{"shareId":{"type":"string","description":"12-char hex link id."},"mode":{"type":"string","enum":["video","file","collection","site"]},"type":{"type":"string","description":"Human label for the mode."},"label":{"type":"string"},"displayTitle":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string","format":"uri","description":"Public share URL."},"showOnProfile":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"expiresAt":{"type":"string","format":"date-time"},"retentionRule":{"type":"string"},"expired":{"type":"boolean"},"fileCount":{"type":"integer"},"sizeBytes":{"type":"integer"},"durationSeconds":{"type":"integer","description":"Video only."},"streamStatus":{"type":"string","description":"Video only."},"thumbnailUrl":{"type":"string","format":"uri"},"deletedAt":{"type":"string","description":"Set when the link is in trash."}}},"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string"}}},"VideoEdits":{"type":"object","description":"The full Studio edit document for a video link. The server normalizes (sorts, clamps, merges) on save, so a partial or unclamped document is tolerated.","properties":{"version":{"type":"integer","const":1},"durationSec":{"type":"number","description":"Source duration. 0 lets the server fill it from the stored video."},"trimStartSec":{"type":"number"},"trimEndSec":{"type":"number","description":"0 means \"to the end\"."},"cuts":{"type":"array","description":"Time ranges removed from playback and renders.","items":{"type":"object","properties":{"startSec":{"type":"number"},"endSec":{"type":"number"}}}},"markers":{"type":"array","description":"Chapter/split markers.","items":{"type":"object","properties":{"atSec":{"type":"number"},"label":{"type":"string"}}}},"zoomFragments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Unique. Ids starting with \"auto-\" are treated as generated auto-zooms."},"startSec":{"type":"number"},"endSec":{"type":"number"},"zoomLevel":{"type":"number","description":"1.1–4"},"speed":{"type":"number","description":"1–10 ease-in speed."},"focusX":{"type":"number","description":"0–100 (% of frame)."},"focusY":{"type":"number","description":"0–100 (% of frame)."},"movementEnabled":{"type":"boolean"},"movementEndX":{"type":"number"},"movementEndY":{"type":"number"}}}},"speedRanges":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"startSec":{"type":"number"},"endSec":{"type":"number"},"rate":{"type":"number","description":"Playback rate, e.g. 1.5 or 2."}}}},"annotations":{"type":"array","description":"Timed popup messages shown over the Studio preview and public player.","items":{"type":"object","properties":{"id":{"type":"string"},"startSec":{"type":"number"},"endSec":{"type":"number"},"text":{"type":"string","maxLength":500},"type":{"type":"string","enum":["note","correction","cta","acknowledgement","resource"]},"position":{"type":"string","enum":["top-left","top-right","bottom-left","bottom-right","center","custom"]},"x":{"type":"number","description":"Custom horizontal position, 0–100."},"y":{"type":"number","description":"Custom vertical position, 0–100."},"mobilePosition":{"type":"string","enum":["top-left","top-right","bottom-left","bottom-right","center"]},"style":{"type":"string","enum":["brand","custom"]},"size":{"type":"string","enum":["compact","standard","large"]},"backgroundColor":{"type":"string","description":"Six-digit hex colour used for custom styling."},"textColor":{"type":"string","description":"Six-digit hex colour used for custom styling."},"borderColor":{"type":"string","description":"Six-digit hex colour used for custom styling."},"pausePlayback":{"type":"boolean"},"dismissible":{"type":"boolean"},"showOnce":{"type":"boolean"},"showUntilDismissed":{"type":"boolean"},"actionLabel":{"type":"string","maxLength":80},"actionUrl":{"type":"string","format":"uri"}}}},"mediaOverlays":{"type":"array","description":"Timed B-roll assets layered over the recording while its narration continues.","items":{"type":"object","properties":{"id":{"type":"string"},"assetId":{"type":"string"},"assetUrl":{"type":"string","format":"uri"},"posterUrl":{"type":"string","format":"uri"},"label":{"type":"string"},"startSec":{"type":"number"},"endSec":{"type":"number"},"fit":{"type":"string","enum":["cover","contain"]},"audioMode":{"type":"string","enum":["mute","mix"]},"sourceType":{"type":"string","enum":["upload","sourced","generated"]},"generationJobId":{"type":"string"},"attribution":{"type":["object","null"],"properties":{"name":{"type":"string"},"url":{"type":"string","format":"uri"}}}}}}}}}},"paths":{"/me":{"get":{"summary":"Get the authenticated account, plan, and usage.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Account summary."},"401":{"description":"Unauthenticated."}}}},"/engagement":{"get":{"summary":"Account-wide engagement analytics: views, leads, top links, daily series, and publishing results (Pro).","security":[{"bearerAuth":[]}],"parameters":[{"name":"days","in":"query","schema":{"type":"integer","minimum":1,"maximum":365},"description":"Range length ending today (default 30)."},{"name":"start","in":"query","schema":{"type":"string","format":"date"}},{"name":"end","in":"query","schema":{"type":"string","format":"date"}},{"name":"shareId","in":"query","schema":{"type":"string"},"description":"Limit to one share link."}],"responses":{"200":{"description":"Engagement dashboard payload."},"401":{"description":"Unauthenticated."},"403":{"description":"Requires Pro."}}}},"/links":{"get":{"summary":"List share links on the account.","security":[{"bearerAuth":[]}],"parameters":[{"name":"mode","in":"query","schema":{"type":"string","enum":["video","file","collection","site"]}},{"name":"tag","in":"query","schema":{"type":"string"}},{"name":"includeTrashed","in":"query","schema":{"type":"string","enum":["0","1"]}},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Paginated link list."}}},"post":{"summary":"Create a hosted (non-video) share link. Requires write scope.","description":"Upload file(s) as multipart/form-data, or send JSON with a fetchable `sourceUrl`. One file → a `file` link; multiple files (or a single `.zip`) → a `collection`; `shareMode=site` with a `.zip` containing an index.html → a hosted `site`. Enforces the same plan size/type limits and expiry rules as the browser upload. Hosted uploads are capped at 100 MB — use POST /links/video for large media. Video is rejected with code use_video_endpoint.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"A single file to host (alias of `files`)."},"files":{"type":"array","items":{"type":"string","format":"binary"},"description":"One or more files. 2+ → collection."},"shareMode":{"type":"string","enum":["file","site"],"description":"`site` hosts a static site from a single .zip (default `file`)."},"linkExpiry":{"type":"string","enum":["1h","24h","7d","never"],"description":"Defaults to 7d. `never` requires Pro."},"linkPassword":{"type":"string","description":"Optional password gate."},"linkSlug":{"type":"string","description":"Custom slug (Pro)."},"linkPathName":{"type":"string","description":"Custom slug namespace (Pro)."},"label":{"type":"string"},"leadCapture":{"type":"string","enum":["0","1"],"description":"Enable lead capture (Pro)."},"leadHeadline":{"type":"string"},"showOnProfile":{"type":"string","enum":["0","1"]}}}},"application/json":{"schema":{"type":"object","properties":{"sourceUrl":{"type":"string","format":"uri","description":"A publicly fetchable http(s) URL. The server downloads it into R2. A `.zip` expands into a collection (or a site with shareMode=site)."},"fileName":{"type":"string","description":"Overrides the filename inferred from the URL."},"shareMode":{"type":"string","enum":["file","site"]},"linkExpiry":{"type":"string","enum":["1h","24h","7d","never"]},"linkPassword":{"type":"string"},"linkSlug":{"type":"string"},"linkPathName":{"type":"string"},"label":{"type":"string"},"leadCapture":{"type":"boolean"},"leadHeadline":{"type":"string"},"showOnProfile":{"type":"boolean"}},"required":["sourceUrl"]}}}},"responses":{"201":{"description":"Created link (same shape as the Link schema); `mode` is file, collection, or site."},"400":{"description":"No file / unsupported type / use_video_endpoint / site_requires_zip / too_many_files."},"403":{"description":"Insufficient scope / invalid expiry / requires Pro."},"413":{"description":"File exceeds the API upload ceiling."}}}},"/links/video":{"post":{"summary":"Start a Cloudflare Stream direct upload for a video link. Requires write scope.","description":"Returns a one-time Stream upload URL and a shareId. Upload the bytes directly to `upload.url` (they never pass through this API), then call POST /links/video/complete to finalize.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"fileName":{"type":"string","description":"Video filename (must be a supported video extension)."},"fileSize":{"type":"integer","description":"Byte length of the video (used for plan limits + upload method)."},"linkExpiry":{"type":"string","enum":["1h","24h","7d","never"]},"linkPassword":{"type":"string"},"linkSlug":{"type":"string"},"linkPathName":{"type":"string"}},"required":["fileName","fileSize"]}}}},"responses":{"201":{"description":"{ shareId, upload: { url, method, streamUid }, slug, expiresAt, complete }."},"403":{"description":"Insufficient scope / invalid expiry."},"503":{"description":"Video hosting not configured."}}}},"/links/video/complete":{"post":{"summary":"Finalize a Stream upload after the bytes are uploaded. Requires write scope.","security":[{"bearerAuth":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"shareId":{"type":"string","description":"From POST /links/video."},"streamUid":{"type":"string","description":"From POST /links/video (upload.streamUid)."}},"required":["shareId","streamUid"]}}}},"responses":{"200":{"description":"{ shareId, url, slug, streamStatus, processing }."},"404":{"description":"Upload session not found or not owned by this account."}}}},"/links/{id}":{"get":{"summary":"Get a single link.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Link."},"404":{"description":"Not found."}}},"patch":{"summary":"Update a link (label, tags, profile visibility). Requires write scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"showOnProfile":{"type":"boolean"}}}}}},"responses":{"200":{"description":"Updated link."},"403":{"description":"Insufficient scope."}}},"delete":{"summary":"Move a link to trash (recoverable). Requires write scope.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Trashed."},"403":{"description":"Insufficient scope."}}}},"/links/{id}/analytics":{"get":{"summary":"Engagement analytics for a link (Pro / analytics feature).","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"range","in":"query","schema":{"type":"string","description":"e.g. 7d, 30d, 90d"}}],"responses":{"200":{"description":"Analytics dashboard payload."},"403":{"description":"Requires Pro."}}}},"/links/{id}/edits":{"get":{"summary":"Get the Studio edit document for a video link (Pro).","description":"Returns `edits` (the parsed VideoEdits document), `editorRevision` for optimistic concurrency, the scalar `settings` map (export/page/CTA settings), and the current render state.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ edits, editorRevision, settings, render, transcriptStatus }."},"403":{"description":"Requires Pro."},"404":{"description":"Not a video link on this account."}}},"put":{"summary":"Save the Studio edit document (Pro, write scope). PATCH is an alias.","description":"Merge-save: the current editor settings are loaded server-side and the body is merged over them, so `{ \"edits\": { … } }` alone is a safe save — omitted settings keep their values. Echo `editorRevision` from a prior GET for strict optimistic concurrency; a stale value returns 409 revision_conflict. Scalar settings (exportAspect, pageTitle, musicEnabled, …) may be included alongside `edits` to update them in the same save.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"edits":{"$ref":"#/components/schemas/VideoEdits"},"editorRevision":{"type":"string","description":"From a prior GET. Omit to save against the current revision."}}}}}},"responses":{"200":{"description":"The saved document (same shape as GET)."},"400":{"description":"invalid_edits."},"403":{"description":"Requires Pro / insufficient scope."},"409":{"description":"revision_conflict — reload with GET and re-apply."}}}},"/links/{id}/render":{"get":{"summary":"Timeline render status for a video link (Pro).","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ render: { status, job, features, … } }."}}},"post":{"summary":"Start (or cancel) a timeline render (Pro, write scope).","description":"Empty body (or `{}`) queues a render of the saved edits. `{ \"action\": \"cancel\" }` cancels a queued/running render. Poll GET for status.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["cancel"]}}}}}},"responses":{"200":{"description":"Cancelled."},"202":{"description":"Render queued."}}}},"/links/{id}/transcript":{"get":{"summary":"Transcript and cleanup suggestions for a video link.","description":"Returns `transcript` (status + caption cues) and `cleanup` (detected silence and filler-word ranges, ready to append to `edits.cuts`).","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"{ transcript: { status, cues }, cleanup: { silenceRanges, fillerRanges } }."}}},"post":{"summary":"Request transcript generation (write scope).","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"202":{"description":"Generation started; poll GET."}}}},"/links/{id}/broll-plan":{"get":{"summary":"Suggest sourced or generated B-roll beats from the transcript.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Transcript-timed B-roll beat suggestions."}}}},"/generation":{"get":{"summary":"List generation models and the account credit balance.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"Models, included credits, and current balance."}}},"post":{"summary":"Queue generated B-roll (write scope).","description":"Provide an Idempotency-Key header to make retries safe. Poll GET /generation/{id}.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["shareId","prompt"],"properties":{"shareId":{"type":"string"},"prompt":{"type":"string"},"avoid":{"type":"string"},"model":{"type":"string"},"generationType":{"type":"string","enum":["video","image"]},"durationSeconds":{"type":"integer","minimum":3,"maximum":10},"aspectRatio":{"type":"string","enum":["16:9","9:16","1:1"]},"imageInput":{"type":"string","format":"uri"}}}}}},"responses":{"202":{"description":"Generation queued and credits reserved."},"402":{"description":"Insufficient credits."}}}},"/generation/{id}":{"get":{"summary":"Get a generation job and its ingested asset.","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Queued, processing, completed, or failed job."},"404":{"description":"Not found."}}}},"/leads":{"get":{"summary":"List captured leads (Pro / analytics feature).","security":[{"bearerAuth":[]}],"parameters":[{"name":"shareId","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":100,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Paginated lead list ({ leads, pagination })."},"403":{"description":"Requires Pro."}}}}}}