Medien-API
Dateien per API zu den Medien deines Teams hochladen, auflisten, taggen, in Ordnern organisieren und verwalten.
Medien-API
Mit der Medien-API laden Gameserver, Bots, Launcher, Handy-Skripte oder eigene Tools Dateien direkt zu den Medien deines Teams – und bekommen für öffentliche Dateien sofort einen CDN-Link zurück.
Grundlagen
| Was | Wert |
|---|---|
| Basis-URL | https://api.team-panel.com |
| Routen | alle unter /v1/cdn/media/{teamId} |
| Authentifizierung | Authorization: Bearer TP_… (Team-API-Schlüssel) |
| Rate Limit | 32 Anfragen / 10 Sekunden pro API-Schlüssel |
| Öffentliche URLs | https://cdn.team-panel.com/media/team/<teamId>/<datei> |
Die Team-ID und die fertige Upload-URL findest du unter Medien im Tab API & Integrationen.
Eigener Schlüssel für Uploads
Lege für Gameserver und Skripte einen eigenen API-Schlüssel mit dem
Scope media:upload an. Er kann dann nur die beiden Upload-Routen nutzen und lässt sich
unabhängig rerollen oder löschen. Der Schlüssel hat zusätzlich nur die Rechte des verknüpften
Mitglieds (bzw. des Owners) – für Uploads braucht es team.media.upload.
Schlüssel geheim halten
Lade Dateien möglichst serverseitig hoch. Ein Schlüssel im Client (z. B. im Spiel, in einer App oder im Browser) kann ausgelesen und missbraucht werden.
Endpunkte
Alle Pfade relativ zu /v1/cdn/media/{teamId}:
| Methode | Pfad | Beschreibung | Recht |
|---|---|---|---|
POST | /upload/file | Upload als multipart/form-data (Gameserver, curl) | team.media.upload |
POST | /upload | Upload als JSON mit Data-URI (serverseitige Skripte) | team.media.upload |
GET | (Basis-Pfad) | Dateien auflisten, filtern und suchen | team.media.view |
GET | /{mediaId} | URL einer Datei (öffentlich oder signiert, 1 h gültig) | team.media.view |
PATCH | /update/{mediaId} | Dateiname, Sichtbarkeit, Ordner, Tags ändern | team.media.edit |
DELETE | /delete/{mediaId} | Datei löschen | team.media.delete |
POST | /bulk | Mehrfachaktion für bis zu 100 Dateien | edit / delete |
GET | /folders | Ordner auflisten | team.media.view |
POST | /folders | Ordner anlegen | team.media.edit |
PATCH | /folders/{folderId} | Ordner umbenennen oder Regeln ändern | team.media.edit |
DELETE | /folders/{folderId} | Ordner löschen (Inhalt und Unterordner wandern zum Parent) | team.media.edit |
GET | /tags?search= | Verwendete Tags mit Anzahl | team.media.view |
GET | /usage?days=30 | Speicher, Limits und tägliche Nutzung (1–90 Tage) | team.media.view |
Request- und Response-Schemas findest du in der Swagger UI.
Upload (multipart)
POST /v1/cdn/media/{teamId}/upload/file
Die Datei steht im Formularfeld file – sonst wird die erste Datei im Formular verwendet. Optionen als Query-Parameter:
| Parameter | Beschreibung |
|---|---|
public | true / false. Standard: true – bzw. die Standard-Sichtbarkeit des Zielordners |
fileName | Gespeicherter Dateiname. Standard: Name der hochgeladenen Datei |
folder | Ordnername oder Pfad, z. B. screenshots oder screenshots/2026. Fehlende Ordner werden angelegt |
folderId | ID eines bestehenden Ordners. Nicht zusammen mit folder |
tags | Kommagetrennt, z. B. license:abc123,type:screenshot |
Antwort 201:
{
"id": "4d6c…",
"path": "media/team/<teamId>/<datei>.jpg",
"publicUrl": "https://cdn.team-panel.com/media/team/<teamId>/<datei>.jpg",
"usedTeamStorage": 1234567,
"folderId": "8f1e…",
"tags": ["license:abc123", "type:screenshot"]
}Bei privaten Dateien ist publicUrl null – den signierten Link holst du dann über GET /{mediaId}.
curl -X POST "https://api.team-panel.com/v1/cdn/media/<teamId>/upload/file?folder=screenshots&tags=type:screenshot" \
-H "Authorization: Bearer TP_dein_schluessel" \
-F "file=@screenshot.jpg"// Node.js 18+ (fetch, FormData und Blob sind eingebaut)
import { readFile } from 'node:fs/promises';
const TEAM_ID = process.env.TEAMPANEL_TEAM_ID;
const API_KEY = process.env.TEAMPANEL_API_KEY; // TP_…
const form = new FormData();
form.append(
'file',
new Blob([await readFile('screenshot.png')], { type: 'image/png' }),
'screenshot.png',
);
const url = new URL(`https://api.team-panel.com/v1/cdn/media/${TEAM_ID}/upload/file`);
url.searchParams.set('folder', 'screenshots');
url.searchParams.set('tags', 'type:screenshot,discord:123456789');
const res = await fetch(url, {
method: 'POST',
headers: { Authorization: `Bearer ${API_KEY}` },
body: form,
});
if (res.status !== 201)
throw new Error(`Upload fehlgeschlagen (${res.status}): ${await res.text()}`);
const { id, publicUrl } = await res.json();
console.log(id, publicUrl);Upload (JSON)
POST /v1/cdn/media/{teamId}/upload
Ideal für serverseitige Skripte, die die Datei bereits als Base64 bzw. Data-URI haben (z. B. FiveM mit screenshot-basic). Antwort wie beim Multipart-Upload.
{
"file": "data:image/jpeg;base64,/9j/4AAQ…",
"fileName": "screenshot.jpg",
"public": true,
"folder": "screenshots",
"tags": ["license:abc123", "type:screenshot"]
}public, folder, folderId und tags (hier als Array) sind optional – mit denselben Regeln wie oben.
Ohne public gilt die Standard-Sichtbarkeit des Zielordners. Ohne Ordnerregel sind JSON-Uploads privat (false), Multipart-Uploads dagegen öffentlich (true).
Komplettes Beispiel für FiveM: FiveM-Screenshots.
Dateien auflisten
GET /v1/cdn/media/{teamId}
| Query | Beschreibung |
|---|---|
folderId | Ordner-ID oder none (nur Dateien ohne Ordner) |
includeSubfolders | true – auch Dateien aus Unterordnern von folderId |
tags | Kommagetrennt; Datei muss alle Tags haben (UND) |
search | Suche im Dateinamen |
type | image, video, audio, document oder all |
Seitenweise Abfrage über die Header pageNumber (ab 1) und pageSize (Standard 50). Die Antwort enthält media, totalCount (Gesamtanzahl für die Filter) und usedTeamStorage.
# Alle Screenshots eines Spielers, Seite 2 mit je 48 Einträgen
curl "https://api.team-panel.com/v1/cdn/media/<teamId>?tags=license:abc123,type:screenshot" \
-H "Authorization: Bearer TP_dein_schluessel" \
-H "pageNumber: 2" \
-H "pageSize: 48"Datei abrufen, ändern, löschen
GET /{mediaId}→{ "url": "…" }– bei öffentlichen Dateien der CDN-Link, bei privaten ein signierter Link (1 Stunde gültig)PATCH /update/{mediaId}mit{ "fileName"?, "public"?, "folderId"?, "tags"? }–folderId: nullentfernt den Ordner,tagsersetzt alle TagsDELETE /delete/{mediaId}– löscht die Datei und entfernt sie aus dem CDN-Cache
Mehrfachaktionen
POST /v1/cdn/media/{teamId}/bulk – für bis zu 100 Dateien:
action | Zusätzliches Feld | Recht |
|---|---|---|
delete | – | team.media.delete |
move | folderId (UUID oder null) | team.media.edit |
setPublic | public (true / false) | team.media.edit |
addTags | tags (Array) | team.media.edit |
removeTags | tags (Array) | team.media.edit |
curl -X POST "https://api.team-panel.com/v1/cdn/media/<teamId>/bulk" \
-H "Authorization: Bearer TP_dein_schluessel" \
-H "Content-Type: application/json" \
-d '{ "action": "move", "ids": ["<mediaId>", "<mediaId>"], "folderId": "<folderId>" }'
# → { "affected": 2, "failed": [] }Dateien, die nicht verarbeitet werden konnten (z. B. fremde IDs oder mehr als 20 Tags), stehen in failed mit id und message.
Ordner
POST /folders legt einen Ordner an:
{
"name": "screenshots",
"parentId": null,
"retentionDays": 30,
"maxFiles": 500,
"defaultPublic": true
}| Feld | Beschreibung |
|---|---|
name | 1–100 Zeichen, muss eindeutig sein (sonst 409) |
parentId | Übergeordneter Ordner für Unterordner (max. 5 Ebenen) |
retentionDays | Dateien nach N Tagen automatisch löschen |
maxFiles | Nur die neuesten N Dateien behalten |
defaultPublic | Standard-Sichtbarkeit für Uploads in diesen Ordner |
Die Regeln gelten nur für Dateien direkt im Ordner und werden stündlich angewendet. PATCH /folders/{folderId} akzeptiert dieselben Felder. Beim DELETE wandern Dateien und Unterordner in den übergeordneten Ordner.
Tags & Nutzung
GET /tags?search=license– meistgenutzte Tags inkl. Anzahl, z. B. für eine AutovervollständigungGET /usage?days=30– Speicher, Plan-Limits (null= unbegrenzt), Uploads pro Tag (inkl. API) und – falls aktiviert – CDN-Anfragen und -Traffic. Größen in Bytes.
Tag-Regeln: 1–64 Zeichen a-z 0-9 _ . : @ -, Kleinschreibung, max. 20 pro Datei. Empfohlen ist key:value, z. B. license:…, steam:…, discord:…, type:screenshot.
Fehlercodes
| Status | Bedeutung |
|---|---|
400 | Ungültige Eingabe, ungültiger Tag, mehr als 20 Tags, ungültiger Ordner oder folder + folderId zusammen |
401 | Ungültiger API-Schlüssel oder Schlüssel eines anderen Teams |
402 | Plan-Limit erreicht: Datei zu groß (maxMediaFileSize) oder Team-Speicher voll (maxTeamStorage) |
403 | Fehlendes Recht des Mitglieds oder fehlender Scope am Schlüssel |
404 | Datei oder Ordner nicht gefunden |
409 | Ordnername bereits vergeben |
413 | Anfrage zu groß (Multipart 260 MB, JSON 140 MB – Dateien über ca. 100 MB nur per Multipart) |
415 | Gesperrter Dateityp (HTML, JavaScript, ausführbare Dateien, Shell-Skripte, SVG) |
429 | Rate Limit überschritten (32 Anfragen / 10 s pro Schlüssel) |
503 | Upload-Kapazität vorübergehend belegt (error.media.uploadBusy); Retry-After: 5 |
Bei 503 mit error.media.uploadBusy warte mindestens die im Header Retry-After angegebene Zeit (aktuell 5 Sekunden). Sende danach denselben Upload erneut. Die API lehnt diese Anfrage vor der Verarbeitung des Anfrageinhalts ab; es wurde keine Datei angelegt. Dieser Fehler ist unabhängig vom Rate Limit (429).
Plan-Limits
Medien nutzen den gemeinsamen Team-Speicher (mit Mitglieder-Dateien, ToDo-Anhängen und Backups).
Die max. Dateigröße steigt mit dem Plan (Free 10 MB bis Enterprise 250 MB). Aktuelle Werte liefert
GET /usage (storage.usedBytes, limitBytes, mediaBytes, maxFileSizeBytes).
Automatisierung
Jeder Upload löst das Event Medien hochgeladen aus – z. B. um neue Screenshots in Discord zu posten. Siehe Automatisierung.
Siehe auch
- Medien – Oberfläche, Ordner-Regeln, Rechte
- FiveM-Screenshots – Upload mit
screenshot-basic - API-Schlüssel – Scopes und Mitgliedskontext