Developer API

    Integrate video hosting and streaming capabilities into your applications with our RESTful API. Upload videos, manage content, and deliver media at scale.

    https://api.streamvault.one/v1
    Authorization: Bearer YOUR_API_KEY
    Recently shipped — May 2026

    Projects API with subprojects, Auto-Delete Plans, Branding (per-project logo with auto-extracted color palette), and the full Player Skin API with 4-level inheritance (account / project / channel / video). See the corresponding sections below for the full reference.

    Quick Start

    Get your API key and make your first request in 5 minutes.

    Get your API keys

    Your API requests are authenticated using API keys. Any request that doesn't include an API key will return an error. You can generate an API key from your Dashboard under Videos → Global Settings → API Keys.

    Note: The API key does not expire. The key should be used on the server-end and not on the client-end. If the API key gets compromised, you can delete it from the dashboard and generate a new one.

    API Authentication

    Use Bearer authentication. Send an Authorization header in all requests:

    http
    Authorization: Bearer YOUR_API_KEY

    Base URL

    StreamVault has a single canonical base URL. All endpoints — including video uploads — live under it:

    PurposeURL
    API base (everything: videos, uploads, channels, streams, projects, branding)https://api.streamvault.one/v1
    Health check (returns Worker version + uptime)https://api.streamvault.one/v1/health

    Make your first request

    Send an authenticated request to the videos endpoint to list your videos:

    javascript
    // List your videos
    const response = await fetch('https://api.streamvault.one/v1/videos', {
      headers: {
        'Authorization': 'Bearer YOUR_API_KEY'
      }
    });
    
    const data = await response.json();
    console.log(data.items);

    Example Response

    json
    {
      "items": [
        {
          "videoId": "fFL56RU6PjvJPeE",
          "videoKey": "fFL56RU6PjvJPeE",
          "name": "My Video",
          "projectId": "40e78621-c780-4224-94fc-20a831110d91",
          "duration": 150,
          "status": { "state": "ready" },
          "created": "2024-01-15T10:30:00+00:00"
        }
      ],
      "page": 1,
      "size": 20,
      "total": 10
    }

    A page holds up to 100 videos (size, 20 by default); total is how many videos match, so keep asking for the next page until you have them all. Each video has more fields than this example shows: see List Videos.

    Rate Limits

    API requests are limited to 120 requests per minute per API key. When you exceed the limit, requests return 429 Too Many Requests until the window resets. Batch endpoints let you act on many videos in a single request — prefer them over per-video loops.

    json
    {
      "error": "Rate limit exceeded. Please slow down and try again shortly."
    }

    Errors

    Errors return a JSON body with an error message and a conventional HTTP status code:

    StatusMeaning
    400Invalid request — missing or malformed parameters
    401Missing, invalid, or revoked API key
    404Resource not found, or not owned by your account
    429Rate limit exceeded — retry after a short delay
    500Something went wrong on our side — safe to retry
    json
    {
      "error": "Invalid or revoked API key"
    }

    Videos API

    The Videos API allows you to upload, manage, and retrieve video content programmatically.

    https://api.streamvault.one/v1

    Upload Videos

    MethodEndpointDescription
    POST/videosUpload video via URL
    POST/videos/browserGet TUS upload credentials for browser-based upload

    Manage Videos

    MethodEndpointDescription
    GET/videosGet list of videos
    GET/videos/{id}Get a single video
    GET/videos/{id}/statusGet video transcoding status
    PUT/videos/{id}Update video details
    DELETE/videos/{id}Delete a video
    POST/videos/{id}/cta/copyCopy CTA from one video to others
    POST/videos/change-projectMove videos between projects

    Additional Operations

    MethodEndpointDescription
    GET/videos/{id}/original-fileGet original video URL
    GET/videos/collected-emailsGet collected emails (last 365 days)
    POST/videos/{id}/expirable-linkCreate expirable playback link
    PUT/videos/image/{id}Update video thumbnail
    POST/videos/captions/{id}Add captions/subtitles
    DELETE/videos/captions?videoId=&captionsId=Delete captions/subtitles

    Video Status Codes

    StatusValueDescription
    Na0Not available
    Uploaded1Uploaded
    Processing2Transcoding in progress
    Published3Ready for playback
    Error-1Transcoding failed
    Deleted50Deleted

    Quick Example

    javascript
    // Upload a video via URL
    const response = await fetch('https://api.streamvault.one/v1/videos', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer YOUR_API_KEY',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        channelKey: 'abc123',
        url: 'https://example.com/video.mp4'
      })
    });
    
    const video = await response.json();
    console.log(video.iframeLink);
    View full Videos API reference

    Projects API

    Projects are folders that group your videos. They support arbitrary nesting via parent_id so you can model brands → shows → seasons (or any hierarchy that fits your workflow).

    Endpoints

    MethodEndpointDescription
    GET/projectsList your projects, paged (page, size, parentId, search, sort)
    POST/projectsCreate a project (or subproject via parent_id)
    GET/projects/{id}Get one project, with its sub-project and video counts
    PATCH/projects/{id}Update name / description / parent_id
    DELETE/projects/{id}Delete (cascade-deletes videos in the project)

    Create a project

    bash
    curl -X POST https://api.streamvault.one/v1/projects \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Kinana VOD AR",
        "description": "Arabic news bulletins for the Kinana brand",
        "parent_id": null
      }'

    Response

    json
    {
      "id": "448ba8b6-eaa6-4ff8-80be-9a075e47c642",
      "user_id": "...",
      "name": "Kinana VOD AR",
      "description": "...",
      "parent_id": null,
      "created_at": "2026-05-07T...",
      "updated_at": "2026-05-07T..."
    }

    Subprojects (parent_id)

    Pass parent_id (a UUID) to create a subproject under an existing parent. Set parent_id to null for a top-level project.

    Update / move a project

    PATCH /v1/projects/{id} accepts a partial body. Setting parent_id to another project's UUID re-parents it; setting it to null makes it top-level.

    json
    { "name": "Renamed", "description": "New description", "parent_id": "uuid-of-new-parent-or-null" }

    Listing sub-projects

    GET /v1/projects is paged and lists projects at every level of nesting. Add parentId to list only the sub-projects directly below one project. A video belongs to exactly one project, so a parent that only holds sub-projects has no videos of its own: to get everything under it, use includeSubprojects=true on GET /v1/videos (below).

    Query parameters: GET /v1/projects
    ParameterTypeRequiredDescription
    pageintegerOptional1-based page number. A page past the last one returns an empty items array and the real total.Default: 1
    sizeintegerOptionalProjects per page, 1 to 200. limit is accepted as an alias.Default: 50
    parentIdstringOptionalOnly the projects directly below this project (its id or short key). Use root for top-level projects only. Left out: projects at every level.
    searchstringOptionalName contains this text, case-insensitive. Up to 100 characters.
    sortstringOptionalcreated_at, updated_at or name. Prefix with - for descending.Default: -created_at

    The sub-projects of one project

    bash
    curl "https://api.streamvault.one/v1/projects?parentId=PROJECT_ID&size=100" \
      -H "Authorization: Bearer YOUR_API_KEY"
    json
    {
      "items": [
        { "id": "...", "name": "1. Basic Forex Education", "parent_id": "PROJECT_ID", "created_at": "...", "updated_at": "..." }
      ],
      "page": 1,
      "size": 100,
      "total": 13,
      "hasMore": false
    }

    total is the number of matching projects across all pages; hasMore is true while there is another page. To read every project:

    javascript
    const all = [];
    for (let page = 1; ; page++) {
      const res = await fetch('https://api.streamvault.one/v1/projects?size=200&page=' + page, {
        headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
      });
      const data = await res.json();
      all.push(...data.items);
      if (!data.hasMore) break;
    }

    Videos of a project and everything below it

    GET /v1/videos?projectId=ID lists the videos of exactly that project, so a project that only holds sub-projects returns none. Add includeSubprojects=true to cover the project and every sub-project below it, at any depth. Every video says which project it sits in (projectId), so you can tell the sub-projects apart.

    bash
    curl "https://api.streamvault.one/v1/videos?projectId=PROJECT_ID&includeSubprojects=true&size=100" \
      -H "Authorization: Bearer YOUR_API_KEY"
    json
    {
      "items": [
        { "videoId": "...", "name": "...", "projectId": "ID-OF-THE-SUB-PROJECT-IT-SITS-IN", "...": "..." }
      ],
      "page": 1,
      "size": 100,
      "total": 132,
      "includeSubprojects": true,
      "projectCount": 14
    }

    projectCount is how many projects the listing covered: the project itself plus the sub-projects below it. One call covers a project and up to 149 sub-projects under it. A bigger tree is refused with 400 and "code": "SUBTREE_TOO_LARGE": list its sub-projects with parentId and request their videos one at a time.

    Counts on one project

    GET /v1/projects/{id} also says how big the tree under the project is. A count is null when it could not be worked out (a tree deeper than 10 levels or larger than 2,000 projects, or a temporary error); it is never a number that is too small.

    json
    {
      "id": "PROJECT_ID",
      "name": "FXEM VOD AR",
      "parent_id": null,
      "ancestor_chain": [],
      "child_count": 13,
      "video_count": 0,
      "descendant_count": 13,
      "subtree_video_count": 132
    }
    Count fields
    ParameterTypeRequiredDescription
    child_countintegerOptionalSub-projects directly below this project.
    video_countintegerOptionalVideos directly in this project (what GET /v1/videos?projectId= lists).
    descendant_countintegerOptionalSub-projects at every level below this project.
    subtree_video_countintegerOptionalVideos in this project and in every sub-project below it (what includeSubprojects=true lists).

    Errors

    StatusValueDescription
    Bad request400parentId, search, sort or includeSubprojects cannot be read (the message names it), includeSubprojects was sent without a projectId, or the tree is too large (SUBTREE_TOO_LARGE)
    Not found404The project named by parentId, or by projectId together with includeSubprojects, is not one of yours

    An empty value counts as leaving the parameter out. These parameters are checked strictly: a value that cannot be read is an error that names it, never silently ignored, because an ignored filter would hand back every project and look like a result.

    Auto-Delete Plans

    Delete old videos automatically. A plan names the projects it covers, an age in days and an optional title filter; while a plan is active, an hourly job permanently deletes the videos it matches. Manage plans in the dashboard under Settings → Auto Delete, or with the endpoints below. Mirrors DynTube's /auto-delete-plans URL shape so existing DynTube integrations work with just a host swap.

    Endpoints

    MethodEndpointDescription
    POST/auto-delete-plans/searchList with body-based pagination (DynTube-compatible)
    GET/auto-delete-plansList with query params (?page=1&size=50)
    GET/auto-delete-plans/{id}Get one plan
    GET/auto-delete-plans/{id}/previewThe videos the plan matches right now, oldest first — deletes nothing
    POST/auto-delete-plansCreate a plan
    PUT/auto-delete-plans/{id}Update a plan — send only the fields you want to change; the others stay as they are
    PATCH/auto-delete-plans/{id}Same as PUT
    DELETE/auto-delete-plans/{id}Delete a plan

    Plan shape

    json
    {
      "id": "uuid",
      "name": "Daily News Cleanup",
      "project_ids": ["uuid1", "uuid2"],
      "retention_days": 15,
      "title_filter": { "enabled": false, "value": "" },
      "is_active": true,
      "last_run_at": "2026-05-09T12:00:00Z",
      "last_run_deleted_count": 7,
      "last_run_error_count": 0,
      "created_at": "2026-05-01T00:00:00Z",
      "updated_at": "2026-05-09T12:00:00Z"
    }
    • project_ids — list of project UUIDs the rule applies to; each project's sub-projects are covered too. Every entry must be the id of one of your own projects, otherwise the request is refused. Empty array (or no project_ids at all) = applies to every project you own.
    • retention_days — videos older than this many days are deleted. A whole number from 1 to 3650.
    • title_filter.enabled + value — substring match (case-insensitive); the text is taken literally, so % and _ are ordinary characters. Videos must match the filter to be deleted. A value without "enabled": true does nothing — the plan then covers every video of its projects.
    • is_active — false pauses the plan without deleting it. A plan created through the API is active unless you send is_active: false; the dashboard always creates plans paused.

    How a run works

    • Once an hour, every active plan is evaluated. A video matches when it is in a covered project (or one of its sub-projects), is older than retention_days and passes the title filter, if one is on.
    • Matches are deleted oldest first, at most 40 videos per hourly run, shared by all plans. A bigger backlog is worked off over several hours.
    • A video that is still being encoded is skipped and tried again the next hour.
    • A deleted video disappears from your account at once. Its files are removed from storage about 24 hours later.
    • If a step fails (for example a storage error) the video is kept and tried again the next hour. last_run_at, last_run_deleted_count and last_run_error_count describe the latest run.
    • Deleting is permanent. Pause a plan (is_active: false) or delete it to stop.

    Safe way to start: create the plan paused, read its preview, then switch it on.

    • Only the fields listed above are accepted. A key the API does not know (project_id instead of project_ids, say) answers 400, names the key and changes nothing. The fields a GET returns (id, created_at, last_run_*) may be sent back; they are ignored. A flag must be true or false — "false" in quotes is refused. A plan with no project list covers all your projects, so always send is_active: false with a new plan and check its preview before you switch it on.
    • Edits made through the API apply at once. The next hourly run uses the new projects, age or title filter of an active plan. The dashboard is more careful: changing what an active plan selects pauses it, so the new selection can be previewed first. If you change an active plan through the API, send is_active: false with the change, preview, then switch it back on.
    • A plan id that does not exist (or is not yours) answers 404 on every endpoint, the preview included. Creating or changing a plan with a project id that is not yours, a retention that is not a whole number of days, or a list that holds anything but project ids answers 400 with the reason, and nothing is created or changed.

    Create a plan

    bash
    curl -X POST https://api.streamvault.one/v1/auto-delete-plans \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Daily News 15-day cleanup",
        "project_ids": ["uuid-of-project-1"],
        "retention_days": 15,
        "title_filter": { "enabled": true, "value": "daily news" },
        "is_active": false
      }'

    Preview, then start

    bash
    # What would this plan delete? (nothing is deleted)
    curl https://api.streamvault.one/v1/auto-delete-plans/PLAN_ID/preview \
      -H "Authorization: Bearer YOUR_API_KEY"
    
    # Looks right? Switch it on — it runs from the next hour
    curl -X PUT https://api.streamvault.one/v1/auto-delete-plans/PLAN_ID \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "is_active": true }'
    json
    {
      "plan_id": "uuid",
      "evaluated_at": "2026-10-12T09:00:00Z",
      "candidate_count": 485,
      "candidates": [
        { "video_id": "uuid", "name": "Daily news 29 Sep", "created_at": "2026-09-29T13:18:51Z", "age_days": 13, "storage_location": "b2" }
      ]
    }
    bash
    curl -X POST https://api.streamvault.one/v1/auto-delete-plans/search \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "page": 1, "pageSize": 50 }'

    Response

    json
    {
      "plans": [ /* ... */ ],
      "pager": { "page": 1, "pageSize": 50, "totalResults": 3, "totalPages": 1 }
    }

    Branding (Project Logo)

    Upload a brand logo per project, automatically extract its color palette, and apply it to that project's player skin (controls color, big play disc, frame, watermark). The same logo also doubles as a corner overlay on the player.

    Endpoints

    MethodEndpointDescription
    PUT/projects/{id}/logoUpload a logo (multipart) and auto-extract its palette
    GET/projects/{id}/logoGet the logo URL + extracted palette
    DELETE/projects/{id}/logoRemove the logo (does not clear applied colors)
    POST/projects/{id}/logo/applyRe-apply the saved palette to player_settings

    Upload a file

    bash
    curl -X PUT https://api.streamvault.one/v1/projects/PROJECT_ID/logo \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -F "file=@./logo.png" \
      -F "auto_apply=true" \
      -F 'palette={"vibrant":"#FFC800","dark_muted":"#121218","muted":"#FFFFFF","text_on_vibrant":"#000000"}'

    Or pass a remote URL

    bash
    curl -X PUT https://api.streamvault.one/v1/projects/PROJECT_ID/logo \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -F "url=https://example.com/logo.png" \
      -F "auto_apply=true"
    • Max 2 MB
    • Accepted: PNG, JPEG, WebP, SVG (SVG is rasterized at 256×256 server-side before extraction)
    • auto_apply (default true) — write the extracted palette to project_player_settings (controls_color, big_play_color, big_play_icon_color, player_color, watermark_color)
    • palette (optional, JSON) — pre-extracted palette to use directly. When provided, the server skips its own extraction and uses your values verbatim. Recommended from a browser context where @vibrant/core is already running client-side; server-side extraction is best-effort and may return only a partial palette. Shape: { vibrant?, dark_vibrant?, light_vibrant?, muted?, dark_muted?, light_muted?, text_on_vibrant? } — all fields hex strings, all optional/nullable.

    Response (palette extraction)

    json
    {
      "logo_url": "https://api.streamvault.one/b2/project-logos/PROJECT_ID/logo.png",
      "logo_palette": {
        "vibrant": "#FF6B35",
        "dark_vibrant": "#7A2E0E",
        "light_vibrant": "#FFB89A",
        "muted": "#806257",
        "dark_muted": "#3B2A20",
        "light_muted": "#D4C4BA",
        "text_on_vibrant": "#FFFFFF",
        "extracted_at": "2026-05-09T22:00:00Z"
      },
      "applied": true,
      "applied_fields": [
        "controls_color", "big_play_color", "big_play_icon_color",
        "player_color", "watermark_color"
      ]
    }

    Re-apply palette

    If you previously declined auto_apply, or you've manually overridden colors and want to revert to the logo-derived palette:

    bash
    curl -X POST https://api.streamvault.one/v1/projects/PROJECT_ID/logo/apply \
      -H "Authorization: Bearer YOUR_API_KEY"

    Logo overlay & deletion

    When the project's logo_overlay_enabled player setting is true, the uploaded logo renders as a corner watermark on every player in the project. Position, size, and opacity are configured under Player Skin.

    Delete logo

    bash
    curl -X DELETE https://api.streamvault.one/v1/projects/PROJECT_ID/logo \
      -H "Authorization: Bearer YOUR_API_KEY"

    Note: deleting the logo does not clear the applied colors from project_player_settings. Use the Player Skin API or the dashboard's "Reset to inherit" links to clear those.

    Player Skin API

    Configure how the video player looks and behaves. Settings can be set at account, project (including sub-project), channel and video level; the most specific level wins, field by field.

    When controls_color is set (either directly or via the Branding palette auto-apply), the entire control chrome paints with that color: progress fill, seek handle, volume slider, time display, playback speed pill, and every icon (play/pause, next, volume, settings, picture-in-picture, fullscreen). The big-play disc uses big_play_color, which falls back to controls_color when unset. Net effect: one brand color drives every accent on the player.

    Inheritance hierarchy

    text
    Per-video override     (highest priority, video.player_settings JSONB)
       ↓
    Channel default        (channel_player_settings — look and captions only)
       ↓
    Sub-project default    (project_player_settings of the sub-project)
       ↓
    Project default        (project_player_settings — also where logo palette writes)
       ↓
    Account default        (account_player_settings — set in Global Settings → Player Skins)

    A field set to null means "inherit from parent". The runtime resolution merges left-to-right; per-video wins, then channel, sub-project, project, account, then a built-in default.

    A sub-project is a project with a parent, so it has no endpoints of its own: use the project endpoints with the sub-project's id. Its settings apply on top of its parent's, which apply on top of the account's (up to ten levels deep), and a sub-project that sets nothing shows its parent's look. A channel only changes how the player looks (skin, colors, big-play button) and whether captions start on; it does not change behavior such as autoplay or loop.

    Account / project / sub-project / channel / video endpoints

    MethodEndpointDescription
    GET/account/player-settingsGet your account-level skin defaults
    PATCH/account/player-settingsUpdate account-level defaults
    GET/projects/{id}/player-settingsGet a project's (or sub-project's) own settings, plus the effective settings its videos get and the settings it inherits
    PATCH/projects/{id}/player-settingsOverride fields at project or sub-project level
    GET/channels/{id}/player-settingsGet this channel's own settings
    PATCH/channels/{id}/player-settingsOverride look and captions at channel level
    GET/videos/{id}/player-settingsGet this video's own overrides and the merged effective settings
    PATCH/videos/{id}/player-settingsOverride at the video level

    Sending changes

    PATCH (or PUT) changes only the fields you send. Send them flat, as in the example below, or nested under player_settings; if a field appears both ways, the flat one wins. skin and player_skin are the same field. Fields you do not send keep their value; send "merge": false to replace everything stored on that layer with what you send.

    • Clearing. null removes that override at project, sub-project, channel and video level, so the next layer shows through. The account is the bottom layer, so there null puts the built-in value back.
    • Validation. skin must be one of the skins below, and opacities are whole numbers from 0 to 100 (out-of-range numbers are clamped). A wrong type answers 400.
    • Unknown fields. A key this route does not recognize is not applied. The response lists it under ignored, so a typo is visible instead of silent.
    • Video ids. The video endpoint takes the video's id as the API returns it, or its dashboard id.

    Settings shape

    json
    {
      "skin": "modern",
      "player_color": "#0E1014",
      "controls_color": "#4CC2E0",
      "watermark_color": "#FFFFFF",
      "controls_opacity": 90,
      "watermark_opacity": 70,
    
      "big_play_button": true,
      "big_play_color": null,
      "big_play_icon_color": null,
      "big_play_opacity": 100,
      "big_play_size": "md",
      "big_play_shape": "circle",
      "big_play_show_on_hover": false,
    
      "autoplay": false,
      "start_muted": false,
      "loop": false,
      "captions_on_by_default": false,
      "seek_buttons": true,
      "fullscreen_control": true,
    
      "ip_watermark": false,
    
      "playback_speed_control": true,
      "allow_fast_forward": true,
      "click_for_sound": false,
      "click_to_resume": false,
      "auto_resume_playback": false,
      "default_quality": "auto",
      "default_volume": 100,
    
      "show_player_controls": true,
      "persistent_control_bar": false,
      "volume_control": true,
      "quality_selector": true,
      "sticky_player": false,
      "pause_on_scroll": false
    }

    Skin options

    skin accepts one of: modern (default), pill, sharp, halo, minimal, bold, heritage, floating.

    Big play button options

    • big_play_color (hex, null = inherit controls_color)
    • big_play_icon_color (hex, null = inherit player_color)
    • big_play_opacity (int 0–100)
    • big_play_size (sm / md / lg)
    • big_play_shape (circle / rounded / square)
    • big_play_show_on_hover (boolean — keeps the disc visible during playback while the user hovers; not in DynTube)

    Inheritance behavior

    Setting any color/opacity/skin field to null clears that override. The next-highest-priority layer takes effect. Example: a project sets controls_color: '#FF6B35', a video sets controls_color: null — that video plays with the project's orange. If the project later clears its override too, the video falls back to the account default cyan.

    The dashboard shows an "Inherited from {project|channel|account}" badge next to fields that are inheriting, with a "Reset to inherit" link on overridden fields.

    Update example

    bash
    curl -X PATCH https://api.streamvault.one/v1/projects/PROJECT_ID/player-settings \
      -H "Authorization: Bearer YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "skin": "pill",
        "controls_color": "#FF6B35",
        "big_play_size": "lg",
        "big_play_show_on_hover": true,
        "ip_watermark": true
      }'

    Read what the player will use

    A project's GET returns its own row plus effective (what the player gets for videos in this project: the account, then each parent project, then this one) and inherited (the same without this project's own settings). A video's GET returns its own overrides in player_settings and the full merge in effective; add ?channel_id=CHANNEL_ID to include a channel's look. Fields that no layer sets use the player's built-in defaults.

    bash
    curl https://api.streamvault.one/v1/videos/VIDEO_ID/player-settings \
      -H "Authorization: Bearer YOUR_API_KEY"
    json
    {
      "id": "VIDEO_ID",
      "project_id": "SUB_PROJECT_ID",
      "player_settings": { "autoplay": true },
      "effective": {
        "skin": "pill",
        "player_color": "#0E1014",
        "controls_color": "#FF6B35",
        "controls_opacity": 90,
        "autoplay": true
      }
    }

    Saved skins (legacy)

    Older videos can point at a saved skin, through skinId on PATCH /videos/{id}. That is a legacy mechanism, separate from the layers above: it only touches a few cosmetic details (loading color, background, corner radius, a corner logo), saved skins cannot be created or listed through the API, and a project's default skin is not applied to its videos. To style the player, use the endpoints above.

    CTAs

    Overlay call-to-action buttons and email-capture prompts on the player — per video, or per project with inheritance. In the CTA and subtitle routes, {id} is the video's id as GET /videos lists it and its embed link carries it; the video's asset id (cf_video_id) is accepted too.

    Per-video CTAs

    MethodEndpointDescription
    GET/videos/{id}/ctasList a video's CTAs
    POST/videos/{id}/ctasCreate a CTA on a video
    PATCH/videos/{id}/ctas/{ctaId}Update a CTA
    DELETE/videos/{id}/ctas/{ctaId}Delete a CTA
    POST/videos/{id}/cta/copyCopy this video's CTAs to other videos
    POST/videos/batch/ctaBulk-assign a CTA to many videos

    Per-project CTAs

    MethodEndpointDescription
    GET/projects/{id}/ctasList a project's CTAs
    POST/projects/{id}/ctasCreate a project-level CTA
    PATCH/projects/{id}/ctas/{ctaId}Update a project CTA
    DELETE/projects/{id}/ctas/{ctaId}Delete a project CTA

    Inheritance & override

    Videos inherit their project's CTAs by default. Toggle per level withPATCH /videos/{id}or PATCH /projects/{id}with { "cta_inherit": false }. A video's own CTAs render on top of inherited ones.

    CTA shape

    json
    {
      "title": "Subscribe",
      "url": "https://example.com", // https://, http://, mailto: or tel: only
      "type": "link",            // "link" or "email"
      "show_at": 0,               // seconds; when to appear
      "hide_at": null,            // seconds, later than show_at; null = until end
      "position": "bottom-right", // top/bottom x left/center/right, or "center"
      "background": "#2764EB",    // null = brand palette fallback
      "text_color": "#ffffff"
    }

    A CTA's link must be a full link starting with https://,http://,mailto: ortel:; anything else (a javascript: link, a bare domain, a relative path) is refused with 400, and the player never draws one. The API creates link and email CTAs; image, image-on-pause and custom-HTML CTAs are made in the dashboard.

    Full parameter reference (palette keys, email-capture fields) is in theAPI Referenceunder CTAs.

    Webhooks

    StreamVault allows you to set up webhooks to receive notifications for specific events such asvideoPublished.

    Setting Up Webhooks

    1. Log in to your StreamVault account.
    2. Navigate to Dashboard → Settings → Webhooks.
    3. Set the event type (e.g. videoPublished).
    4. Provide the webhook URL where you want to receive notifications.
    5. Webhook configuration will be automatically saved.

    Video Published Event

    json
    {
      "event": "videoPublished",
      "video": {
        "Id": "YOUR_VIDEO_ID",
        "Duration": "00:00:02.2200000",
        "ProjectId": "YOUR_PROJECT_ID",
        "AccountKey": "YOUR_ACCOUNT_KEY",
        "Region": "use",
        "Image": {
          "xsUrl": "",
          "smUrl": "",
          "mdUrl": "",
          "lgUrl": ""
        },
        "Captions": [],
        "Key": "YOUR_VIDEO_KEY",
        "ChannelKey": "YOUR_CHANNEL_KEY",
        "PrivateLink": "YOUR_PRIVATE_LINK",
        "IframeLink": "YOUR_IFRAME_LINK",
        "HLSLink": "YOUR_HLS_LINK",
        "PlanType": 1,
        "Mp4Url": "",
        "Mp4Urls": [],
        "Formats": {
          "Hls": true,
          "Mp4": false,
          "Options": { "SecureMp4": true }
        },
        "HLSUrl": "YOUR_HLS_URL",
        "HLSUrlWeb": "YOUR_HLS_URL_WEB",
        "Title": "YOUR_VIDEO_TITLE",
        "Description": "YOUR_VIDEO_DESCRIPTION",
        "Options": {},
        "Tags": [],
        "Version": 1,
        "Status": 3,
        "Created": "2024-01-15T10:30:00Z"
      }
    }
    View full Webhooks reference

    SDKs & Libraries

    Official client libraries are on the roadmap but are not published yet. Until they ship, the REST API works from any language with an HTTP client; the examples below use nothing beyond what your runtime already provides.

    JavaScript

    Node.js & browser SDK

    Coming soon

    React

    React component library

    Coming soon

    Python

    Python SDK

    Coming soon

    JavaScript Quick Example (REST)

    javascript
    const BASE = 'https://api.streamvault.one/v1';
    const headers = {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json',
    };
    
    // List videos
    const { videos } = await fetch(`${BASE}/videos`, { headers }).then(r => r.json());
    
    // Upload via URL
    const video = await fetch(`${BASE}/videos`, {
      method: 'POST',
      headers,
      body: JSON.stringify({ url: 'https://example.com/video.mp4', title: 'My Video' }),
    }).then(r => r.json());
    
    console.log(video.iframeLink);

    React Embed Component

    jsx
    function StreamVaultEmbed({ videoId }) {
      return (
        <div style={{ position: 'relative', paddingBottom: '56.25%', height: 0, overflow: 'hidden' }}>
          <iframe
            src={`https://streamvault.one/embed/${videoId}`}
            style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', border: 'none' }}
            allow="encrypted-media; fullscreen; picture-in-picture"
            allowFullScreen
          />
        </div>
      );
    }

    Embed via iframe

    html
    <!-- Embed StreamVault Player -->
    <iframe 
      src="https://streamvault.one/embed/VIDEO_ID"
      width="640" 
      height="360" 
      frameborder="0" 
      allow="encrypted-media"
      allowfullscreen>
    </iframe>
    View full SDKs & Embeds reference

    Need Help?

    Can't find what you're looking for? Reach out to our support team or check the user documentation.