🚀 Jetzt neu: FlowAI! Ein revolutionärer Chat mit personalisierten AI-Agenten. 🌟

Medienverarbeitung API - Video, Bild und Audio als Auftrag

Übergib eine Quelldatei und die gewünschten Ausgaben. Die Verarbeitung läuft asynchron auf unseren Servern, die Ergebnisse landen per PUT in deinem Speicher oder bei uns, und eine signierte Rückmeldung meldet das Ende.

🚀 Schnellstart

Basis-URL: https://creativeskyline.de/api/media/v1/jobs https://creativeskyline.de/api/media/v1/uploads https://creativeskyline.de/api/media/v1/assets https://creativeskyline.de/api/media/v1/presets https://creativeskyline.de/api/media/v1/webhook-endpoints
FlowData-Schlüssel
Diese API gehört zu FlowData und nutzt eigene Schlüssel (fd_...). Du legst sie in der App unter FlowData → API-Schlüssel an. FlowSuite-Schlüssel (pk_..., at_...) funktionieren hier nur noch bis zum 01.01.2027. Die Adressen der Endpunkte bleiben gleich.
Asynchron
POST /jobs antwortet sofort mit 202 und dem Auftrag im Status queued. Das Ergebnis kommt per Rückmeldung (job.finished / job.failed) oder per Abfrage von GET /jobs/{id}.

Überblick

Basis-URL: https://creativeskyline.de/api/media/v1. Jede Anfrage trägt Authorization: Bearer fd_…. Ein Auftrag besteht aus einer Pipeline (oder einem eigenen Preset), einer Eingabe und benannten Ausgaben.

  1. Quelle angeben: eine öffentlich erreichbare https-URL (zum Beispiel eine signierte GET-URL deines Speichers) oder eine upload_id aus POST /uploads.
  2. Ziele angeben: je Ausgabe eine put_url (presigned PUT in deinen Speicher) oder managed: true (wir speichern 7 Tage und liefern einen Download-Link).
  3. Rückmeldung wählen: einen registrierten Webhook-Endpunkt (callback.endpoint_id) oder eine freie callback.url.

Ablauf eines Auftrags

queued → downloading → processing → uploading → finished. Jeder laufende Zustand kann in failed enden, ein wartender Auftrag in cancelled. Ein Auftrag wird genau einmal versucht; ob sich ein neuer Versuch lohnt, sagt error.retryable.

Aus einer Quelle wird nur einmal dekodiert, alle Ausgaben entstehen daraus (zum Beispiel watermarked und clean in einem Durchgang). Videos werden als H.264 High (mindestens Level 4.1, bei größeren Bildern und höheren Bildraten das passende höhere Level, yuv420p, faststart) mit AAC 128 kbit/s kodiert, HDR-Quellen (HLG/PQ) werden auf SDR BT.709 umgerechnet, Metadaten wie GPS werden entfernt.

🧩 Pipelines & Presets

Kein Standard-Logo
Ausgaben mit Wasserzeichen brauchen immer options.watermark.asset (oder logo) mit dem Schlüssel eines eigenen Wasserzeichens aus PUT /assets/{key} oder aus einem Team-Preset.

System-Pipelines

Die Namen entsprechen den festen Profilen. video und image sind Bausteine mit frei benennbaren Ausgaben, deren Typ aus type kommt.

PipelineAusgaben und StandardwerteRegeln
ugc_videowatermarked (video): 540×960 crop, crf 23, preset medium, max_fps 30, audio keep, Wasserzeichen
clean (video): 540×960 crop, crf 23, preset medium, max_fps 30, audio keep
thumbnail (thumbnail): 540×960 crop, format jpg, quality 85
mindestens eine von: watermarked, clean
Standard-Warteschlange (bis 30 min)
ugc_imagewatermarked (image): 640×640 max, format jpg, quality 85, Wasserzeichen
clean (image): 640×640 max, format jpg, quality 85
mindestens eine von: watermarked, clean
Standard-Warteschlange (bis 30 min)
video_thumbnailthumbnail (thumbnail): format jpg, quality 85Pflicht: thumbnail
Standard-Warteschlange (bis 30 min)
chat_imagepreview (image): 1200×1200 max, format webp, quality 80Pflicht: preview
Standard-Warteschlange (bis 30 min)
chat_videovideo (video): 1280×720 scale, crf 26, preset medium, max_fps 30, audio keep
poster (poster): 640×auto max, format webp, quality 80
Pflicht: video
Standard-Warteschlange (bis 30 min)
course_videovideo (video): 1280×720 scale, crf 28, preset medium, max_fps 30, audio keepPflicht: video
lange Warteschlange (bis 3 h)
audio_extractaudio (audio): format mp3Pflicht: audio
lange Warteschlange (bis 3 h)
videobeliebiger Name mit type: video: 1280×720 scale, crf 23, preset medium, max_fps 30, audio keep
beliebiger Name mit type: thumbnail: format jpg, quality 85
beliebiger Name mit type: poster: 640×auto max, format webp, quality 80
Standard-Warteschlange (bis 30 min)
imagebeliebiger Name mit type: image: 1200×1200 max, format jpg, quality 85Standard-Warteschlange (bis 30 min)

Überschreiben

Reihenfolge, spätere Ebene gewinnt: Standardwert der Pipeline → Team-Preset options → Team-Preset outputs.<name> → Auftrag options → Auftrag outputs.<name>.

  • width, height (16 bis 7680 px), max_side, fit (crop füllt und schneidet zu, scale passt ein, max verkleinert nur)
  • Video: crf (18 bis 32), preset (x264, ultrafast bis veryslow), max_fps (nur wenn die Quelle schneller ist), audio (keep / strip)
  • Bild, Vorschaubild, Poster: format (jpg, webp, png), quality (1 bis 100)
  • watermark (true / false) je Video- oder Bildausgabe
  • options.watermark: asset, width_percent (1 bis 100), opacity (0 bis 1), position (center, top_center, bottom_center, top_left, top_right, bottom_left, bottom_right, center_left, center_right), margin_x, margin_y (px)

Team-Presets

Ein Team-Preset bündelt eine Basis-Pipeline mit eigenen options und outputs-Vorlagen und wird mit "preset": "<key>" statt pipeline verwendet. Die Vorlagen werden beim Speichern mit denselben Regeln wie ein Auftrag geprüft.

Presets auflisten

GET /api/media/v1/presets

Alle System-Pipelines (system: true) und die Presets deines Teams.

Preset anlegen oder ersetzen

POST /api/media/v1/presets

Legt ein Team-Preset an (201) oder ersetzt eines mit gleichem Schlüssel (200). System-Namen sind reserviert.

Request Body:

key string * 1 bis 64 Zeichen aus a-z, 0-9, _ und -
name string Bezeichnung, höchstens 191 Zeichen
pipeline string * Basis-Pipeline, zum Beispiel ugc_video
options object Optionen für alle Ausgaben inklusive watermark
outputs object Überschreibungen je Ausgabename

Preset löschen

DELETE /api/media/v1/presets/{key}

Löscht ein Team-Preset. Bereits angelegte Aufträge sind nicht betroffen, sie tragen eine Kopie ihrer Einstellungen.

🎬 Aufträge

Idempotenz
Sende einen Idempotency-Key-Header (1 bis 191 Zeichen aus A-Z a-z 0-9 . _ : -). Innerhalb von 24 Stunden liefert dieselbe Anfrage mit demselben Body den ursprünglichen Auftrag mit 200 zurück, ein anderer Body ergibt 409 idempotency_conflict.

Auftrag anlegen

POST /api/media/v1/jobs

Legt einen Auftrag an und antwortet mit 202 und queue_position. Genau eines von pipeline und preset, genau eines von input.url und input.upload_id, je Ausgabe genau eines von put_url und managed: true. Alle URLs müssen https sein und auf öffentliche Adressen zeigen.

Request Body:

pipeline string System-Pipeline (siehe Tabelle), alternativ preset
preset string Schlüssel eines Team-Presets, alternativ pipeline
reference string Deine eigene Kennung (höchstens 191 Zeichen), abfragbar über GET /jobs?reference=
priority string high, normal (Standard) oder low; course_video ist standardmäßig low
input.url string Öffentliche https-URL der Quelle, bis zu 3 Weiterleitungen
input.upload_id string ID aus POST /uploads, einmal verwendbar
input.filename string Originaler Dateiname (nur zur Anzeige)
input.size integer Größe in Bytes; wird vorab gegen das Limit geprüft und verbessert die Kostenreservierung
outputs.<name>.put_url string Presigned PUT-URL für das Ergebnis
outputs.<name>.managed boolean true: wir speichern das Ergebnis 7 Tage
outputs.<name>.content_type string Content-Type beim PUT, Standard passend zum Format
outputs.<name>.* mixed Überschreibungen wie width, height, fit, crf, format, quality, watermark
options object Optionen für alle Ausgaben, inklusive watermark.asset
callback.endpoint_id string ID eines registrierten Webhook-Endpunkts
callback.url string Freie https-URL; signiert mit dem Geheimnis des Standard-Endpunkts, sonst unsigniert
callback.events array job.finished und/oder job.failed (Standard: beide)
metadata object Beliebiges JSON bis 4 KB, kommt unverändert in der Rückmeldung zurück

Auftrag abrufen

GET /api/media/v1/jobs/{job_id}

Liefert den Auftrag in derselben Form wie die Rückmeldung. Bei managed-Ausgaben enthält download_url einen signierten Link, der bis expires_at gilt.

Aufträge nach Referenz suchen

GET /api/media/v1/jobs

Die bis zu 20 neuesten Aufträge mit dieser reference.

Query-Parameter:

reference string * Deine Referenz aus POST /jobs

Auftrag abbrechen

DELETE /api/media/v1/jobs/{job_id}

Ein wartender Auftrag wird sofort cancelled (ohne Rückmeldung, Reservierung wird freigegeben). Ein laufender Auftrag erhält eine Abbruchanforderung (cancel_requested: true), der Worker stoppt innerhalb weniger Sekunden und meldet job.failed mit error.code = cancelled. Ein beendeter Auftrag ergibt 409 conflict.

Direkt-Upload anlegen

POST /api/media/v1/uploads

Für Quellen ohne öffentliche URL: liefert eine presigned PUT-URL in unseren Speicher. Die Datei dort hochladen und danach als input.upload_id verwenden. Gültig 24 Stunden, einmal verwendbar. Je Team sind höchstens acht Direkt-Uploads und zusammen 10 GB gemeldete Größe gleichzeitig offen. Ein Upload zählt weiter, nachdem er einem Auftrag zugeordnet wurde, bis die Zeile entfernt ist.

Request Body:

filename string Dateiname, höchstens 255 Zeichen
size integer * Größe in Bytes, Pflicht. Die presigned PUT-URL signiert genau diese Content-Length; ein größerer Körper wird vom Speicher abgelehnt.
content_type string Content-Type, der beim PUT gesendet werden muss

Nutzung abfragen

GET /api/media/v1/usage

Aufträge, Fehlschläge, Rechenzeit und Eingabevolumen im Zeitraum (Standard: aktueller Monat). Kosten und Guthaben stehen in der App unter FlowData → Guthaben.

Query-Parameter:

from string Startdatum YYYY-MM-DD
to string Enddatum YYYY-MM-DD

Status

GET /api/media/v1/health

Ohne Anmeldung. ok, degraded (Wartung oder Bildverarbeitung eingeschränkt) oder down, dazu die Länge der Warteschlange.

🖼️ Wasserzeichen

Wasserzeichen sind PNG-Dateien mit Transparenz (Alphakanal oder tRNS), höchstens 2 MB und 8000 × 8000 px. Jede Version wird unter ihrem SHA-256 abgelegt; ein Auftrag merkt sich den Hash (options.watermark.asset_sha256), ein späteres Ersetzen ändert also keinen wartenden Auftrag. Verwalten lassen sie sich auch in der App unter FlowData → Wasserzeichen.

Wasserzeichen hochladen

PUT /api/media/v1/assets/{key}

Body als rohes PNG (Content-Type: image/png) oder multipart mit dem Feld file. Antwort 201 für einen neuen Schlüssel, 200 für eine neue Version.

Request Body:

file file * PNG mit Alphakanal, höchstens 2 MB

Wasserzeichen hochladen (POST)

POST /api/media/v1/assets/{key}

Gleich wie PUT, für Clients, die multipart nur mit POST senden können.

Request Body:

file file * PNG mit Alphakanal, höchstens 2 MB

Wasserzeichen auflisten

GET /api/media/v1/assets

Alle Wasserzeichen deines Teams mit aktueller Version.

🔔 Rückmeldungen (Webhooks)

Signatur immer prüfen
Prüfe webhook-signature gegen den rohen Body und weise Zeitstempel ab, die mehr als fünf Minuten abweichen. Das Geheimnis (whsec_…) wird nur beim Anlegen und beim Erneuern angezeigt.

Format

Ereignisse: job.finished (alle Ausgaben hochgeladen) und job.failed (inklusive Abbruch). Body: {"event": "job.finished", "job": { …wie GET /jobs/{id}… }, "metadata": { …deine metadata… }}. Eine Testzustellung aus der App trägt "event": "webhook.test".

Signatur nach Standard Webhooks

  • webhook-id: ID der Zustellung, bei Wiederholungen gleich (zur Duplikaterkennung)
  • webhook-timestamp: Unix-Sekunden des Versands
  • webhook-signature: v1,<base64>, HMAC-SHA256 über {webhook-id}.{webhook-timestamp}.{body} mit dem base64-dekodierten Teil des Geheimnisses nach whsec_. Während einer Rotation (24 Stunden) stehen zwei Signaturen, durch Leerzeichen getrennt, im Header.
  • Zusätzlich: X-Media-Event mit dem Ereignisnamen und User-Agent: CreativeSkyline-Media/1.0.

Eine freie callback.url wird mit dem Geheimnis des Standard-Endpunkts deines Teams signiert; gibt es keinen, geht sie unsigniert raus. Jede Standard-Webhooks-Bibliothek kann die Signatur prüfen, alternativ so:

// Laravel-Controller, der eine FlowData-Rückmeldung prüft (Standard Webhooks).
$secret = 'whsec_...';                       // aus FlowData → Webhooks
$key = base64_decode(substr($secret, 6));    // HMAC-Schlüssel = Teil nach "whsec_"

$id = $request->header('webhook-id');
$timestamp = (int) $request->header('webhook-timestamp');
$body = $request->getContent();              // roher Body, nicht neu kodieren

if (abs(time() - $timestamp) > 300) {
    abort(400, 'timestamp outside tolerance');
}

$expected = base64_encode(hash_hmac('sha256', "{$id}.{$timestamp}.{$body}", $key, true));
$valid = false;

// Während einer Rotation stehen zwei Signaturen im Header: "v1,<a> v1,<b>".
foreach (explode(' ', (string) $request->header('webhook-signature')) as $candidate) {
    [$version, $signature] = array_pad(explode(',', $candidate, 2), 2, '');
    if ($version === 'v1' && hash_equals($expected, $signature)) {
        $valid = true;
    }
}

abort_unless($valid, 401);

// webhook-id ist je Zustellung stabil: bereits verarbeitete IDs überspringen.
$event = json_decode($body, true);  // {"event":"job.finished","job":{...},"metadata":{...}}

Zustellung und Wiederholung

Erfolgreich ist jede Antwort mit 2xx innerhalb von 10 Sekunden. Weiterleitungen werden nicht verfolgt. Sonst folgen Wiederholungen nach 30 s, 2 min, 10 min, 30 min, 1 h, 3 h und 6 h. Rückmeldungen eines Auftrags gehen in Reihenfolge und nie gleichzeitig raus. In der App lässt sich jede Zustellung am Auftrag erneut senden.

Endpunkte auflisten

GET /api/media/v1/webhook-endpoints

Alle Endpunkte deines Teams, ohne Geheimnis.

Endpunkt anlegen

POST /api/media/v1/webhook-endpoints

Legt einen Endpunkt an und liefert das Geheimnis einmalig im Feld secret. is_default: true nimmt die Markierung einem anderen Endpunkt ab.

Request Body:

url string * Öffentliche https-URL
events array job.finished und/oder job.failed (Standard: beide)
description string Höchstens 191 Zeichen
is_default boolean Signiert auch freie callback.url

Geheimnis erneuern

POST /api/media/v1/webhook-endpoints/{endpoint_id}/rotate-secret

Erzeugt ein neues Geheimnis und liefert es einmalig. Das alte signiert 24 Stunden lang mit (previous_secret_expires_at).

Endpunkt löschen

DELETE /api/media/v1/webhook-endpoints/{endpoint_id}

Löscht den Endpunkt. Offene Zustellungen an ihn werden aufgegeben.

⚠️ Fehler & Limits

Fehlerformat

Jede Fehlerantwort hat die Form {"error": {"code", "message", "details", "type"}}. Ein fehlgeschlagener Auftrag trägt error = {"code", "message", "stage", "retryable"}.

HTTPcodeBedeutung
400bad_requestKein gültiges JSON-Objekt oder ungültiger Idempotency-Key
401unauthenticatedFehlender oder ungültiger Schlüssel
403forbiddenSchlüssel ohne FlowData-Zugriff
404not_foundUnbekannte oder fremde ID
409idempotency_conflictIdempotency-Key mit anderem Body
409conflictAbbruch eines bereits beendeten Auftrags
413input_too_largeinput.size oder Wasserzeichen über dem Limit
422validation_failedUngültige Felder; details.field und details.errors nennen sie
429rate_limitedZu viele Anfragen oder mehr als 200 wartende Aufträge; Retry-After beachten
429upload_quota_exceededZu viele offene Direkt-Uploads (Anzahl oder Summe der gemeldeten Bytes, auch bereits zugeordnete)
429insufficient_quotaPrepaid: das Guthaben reicht nicht. Postpaid: Kreditrahmen oder Monatslimit reicht nicht
429spend_limit_exceededAusgabenlimit des Schlüssels oder Monatslimit des Teams erreicht
500 / 503internal_error / unavailableFehler bei uns

Fehlercodes eines Auftrags

coderetryableBedeutung
input_download_failedjaQuelle vorübergehend nicht ladbar
input_not_foundneinQuelle abgelaufen, gelöscht oder gesperrt (403/404)
input_too_largeneinGröße, Dauer oder Bildmaße über dem Limit
input_unreadableneinDatei beschädigt oder Format nicht unterstützt
input_no_video_streamneinVideo-Pipeline ohne Videospur
input_no_audio_streamneinaudio_extract ohne Tonspur
processing_failedneinKodierung oder Qualitätsprüfung fehlgeschlagen
output_upload_failedjaPUT auf deine URL mit 5xx oder Netzfehler
output_url_expiredneinPUT-URL abgelaufen oder abgelehnt (403)
timeoutjaMaximale Verarbeitungszeit überschritten
cancelledneinPer DELETE abgebrochen
insufficient_quotaneinNach der Analyse reicht das Guthaben (Prepaid) oder der Kreditrahmen bzw. das Monatslimit (Postpaid) nicht für die tatsächliche Dauer
spend_limit_exceededneinAusgabenlimit von Schlüssel oder Team während der Verarbeitung erreicht, oder Abrechnung gesperrt
forbiddenneinDer Schlüssel wurde während der Verarbeitung deaktiviert oder darf nicht mehr abrechnen
internal_errorjaFehler bei uns

Limits

PipelineMax. GrößeMax. Dauer
Video (ugc_video, chat_video, video, video_thumbnail)2 GB10 min
Langes Video (course_video)5 GB3 h
Audio (audio_extract)5 GB3 h
Bild (ugc_image, chat_image, image)50 MB–

Bilder höchstens 16000 × 16000 px. Standardmäßig 60 Auftragsanlagen pro Minute je Schlüssel und höchstens 200 wartende Aufträge je Team. metadata höchstens 4 KB. Bis zu vier Aufträge laufen gleichzeitig, Kursvideos und Tonspuren in einer eigenen Warteschlange.

Abrechnung

Abgerechnet wird über das FlowData-Guthaben, und zwar nur für tatsächlich hochgeladene Ausgaben: Video 0,010 € je angefangene Minute, Audio 0,005 € je angefangene Minute, Bild, Vorschaubild und Poster 0,002 € je Ausgabe (Listenpreise, abweichende Vereinbarungen möglich). Bei der Anlage wird eine Schätzung reserviert und am Ende mit dem tatsächlichen Wert verrechnet. Auch bei einem teilweise fehlgeschlagenen Auftrag werden die hochgeladenen Ausgaben berechnet.

Aufbewahrung

Bei uns gespeicherte Ausgaben (managed) werden nach 7 Tagen gelöscht, Direkt-Uploads mit dem Ende ihres Auftrags (unbenutzt nach 24 Stunden), Auftragsdaten nach 90 Tagen. Abrechnungsdaten bleiben dauerhaft. Signierte URLs werden verschlüsselt gespeichert und nie protokolliert.