API-Integration
Diese Anleitung führt Sie vom ersten authentifizierten Request bis zum fertig eingebetteten Video — Schritt für Schritt und mit Beispielen, die Sie direkt kopieren können.
Überblick
Über unsere REST-API können Sie alles automatisieren, was Sie auch in der Oberfläche machen: Libraries anlegen, Videos hochladen, den Encoding-Status abfragen und fertige Videos einbetten. Das ist ideal für Shop-Systeme, Headless-CMS und eigene Workflows.
Jeder Request ist an Ihre Organisation gebunden. Sie sehen und verändern ausschließlich Daten innerhalb Ihrer eigenen Organisation — die Berechtigungen entsprechen Ihrer Rolle.
Basis-URL
https://api.videooptimizer.eu/api/v1Authentifizierung
Alle Requests (außer dem öffentlichen Embed-Endpoint) werden mit einem Bearer-Token authentifiziert. So kommen Sie an Ihren Token:
- Melden Sie sich in der App an. Für eine Integration legen Sie unter „Integrationen → API-Tokens“ einen Organisations-Token an (Owner/Admin; an keine Person gebunden). Für eigene Skripte genügt auch ein persönlicher Token unter „Konto → API-Tokens“.
- Erstellen Sie einen neuen Token und vergeben Sie einen sprechenden Namen.
- Kopieren Sie den Token sofort — er wird nur einmal vollständig angezeigt.
Senden Sie den Token bei jedem Request im Authorization-Header mit:
Authorization: Bearer vp_your_token_hereIhr erster Request
Prüfen Sie mit einem einfachen GET, ob Ihr Token funktioniert. Dieser Request gibt alle Libraries Ihrer Organisation zurück.
curl https://api.videooptimizer.eu/api/v1/libraries \
-H "Authorization: Bearer vp_your_token_here"const res = await fetch("https://api.videooptimizer.eu/api/v1/libraries", {
headers: { Authorization: "Bearer vp_your_token_here" },
});
const { data } = await res.json();
console.log(data); // -> array of your librariesKomplett-Ablauf: vom Upload bis zum Embed
Die folgenden vier Schritte zeigen den typischen Ablauf einer Integration: eine Library anlegen, ein Video hochladen, auf das Encoding warten und schließlich die Embed-Daten abrufen.
1 Library anlegen
Eine Library bündelt Ihre Videos und legt die Encoding-Einstellungen fest (Codecs und Auflösungen). In der Antwort finden Sie die Library-UUID (`id`), in die Sie anschließend hochladen.
curl -X POST https://api.videooptimizer.eu/api/v1/libraries \
-H "Authorization: Bearer vp_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"name": "My first library",
"codec": "h264,h265",
"resolutions": "360p,720p,1080p"
}'const res = await fetch("https://api.videooptimizer.eu/api/v1/libraries", {
method: "POST",
headers: {
Authorization: "Bearer vp_your_token_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "My first library",
codec: "h264,h265",
resolutions: "360p,720p,1080p",
}),
});
const { data: library } = await res.json();
console.log(library.id); // the library UUID you'll upload into2 Video hochladen
Der Upload ist ein Multipart-Request. Die Ziel-Library wird über den Header `x-library-id` gewählt, der Titel optional über `x-title`. Die Antwort enthält die Video-UUID und den Status `processing`.
curl -X POST https://api.videooptimizer.eu/api/v1/videos \
-H "Authorization: Bearer vp_your_token_here" \
-H "x-library-id: <LIBRARY_UUID>" \
-H "x-title: My product video" \
-F "file=@/path/to/video.mp4"const form = new FormData();
form.append("file", fileInput.files[0]);
const res = await fetch("https://api.videooptimizer.eu/api/v1/videos", {
method: "POST",
headers: {
Authorization: "Bearer vp_your_token_here",
"x-library-id": "<LIBRARY_UUID>",
"x-title": "My product video",
},
body: form, // do NOT set Content-Type manually — the browser adds the boundary
});
const { data } = await res.json();
console.log(data); // { uuid: "...", status: "processing" }3 Auf das Encoding warten
Das Encoding läuft asynchron. Fragen Sie den Video-Status in Intervallen ab, bis er `ready` ist. Erst dann ist das Video abspielbar.
curl https://api.videooptimizer.eu/api/v1/videos/<VIDEO_UUID> \
-H "Authorization: Bearer vp_your_token_here"async function waitUntilReady(uuid) {
while (true) {
const res = await fetch(`https://api.videooptimizer.eu/api/v1/videos/${uuid}`, {
headers: { Authorization: "Bearer vp_your_token_here" },
});
const { data } = await res.json();
if (data.status === "ready") return data;
await new Promise(r => setTimeout(r, 5000)); // poll every 5s
}
}4 Video einbetten
Sobald das Video bereit ist, liefert der öffentliche Embed-Endpoint alle nötigen Daten für den Player. Dieser Endpoint benötigt keinen Token und kann direkt aus dem Browser aufgerufen werden.
curl https://api.videooptimizer.eu/api/v1/embed/<VIDEO_UUID>// Public endpoint — no token required.
const res = await fetch("https://api.videooptimizer.eu/api/v1/embed/<VIDEO_UUID>");
const { data } = await res.json();
// data: theme, sources (HLS master first, then MP4s), poster, posterSrcset,
// tracks (subtitles), title, duration, resolution, views — render your own player.Wenn Sie den fertigen HTML-Embed-Code lieber per Copy-and-paste verwenden möchten, sehen Sie sich den Embed-Guide für Content-Teams.
Erweiterte Funktionen
Für den produktiven Einsatz bietet die API noch mehr: Sie können große Listen seitenweise abrufen, sehr große Dateien zuverlässig hochladen, Videos direkt von einer URL importieren lassen und sich per Webhook statt per Polling benachrichtigen lassen.
Pagination
Die Listen-Endpunkte `GET /api/v1/libraries`, `GET /api/v1/videos` und `GET /api/v1/libraries/:id/videos` liefern ihre Ergebnisse seitenweise (Cursor-basiert, keine Seitenzahlen). Steuern Sie die Seitengröße über `?limit=` (Standard 50, maximal 100) und blättern Sie mit `?cursor=` weiter. Die Antwort enthält zusätzlich ein `pagination`-Objekt mit `next_cursor` und `has_more` — reichen Sie `next_cursor` als nächsten `cursor`-Wert weiter, bis `has_more` `false` ist.
curl "https://api.videooptimizer.eu/api/v1/libraries?limit=20" \
-H "Authorization: Bearer vp_your_token_here"let cursor;
do {
const url = new URL("https://api.videooptimizer.eu/api/v1/libraries");
url.searchParams.set("limit", "20");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, {
headers: { Authorization: "Bearer vp_your_token_here" },
});
const { data, pagination } = await res.json();
// ...process this page of results...
cursor = pagination.next_cursor;
} while (cursor);Große Dateien & Resumable Upload
Für kleinere Dateien reicht der einfache Multipart-Upload aus Schritt 2 oben. Bei sehr großen Dateien laden Sie stattdessen in Teilen direkt zu unserem Storage hoch: `POST /api/v1/videos/upload/initiate` liefert eine vorsignierte URL pro Teil, die Sie einzeln per `PUT` hochladen; anschließend meldet `POST /api/v1/videos/upload/complete` die hochgeladenen Teile, um das Video zusammenzusetzen.
# 1. Initiate — get one presigned PUT URL per part
curl -X POST https://api.videooptimizer.eu/api/v1/videos/upload/initiate \
-H "Authorization: Bearer vp_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"libraryId": "<LIBRARY_UUID>",
"filename": "movie.mp4",
"fileSize": 524288000
}'
# 2. PUT each part straight to its presigned URL (repeat per part —
# read the ETag from each PUT response, you need it for step 3)
curl -X PUT "<PRESIGNED_PART_URL>" --data-binary @part-1.bin
# 3. Complete — hand back the part ETags to assemble the video
curl -X POST https://api.videooptimizer.eu/api/v1/videos/upload/complete \
-H "Authorization: Bearer vp_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"libraryId": "<LIBRARY_UUID>",
"uuid": "<VIDEO_UUID_FROM_INITIATE>",
"key": "<KEY_FROM_INITIATE>",
"uploadId": "<UPLOAD_ID_FROM_INITIATE>",
"title": "My product video",
"parts": [{ "partNumber": 1, "etag": "<ETAG_FROM_STEP_2>" }]
}'const { data: init } = await fetch("https://api.videooptimizer.eu/api/v1/videos/upload/initiate", {
method: "POST",
headers: {
Authorization: "Bearer vp_your_token_here",
"Content-Type": "application/json",
},
body: JSON.stringify({ libraryId, filename: file.name, fileSize: file.size }),
}).then(r => r.json());
const parts = [];
for (const part of init.parts) {
const chunk = file.slice((part.partNumber - 1) * init.partSize, part.partNumber * init.partSize);
const putRes = await fetch(part.url, { method: "PUT", body: chunk });
parts.push({ partNumber: part.partNumber, etag: putRes.headers.get("ETag") });
}
await fetch("https://api.videooptimizer.eu/api/v1/videos/upload/complete", {
method: "POST",
headers: {
Authorization: "Bearer vp_your_token_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
libraryId, uuid: init.uuid, key: init.key, uploadId: init.uploadId, parts,
}),
});Video per URL importieren
Statt die Datei selbst hochzuladen, können Sie `POST /api/v1/videos` auch mit JSON aufrufen und eine öffentlich erreichbare `https`-URL angeben (`source_url`) — die Plattform lädt die Datei dann selbstständig herunter.
curl -X POST https://api.videooptimizer.eu/api/v1/videos \
-H "Authorization: Bearer vp_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"library_id": "<LIBRARY_UUID>",
"source_url": "https://example.com/videos/movie.mp4",
"title": "My product video"
}'const res = await fetch("https://api.videooptimizer.eu/api/v1/videos", {
method: "POST",
headers: {
Authorization: "Bearer vp_your_token_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
library_id: "<LIBRARY_UUID>",
source_url: "https://example.com/videos/movie.mp4",
title: "My product video",
}),
});
const { data } = await res.json();
console.log(data); // { uuid: "...", status: "processing" }Eigene Daten speichern und Videos darüber finden
Geben Sie einem Video eigene Schlüssel/Wert-Daten als `metadata` mit – zum Beispiel Space, Umgebung und Asset-ID des CMS-Eintrags, zu dem es gehört – und finden Sie das Video später über jeden dieser Werte. Anders als Tags sind Metadaten genau dafür gedacht: Kennungen aus Ihrem System, exakt gespeichert und exakt verglichen.
- Setzen Sie sie beim Anlegen des Videos (`metadata` im JSON-Body von `POST /api/v1/videos` mit `source_url` oder bei `upload/complete`); bei einem Multipart-Upload anschließend per `PATCH`.
- `PATCH /api/v1/videos/:uuid` führt zusammen: Die gesendeten Schlüssel werden gesetzt, ein Schlüssel mit `null` wird entfernt, alle anderen bleiben. `"metadata": null` entfernt alles.
- Finden Sie Videos mit `GET /api/v1/videos?metadata[schlüssel]=wert` (auch bei `GET /api/v1/libraries/:id/videos`); bis zu 5 solcher Filter, mit UND verknüpft. Jedes Video-Objekt – in API-Antworten, Webhooks und MCP – enthält `metadata` (`{}`, wenn leer).
- Der Vergleich ist exakt und unterscheidet Groß- und Kleinschreibung (`ABC` findet nicht `abc`); Werte sind nicht eindeutig – mehrere Videos dürfen denselben Wert tragen, eine Suche liefert daher eine Liste.
- Höchstens 10 Schlüssel je Video; Schlüssel 1–40 Zeichen aus Kleinbuchstaben, Ziffern, `_`, `.` und `-`; Werte 1–64 Zeichen, keine Steuerzeichen, Leerzeichen am Anfang und Ende werden entfernt. Ein ungültiger Wert wird mit 400 abgelehnt (`data.code` nennt den Grund), nie stillschweigend verworfen.
# Store your own ids when creating the video …
curl -X POST https://api.videooptimizer.eu/api/v1/videos \
-H "Authorization: Bearer vp_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"library_id": "<LIBRARY_UUID>",
"source_url": "https://example.com/videos/movie.mp4",
"metadata": {
"contentful_space": "abc123",
"contentful_environment": "master",
"contentful_asset_id": "4ycPbMkEPCPWDBVVuJSVJy"
}
}'
# … find the video by one of them later (exact, case-sensitive match) …
curl -G https://api.videooptimizer.eu/api/v1/videos \
-H "Authorization: Bearer vp_your_token_here" \
--data-urlencode "metadata[contentful_asset_id]=4ycPbMkEPCPWDBVVuJSVJy"
# … or change it: keys you send are set, null removes a key, the rest stays
curl -X PATCH https://api.videooptimizer.eu/api/v1/videos/<VIDEO_UUID> \
-H "Authorization: Bearer vp_your_token_here" \
-H "Content-Type: application/json" \
-d '{ "metadata": { "contentful_environment": "staging", "contentful_space": null } }'const params = new URLSearchParams({
"metadata[contentful_asset_id]": "4ycPbMkEPCPWDBVVuJSVJy",
});
const res = await fetch(`https://api.videooptimizer.eu/api/v1/videos?${params}`, {
headers: { Authorization: "Bearer vp_your_token_here" },
});
const { data } = await res.json();
// Not unique: zero, one or several videos can carry the same value.
console.log(data.map(v => [v.uuid, v.metadata]));Webhooks statt Polling
Statt den Video-Status per Polling abzufragen, können Sie sich per Webhook benachrichtigen lassen, sobald ein Video fertig ist (`video.ready`) oder das Encoding fehlgeschlagen ist (`video.failed`). Webhooks werden in der App unter „Integrationen → Webhooks“ konfiguriert (nur für Owner/Admin) — nicht über die API.
Jede Zustellung ist signiert. Prüfen Sie den Header `X-VideoOptimizer-Signature` (`sha256=` + HMAC-SHA256 über Zeitstempel + Rohtext-Body, siehe Beispiel unten, mit Ihrem Endpoint-Secret) gegen den mitgesendeten `X-VideoOptimizer-Timestamp`, bevor Sie der Nutzlast vertrauen. Jede Zustellung trägt außerdem eine eindeutige `X-VideoOptimizer-Delivery-Id`.
import crypto from "crypto";
function isValidWebhook(rawBody, timestamp, signatureHeader, secret) {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
return signatureHeader === expected;
}
// Inside your webhook route handler:
const timestamp = req.headers["x-videooptimizer-timestamp"];
const signature = req.headers["x-videooptimizer-signature"];
if (!isValidWebhook(rawBody, timestamp, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}Limits & Hosts
- Rate-Limits pro Minute: 600 lesende Requests (GET) und 120 schreibende Requests je Token; 1.200 Requests je IP für die öffentlichen Embed- und Medien-Endpunkte (Embed-Config, Vorschaubild, Thumbnails, HLS-Master). Jede Antwort enthält `X-RateLimit-Limit` und `X-RateLimit-Remaining`; bei 429 warten Sie die Sekunden aus `Retry-After` ab.
- Die API ist nur über ihre eigene API-Subdomain erreichbar — andere Hosts antworten mit 404.
- Aufwendige Aufrufe haben zusätzlich ein Stundenlimit: Videos anlegen (`POST /videos`, `POST /videos/upload/initiate`) 120 pro Stunde, Untertitel-Uploads 120 pro Stunde, Bibliothek neu verarbeiten 5 pro Stunde.
- Jeder Token ist auf genau eine Organisation beschränkt; Berechtigungen werden bei jedem Request geprüft, Rechteänderungen greifen also sofort.
Best Practices für Seiten mit viel Traffic
Die API bei jedem Seitenaufruf aus Shop oder CMS abzufragen funktioniert, ist aber die langsamste und anfälligste Variante. Mit diesen Mustern bleiben Ihre Seiten schnell und unabhängig von der API:
- Cachen Sie API-Antworten auf Ihrer Seite. Embed-Config, Quellen und Bild-URLs eines Videos ändern sich nur, wenn Sie das Video ändern – eine Stunde Cache (und Aktualisierung per Webhook) ist unbedenklich. Unsere Plugins für Shopware, WordPress, TYPO3 und Sulu machen genau das.
- Binden Sie die stabilen URLs direkt ein, statt sie pro Seitenaufruf nachzuschlagen: `embed_url`, `hls_url`, `poster_url` und `thumbnail_url` ändern sich für ein Video nie, ihr Inhalt wird aktuell gehalten. Speichern Sie sie einmal.
- Nutzen Sie Webhooks (`video.ready`, `video.failed`) statt `GET /videos/{uuid}` in einer Schleife abzufragen.
- Behandeln Sie `429` sauber: Warten Sie die Sekunden aus `Retry-After` ab und versuchen Sie es dann erneut – nie in einer engen Schleife.
- Öffentliche Embed- und Medien-Antworten dürfen Browser und CDNs bis zu 60 Sekunden cachen; ein geändertes Player-Design oder Vorschaubild kann also bis zu einer Minute brauchen, bis es überall erscheint.
KI-Assistenten (MCP)
VideoOptimizer bietet einen MCP-Server (Model Context Protocol). Damit arbeiten KI-Assistenten wie Claude oder Cursor direkt mit Ihren Videos: per Sprache Videos von einer URL hinzufügen, Titel und Tags pflegen, den Status prüfen, Einbettungscode abrufen oder Untertitel übersetzen und hochladen.
Tragen Sie die Adresse unten in Ihrem MCP-Client als „Streamable HTTP“-Server ein und senden Sie einen API-Token als Bearer-Header. Der Assistent arbeitet mit genau den Rechten dieses Tokens.
https://api.videooptimizer.eu/api/v1/mcpclaude mcp add --transport http videooptimizer https://api.videooptimizer.eu/api/v1/mcp \
--header "Authorization: Bearer vp_your_token_here"{
"mcpServers": {
"videooptimizer": {
"type": "http",
"url": "https://api.videooptimizer.eu/api/v1/mcp",
"headers": { "Authorization": "Bearer vp_your_token_here" }
}
}
}Verfügbare Werkzeuge: Bibliotheken und Videos auflisten, Video-Details mit Einbettungscode, einzelne Untertitelspuren lesen, hochladen und löschen, Tags, Bandbreiten-Ersparnis, Video von URL hinzufügen, Titel/Tags/Metadaten ändern, Bibliothek anlegen. Videos und Bibliotheken lassen sich per MCP nicht löschen.
Jeder Werkzeug-Aufruf zählt zum Rate-Limit und erscheint bei schreibenden Aktionen im Audit-Log Ihrer Organisation.
Ausführliche Anleitung für alle Clients