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 und öffnen Sie „Account → 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 contains the player sources, poster and embed URL you can render.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` 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" }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 „Organisation → 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-Limit: 120 Requests pro Minute je Token bzw. IP.
- Die API ist nur über ihre eigene API-Subdomain erreichbar — andere Hosts antworten mit 404.
- Jeder Token ist auf genau eine Organisation beschränkt; Berechtigungen werden bei jedem Request live geprüft.