# VideoOptimizer > VideoOptimizer is a professional, ad-free video hosting and delivery platform for > online shops, CMS-based websites and marketing teams, made and hosted in Europe > (GDPR-compliant). Upload a video once; it is encoded into an adaptive-bitrate > ladder and delivered over a global CDN through a white-label player that you > embed with one code snippet or integrate via a REST API. Built by ScaleCommerce. VideoOptimizer is a product of ScaleCommerce (https://scale.sc), a team with more than 15 years of experience building and operating high-performance online shops. Sign up at https://videooptimizer.eu/auth/register. - Website: https://videooptimizer.eu/ - Documentation: https://videooptimizer.eu/docs - API reference (interactive): https://api.videooptimizer.eu/developers - OpenAPI spec (machine-readable): https://api.videooptimizer.eu/openapi.json - Support: support@scale.sc ## Why VideoOptimizer The alternative to YouTube/Vimeo embeds and to static MP4 files on a web server — built for commerce and brand sites, not for entertainment: - **No ads, no third-party recommendations, no foreign branding.** On YouTube, competitor products are recommended after a product video; on Vimeo their logo appears. VideoOptimizer's player is white-label and keeps visitors on your page. - **GDPR-compliant, made in Europe.** Developed, hosted and operated in Europe, no US servers. - **Automatically optimised for every device.** A single MP4 is either too large for mobile or too low quality for desktop. VideoOptimizer encodes several quality levels (360p up to 4K/2160p) and streams adaptively (HLS), so playback starts fast and does not buffer. - **Your player, your branding.** Colours, controls, autoplay, loop, mute and thumbnail are configurable per organization, library or video. ## Features - **Media library**: upload (drag & drop, multiple files, multi-gigabyte files directly to storage), organise videos in libraries, tag them, track storage and traffic. - **Adaptive streaming**: HLS master playlist plus per-resolution MP4 renditions; the player picks the right quality for the viewer's screen and connection. - **Multi-codec encoding**, selectable per library: H.264 (maximum browser compatibility), H.265 and AV1 (much smaller files at the same quality), VP9. HDR sources are tone-mapped to SDR. - **Global CDN delivery** for video and thumbnail traffic. - **Embeddable player**: iframe embed or inline video.js snippet, responsive for every aspect ratio (16:9, 9:16, 1:1, 21:9), thumbnails ("Vorschaubild") selectable from auto-generated frames or uploaded; reusable player presets. - **Replace a video in place**: upload a new file and every existing embed keeps working. - **Analytics**: player loads, views, traffic and storage per organization, library and video. - **Teams**: multiple users per organization with owner/admin/member roles and fine-grained permissions; switch between organizations. - **Social publishing**: schedule a video as a post on YouTube (more platforms planned). - **Developer platform**: REST API (v1) with organization or personal tokens, signed webhooks, remote-URL ingest, OpenAPI spec and this llms.txt. ## Integrations - Official plugins: Shopware (https://github.com/ScaleCommerce/videooptimizer-shopware) and Sulu CMS (https://github.com/ScaleCommerce/videooptimizer-sulu). - Works with any shop or CMS via the embed code (WordPress, Webflow, Shopify, Oxid, Magento, …) or via the REST API for headless setups and custom workflows. ## Docs - [Documentation overview](https://videooptimizer.eu/docs): guides for developers and content teams. - [Embed guide](https://videooptimizer.eu/docs/embed): embedding videos into a CMS or shop without code. - [Showcase](https://videooptimizer.eu/docs/showcase): live demos of every embed pattern (lightbox with sound, gallery, background video, portrait formats, product video, JavaScript API). - [Developer guide](https://videooptimizer.eu/docs/developers): step-by-step API integration with cURL and JavaScript examples. - [Team & permissions](https://videooptimizer.eu/docs/permissions): roles and grantable permissions. - [API reference](https://api.videooptimizer.eu/developers) and [OpenAPI spec](https://api.videooptimizer.eu/openapi.json). ## API overview The REST API lets you upload, manage and embed videos programmatically from any backend, shop or CMS. All data is scoped to the organization that owns the API token. Base URL for all requests: https://api.videooptimizer.eu/api/v1 ## Authentication Every request (except the public Embed endpoint) needs an API token. It looks like `vp_…`, is shown only once, and is sent as a Bearer token: Authorization: Bearer vp_your_token_here There are two token kinds — both use the same `vp_…` format and Bearer header: - **Organization token** (recommended for plugins / server integrations): created by an org admin under **Organization → API tokens** (or by a superadmin for any organization from the admin area). Not tied to any person, grants full media access (all libraries + videos of the organization), and keeps working when staff change. A name is mandatory and shows up in the audit log. - **Personal token**: created by any user under **Account → API tokens**. Bound to that user + organization and carries their live permissions (permission changes take effect on the next request). Both can have an optional expiry and can be revoked at any time. ## Conventions - Successful responses wrap the payload: `{ "data": … }`. - Errors return `{ "statusCode": , "message": "" }` with the matching HTTP status. - Libraries are addressed by their UUID `id`; videos by their `uuid`. - Custom request headers use hyphens, never underscores (`x-library-id`). - Uploads are asynchronous. After upload, poll `GET /videos/{uuid}` until `status` is `"ready"` (or use the boolean `ready`). Status values: `processing`, `ready`, `failed`. A `failed` video carries a human-readable `error` (e.g. a remote-URL download error); it is `null` while `processing`/`ready`. - `poster_url` (and every `posterSrcset` entry) is the ACTIVE poster — the uploaded custom poster when one is active, otherwise the picked thumbnail frame. It is a STABLE, direct-CDN URL fixed per video that never changes — store it once, use it directly in ``/`poster`; when the poster is later changed the SAME URL immediately serves the new image (the CDN edge is purged on change). This is the image to show before playback. Bind `posterSrcset` to a responsive `` (480/640/1280/1920); `poster_url` is the single medium-size default. - `thumbnail_url` is different: it is always the representative thumbnail FRAME (never the custom poster — use `poster_url` for the active preview) and points at that frame's own direct-CDN key, so it changes if a different thumbnail is selected. The uploaded custom poster before activation is at `poster.custom_url` (preview only). All image traffic stays on the CDN. - Rate limit: 120 requests/minute per token (sliding window). On `429` respect the `Retry-After` and `X-RateLimit-*` response headers. - List endpoints (libraries, videos) use cursor pagination: `?limit=` (default 50, max 100) and `?cursor=` (opaque, from a previous response). They return `{ data, pagination: { next_cursor, has_more } }` — pass `next_cursor` back as `?cursor=` to fetch the next page; `has_more` is `false` (and `next_cursor` is `null`) on the last page. ## Endpoints ### Libraries - `GET https://api.videooptimizer.eu/api/v1/libraries` — list all libraries in your organization (paginated, see Conventions) - `POST https://api.videooptimizer.eu/api/v1/libraries` — create a library (JSON body: `name` required; optional `description`, `resolutions`, `codec`). CDN delivery is provisioned automatically. - `GET https://api.videooptimizer.eu/api/v1/libraries/{id}` — get one library - `PATCH https://api.videooptimizer.eu/api/v1/libraries/{id}` — update `name`/`description`/`codec`/`resolutions` - `DELETE https://api.videooptimizer.eu/api/v1/libraries/{id}` — delete a library and all its videos - `POST https://api.videooptimizer.eu/api/v1/libraries/{id}/reprocess` — re-queue encoding for every video in the library - `GET https://api.videooptimizer.eu/api/v1/libraries/{id}/videos` — list videos in a library (paginated, see Conventions) A library object carries `media_managed` (boolean). When `true`, upload, poster/thumbnail selection, and reprocess are available; when `false` those write endpoints return 400 (the library is delivery-only). Check it before offering those actions instead of reacting to a 400. A library also carries `available_codecs` and `available_resolutions` — the option keys your organization may enable right now. Setting `codec`/`resolutions` (on create or update) to a value outside this set returns 400 (options already active on the library are grandfathered). See Encodings. ### Encodings - `GET https://api.videooptimizer.eu/api/v1/encodings` — list the codecs and resolutions your organization may enable, each with `{ key, label, access, available }`. `access: "addon"` + `available: false` = a paid add-on not yet unlocked for your organization. Options disabled platform-wide are omitted. ### Videos - `GET https://api.videooptimizer.eu/api/v1/videos` — list all videos in your organization, newest first (paginated, see Conventions). Optional `?library_id=` restricts to one library; optional `?tags=a,b` returns only videos carrying ALL listed tags (also on `GET /libraries/{id}/videos`; max 20 names, an invalid name returns 400 instead of being ignored). - `POST https://api.videooptimizer.eu/api/v1/videos` — create a video. Two variants: (1) `multipart/form-data` with a `file` field; headers `x-library-id` (required, target library UUID) and `x-title` (optional), `x-tags` (optional, comma-separated, each name percent-encoded UTF-8, e.g. `Pr%C3%A4sentation,2026`). (2) `application/json` with `{ library_id, source_url, title?, tags? }` — remote-URL ingest, see below. Returns `{ data: { uuid, status: "processing" } }`. - `POST https://api.videooptimizer.eu/api/v1/videos/upload/initiate`, `POST https://api.videooptimizer.eu/api/v1/videos/upload/complete` — presigned multipart upload, **recommended for large files** (see **Large file uploads** below). Selfhosted libraries only. - `GET https://api.videooptimizer.eu/api/v1/videos/{uuid}` — get a video: `status`/`ready`, `error` (reason when failed, else null), `duration`, `resolution`, `views`, `poster_url`, `thumbnail_url`, `hls_url`, `embed_url`, `options`, `renditions`. - `PATCH https://api.videooptimizer.eu/api/v1/videos/{uuid}` — update `title`, player `option` (responsive, autoplay, preload, loop, muted) and/or `tags` (string array, replaces the whole set; `[]` clears it) - `DELETE https://api.videooptimizer.eu/api/v1/videos/{uuid}` — delete a video - `GET https://api.videooptimizer.eu/api/v1/videos/{uuid}/thumbnails` — list auto-generated thumbnails as `{ index, url }` - `POST https://api.videooptimizer.eu/api/v1/videos/{uuid}/thumbnail` — set the active thumbnail (JSON body: `thumbnailIndex` 0–9) - `POST https://api.videooptimizer.eu/api/v1/videos/{uuid}/poster/initiate|complete|select`, `DELETE …/poster` — manage a custom poster image (advanced: presigned upload flow) ### Tags - `GET https://api.videooptimizer.eu/api/v1/tags` — list your organization's tags as `{ name, video_count }`. Tags are organization-wide (shared by all libraries). Every video carries `tags` (names, alphabetical). Names: 1–50 characters, no commas, unique case-insensitively; max 20 per video. Unknown names are created automatically when assigned. ### Media (public, no token — use directly in HTML / a player) - `GET https://api.videooptimizer.eu/api/v1/videos/{uuid}/poster.jpg` — stable poster image (302 to current file) - `GET https://api.videooptimizer.eu/api/v1/videos/{uuid}/thumbnail.jpg` — stable active-thumbnail image - `GET https://api.videooptimizer.eu/api/v1/videos/{uuid}/thumbnails/{index}` — stable image for one auto-generated thumbnail - `GET https://api.videooptimizer.eu/api/v1/videos/{uuid}/hls/master.m3u8` — stable HLS master playlist (adaptive bitrate); point any HLS player at it directly (302 to current file). Requires an H.264 rendition (HLS is H.264-only) and returns 404 for a short while after `ready` until packaging finished — retry, or use the MP4 sources from the embed payload meanwhile. ### Embed (public, no token) - `GET https://api.videooptimizer.eu/api/v1/embed/{uuid}` — player config for building a custom player: the merged `theme` (all player fields — colors, control toggles, autoplay/muted/loop, etc.), HLS + per-resolution sources, and a responsive `posterSrcset` of STABLE fixed CDN URLs (bind to ``; they never change and always show the current thumbnail) plus a single stable `poster`. Populated once the video is ready. ## Large file uploads Recommended for large files, instead of `POST /videos` (avoids buffering the whole file through the request body). Requires a selfhosted library. 1. `POST https://api.videooptimizer.eu/api/v1/videos/upload/initiate` with JSON `{ libraryId, filename, contentType, fileSize }` → `{ data: { uuid, key, uploadId, partSize, partCount, parts } }`, where `parts` is an array of `{ partNumber, url }` — `url` (not `presignedUrl`) is the presigned MinIO PUT URL for that part. 2. `PUT` each chunk of the file to its `parts[i].url`; collect the `ETag` response header per part. 3. `POST https://api.videooptimizer.eu/api/v1/videos/upload/complete` with JSON `{ libraryId, uuid, key, uploadId, title, parts: [{ partNumber, etag }, …] }` → creates the video and queues encoding. Idempotent — safe to retry on failure. ## Remote-URL ingest If the video already lives at a public URL, skip the file upload: curl -X POST https://api.videooptimizer.eu/api/v1/videos \ -H "Authorization: Bearer vp_…" -H "Content-Type: application/json" \ -d '{"library_id":"","source_url":"https://example.com/video.mp4","title":"My video"}' # → { "data": { "uuid": "", "status": "processing" } } The server downloads `source_url` server-side, stores it and queues encoding — polling and the response shape are identical to a file upload. Requires a selfhosted library. `source_url` must be `https`, publicly reachable, and must not resolve to a private/internal/loopback address (validated before download, including redirect targets). If the download fails, the video ends up `failed` with the reason in `error` (e.g. `"source returned status 403"`). ## Typical workflow # 1. Create a library curl -X POST https://api.videooptimizer.eu/api/v1/libraries \ -H "Authorization: Bearer vp_…" -H "Content-Type: application/json" \ -d '{"name":"Product videos","resolutions":"360p,720p,1080p","codec":"h264,h265"}' # → { "data": { "id": "", ... } } # 2. Upload a video into it curl -X POST https://api.videooptimizer.eu/api/v1/videos \ -H "Authorization: Bearer vp_…" \ -H "x-library-id: " -H "x-title: My video" \ -F "file=@/path/to/video.mp4" # → { "data": { "uuid": "", "status": "processing" } } # 3. Poll until ready (status == "ready") curl https://api.videooptimizer.eu/api/v1/videos/ -H "Authorization: Bearer vp_…" # 4. Embed it — the response's embed_url is an iframe-ready hosted player, # poster_url is the stable preview URL. Or build a custom # player from the sources in GET /api/v1/embed/ (public). ## Embedding a video Use the hosted iframe player (recommended) — replace the host with the app URL, not the API host: Optional playback overrides as query params on the iframe src: `autoplay`, `controls`, `loop`, `muted` (each `1`/`0`), `t` (start position in seconds) and `fit=cover` (full-bleed instead of letterbox). Only the params you set override the video's configured player theme; `autoplay` implies `muted` (browsers only autoplay muted) unless you pass `muted=0` explicitly: then the player tries to start WITH sound (works after a visitor click, e.g. a lightbox the click opened) and, if the browser refuses, starts muted with a one-tap "unmute" button. Examples: background video `https://videooptimizer.eu/embed/?autoplay=1&controls=0&loop=1`, lightbox with sound `https://videooptimizer.eu/embed/?autoplay=1&muted=0`. The iframe also accepts postMessage commands from its parent page: `iframe.contentWindow.postMessage({ type: 'videooptimizer:command', command: 'play' }, '*')` (`play` | `pause` | `mute` | `unmute` | `seek` with `time` in seconds), and posts `{ type: 'videooptimizer:event', event, uuid, muted, paused, currentTime }` back (`ready`, `play`, `pause`, `ended`, `volumechange`). These query params apply to the hosted player PAGE (`https://videooptimizer.eu/embed/{uuid}`) only. The JSON config endpoint `GET /api/v1/embed/{uuid}` (below) takes no such params — it returns the merged `theme` (with the same autoplay/muted/loop/controls flags) for you to apply yourself. Or build a custom player from the sources returned by `GET /api/v1/embed/{uuid}`. The first source is the HLS master (adaptive bitrate); the others are per-resolution MP4/WebM for manual quality selection.