Auf dieser Seite
- Basis-URL und Keys
- Zwei Keys, zwei Aufgaben
- Scopes
- Adressen
- Eine Datei hochladen
- Feste Links mit key
- Base64 hochladen
- Was ein Upload antwortet
- Ein Archiv hochladen
- Einmal-Upload-Links
- Einen Clip in der NUI aufnehmen
- Auflisten, lesen und löschen
- Limits und Dateitypen
- Browser und NUI (CORS)
- Wann ein Link funktioniert
- Rate Limits
- Fehler
Dokumentation / Referenz
Upload-API-Referenz
Alle Adressen, auf die die Upload-API antwortet, die Felder, die sie liest, und was sie zurückschickt. Sie spricht den Dialekt von Fivemanage, ein Skript für Fivemanage läuft also, sobald du Basis-URL und Key tauschst.
Basis-URL und Keys
Alle Adressen auf dieser Seite sind relativ zu https://api.fivedock.dev, und Dateien kommen von https://cdn.fivedock.dev.
Zwei Keys, zwei Aufgaben
curl "https://api.fivedock.dev/api/v3/file?limit=1" \
-H "Authorization: Bearer fd_live_…"Behalte das API-Token auf einem Server, den du kontrollierst. Alles, was im Spiel oder Browser eines Spielers läuft, sollte über einen Einmal-Link hochladen, damit es das Token nie sieht.
Scopes
Jede Adresse braucht einen Scope, der in der Tabelle unten steht, und ein Token ohne ihn bekommt 403. files:read listet und liest Dateien, files:write lädt hoch und erzeugt Upload-Links, files:delete löscht, folders:write legt Ordner an, usage:read liest Speicher und Traffic, und logs:write schickt Logs.
Adressen
Pfade und Feldnamen sind die von Fivemanage, dazu kommt ein eigener Archiv-Upload von FiveDock. Auch die Medien-Adressen von Fivemerr werden beantwortet.
| Adresse | Scope | Was sie tut |
|---|---|---|
| Dateien | ||
| POST /api/v3/file | Scope · files:write | Lädt eine Datei als multipart/form-data hoch. |
| POST /api/v3/file/base64 | Scope · files:write | Lädt eine Datei hoch, die als base64 in einem JSON-Body steckt. |
| POST /api/v3/file/archive | Scope · files:write | Lädt eine .zip oder .rar hoch und speichert jede Datei darin. Nur bei FiveDock. |
| GET /api/v3/file | Scope · files:read | Listet deine Dateien, seitenweise. |
| GET /api/v3/file/{id} | Scope · files:read | Liest die Details einer Datei. |
| DELETE /api/v3/file/{id} | Scope · files:delete | Löscht eine Datei. |
| Upload-Links | ||
| GET /api/v3/file/presigned-url | Scope · files:write | Erzeugt einen Einmal-Upload-Link. Er gilt 15 Minuten, sofern du nichts anderes angibst. |
| POST /api/v3/file/presigned-url/{ticket} | Scope · Keiner, der Link selbst ist die Berechtigung | Lädt über einen oben erzeugten Link hoch, ohne Token. |
| GET /api/v2/presigned-url | Scope · files:write | Erzeugt einen Upload-Link auf die ältere Art. Er gilt 5 Minuten, sofern du nichts anderes angibst. |
| POST /api/v2/presigned-url/{ticket} | Scope · Keiner, der Link selbst ist die Berechtigung | Lädt über einen auf die ältere Art erzeugten Link hoch, ohne Token. |
| Fivemanage v2 | ||
| POST /api/v2/image | Scope · files:write | Lädt ein Bild hoch. Die Datei steckt im Feld image oder in file. |
| POST /api/v2/video | Scope · files:write | Lädt ein Video hoch. Die Datei steckt im Feld video oder in file. |
| POST /api/v2/audio | Scope · files:write | Lädt einen Sound hoch. Die Datei steckt im Feld audio oder in file. |
| Fivemerr | ||
| POST /v1/media/images | Scope · files:write | Lädt ein Bild hoch. Die Datei steckt in file oder data. |
| POST /v1/media/videos | Scope · files:write | Lädt ein Video hoch. Die Datei steckt in file oder data. |
| POST /v1/media/audios | Scope · files:write | Lädt einen Sound hoch. Die Datei steckt in file oder data. |
| Logs | ||
| POST /api/logs | Scope · logs:write | Schickt ein Log-Ereignis. |
| POST /api/logs/batch | Scope · logs:write | Schickt Log-Ereignisse gebündelt. |
| POST /api/v3/logs | Scope · logs:write | Schickt Logs an die v3-Adresse von Fivemanage. |
Die Log-Adressen nehmen die Formate an, die Fivemanage-Clients schicken, auch den Fivemanage-Logger von ox_lib (set ox:logger "fivemanage"), und brauchen ein Token mit logs:write. ox_lib hat die Adresse von Fivemanage in imports/logger/providers/fivemanage.lua fest eingetragen. Ändere dort die Zeile endpoint auf die Adresse von FiveDock, auch nach jedem Update von ox_lib.
Eine Datei hochladen
Schick POST /api/v3/file als multipart/form-data, mit der Datei im Teil file. Alle anderen Felder sind optional.
| Feld | Was es tut |
|---|---|
| file | Die Datei selbst. Eine pro Request. |
| filename | Der Name, unter dem sie gespeichert wird. Standardmäßig der Name aus dem Dateiteil. |
| path | Der Zielordner, etwa /mdt/mugshots. Wird bei Bedarf angelegt; standardmäßig der Stammordner. |
| key | Ein fester Name wie items/burger.png. Die Datei ist unter einem Link erreichbar, der sich nie ändert, und ein erneuter Upload mit demselben Key ersetzt sie. Ist er gesetzt, hat er Vorrang vor path und filename. |
| metadata | Ein JSON-Objekt, das mit der Datei gespeichert und beim Lesen zurückgegeben wird, etwa der Fall oder der Charakter, zu dem sie gehört. Alles, was kein JSON-Objekt ist, wird als leeres Objekt gespeichert. |
curl -X POST https://api.fivedock.dev/api/v3/file \
-H "Authorization: Bearer fd_live_…" \
-F "[email protected]" \
-F "path=/evidence/case-1042" \
-F 'metadata={"caseId":1042,"officer":"A. Nowak"}'Feste Links mit key
Eine Datei mit Key liegt unter /o/, dann dem Kurznamen deines Workspace, dann dem Key. Der Kurzname ändert sich nie und ist auch der Bucket-Name am S3-Endpunkt.
https://cdn.fivedock.dev/o/{workspace}/{key}
https://cdn.fivedock.dev/o/my-city/items/burger.png# the first upload makes the link, the next ones replace what it serves
curl -X POST https://api.fivedock.dev/api/v3/file \
-H "Authorization: Bearer fd_live_…" \
-F "[email protected]" \
-F "key=items/burger.png"Base64 hochladen
POST /api/v3/file/base64 nimmt einen JSON-Body statt eines Formulars. base64 ist die Datei, als Data-URI oder reines base64, und filename, path, key und metadata funktionieren wie im Formular. Das ist der kurze Weg aus Lua, denn screenshot-basic gibt dir schon eine Data-URI.
-- server.lua: a mugshot for the MDT, sent as base64
-- your own convar with an API token (fd_live_…), not the fda_live_ agent key
local TOKEN = GetConvar('mdt_upload_token', '')
local function uploadMugshot(source, charId, cb)
exports['screenshot-basic']:requestClientScreenshot(source, { encoding = 'webp', quality = 0.9 }, function(err, dataUri)
if err then return cb(err) end
PerformHttpRequest('https://api.fivedock.dev/api/v3/file/base64', function(status, body)
if status ~= 200 then
return cb(('upload failed (%d): %s'):format(status, body or ''))
end
cb(nil, json.decode(body).url)
end, 'POST', json.encode({
base64 = dataUri,
filename = ('%s.webp'):format(charId),
key = ('mugshots/%s.webp'):format(charId),
metadata = { charId = charId },
}), {
['Authorization'] = TOKEN,
['Content-Type'] = 'application/json',
})
end)
endDie dekodierte Datei darf bis zu 16 MiB groß sein. Alles Größere geht als multipart oder über einen Upload-Link.
Was ein Upload antwortet
Jede Upload-Adresse antwortet mit 200 und derselben Struktur. Die Felder auf oberster Ebene sind die, die Fivemanage schickt; in fivedock stehen unsere Extras.
{
"status": "ok",
"data": {
"id": "8Kq2nR4t",
"url": "https://cdn.fivedock.dev/o/my-city/mugshots/char1-abc.webp",
"originalUrl": "https://cdn.fivedock.dev/o/my-city/mugshots/char1-abc.webp"
},
"id": "8Kq2nR4t",
"uuid": "8Kq2nR4t",
"url": "https://cdn.fivedock.dev/o/my-city/mugshots/char1-abc.webp",
"filename": "char1-abc.webp",
"original_filename": "char1-abc.webp",
"mime_type": "image/webp",
"size": 48213,
"hash": "9f2b…",
"metadata": { "charId": "char1-abc" },
"created_at": "2026-09-29T12:00:00Z",
"updated_at": "2026-09-29T12:00:00Z",
"fivedock": {
"publicId": "8Kq2nR4t",
"key": "mugshots/char1-abc.webp",
"fileUrl": "https://cdn.fivedock.dev/f/8Kq2nR4t.webp",
"deduplicated": false,
"status": "ready",
"folder": "/mugshots",
"moderation": "approved"
}
}Ein Archiv hochladen
POST /api/v3/file/archive nimmt eine .zip oder .rar im Teil file und speichert jede Datei darin. path ist der Ordner, in den entpackt wird, standardmäßig der Stammordner.
curl -X POST https://api.fivedock.dev/api/v3/file/archive \
-H "Authorization: Bearer fd_live_…" \
-F "[email protected]" \
-F "path=items" \
-F "keys=true"Mit keys=true, dem Standard, wird jede Datei eine Datei mit Key, und ihr Key ist der Zielordner plus ihr Pfad im Archiv: path=items und ein Eintrag burger.png ergeben items/burger.png. Mit keys=false bekommen Dateien zufällige IDs und landen im Zielordner plus ihrem Ordner im Archiv.
Ordner im Archiv bleiben erhalten: Ein Archiv mit images/burger.png, entpackt nach items, ergibt items/images/burger.png. Soll ein Ordner wegfallen, zippe seinen Inhalt statt des Ordners selbst.
{
"status": "ok",
"data": {
"uploaded": [
{
"id": "Qw7tL2xa",
"key": "items/burger.png",
"url": "https://cdn.fivedock.dev/o/my-city/items/burger.png",
"fileUrl": "https://cdn.fivedock.dev/f/Qw7tL2xa.png",
"filename": "burger.png",
"path": "/items",
"size": 18233,
"mime_type": "image/png",
"deduplicated": false
}
],
"skipped": [{ "name": "readme.txt", "reason": "unsupported_type" }],
"incomplete": false
}
}Ordner, Links, __MACOSX/, versteckte Dateien, Thumbs.db und desktop.ini werden kommentarlos übersprungen. Jede andere Datei, die sich nicht speichern lässt, steht mit Grund unter skipped: unsupported_type, too_large, encrypted, unsafe_path, too_many_files, archive_too_large, storage_full, duplicate oder failed. Die Antwort ist 200, sobald mindestens eine Datei gespeichert wurde, auch wenn andere übersprungen wurden; ließ sich keine speichern, kommt 422 mit derselben skipped-Liste. Ist das Archiv mittendrin beschädigt, ist incomplete gleich true: Die Dateien vor der Beschädigung sind gespeichert, danach wurde nichts mehr gelesen.
Das Archiv darf bis zu 512 MiB groß sein, mit höchstens 5.000 Dateien und 4 GiB entpackt, und jede Datei muss ein Format haben, das wir annehmen. RAR 4 und RAR 5 werden gelesen; Einträge mit Passwort werden als encrypted übersprungen, mehrteilige Archive werden nicht unterstützt. Was weder zip noch rar ist, bekommt 415, und ein Archiv, das sich nicht lesen lässt, 422. Größere Mengen synchronisierst du über den S3-Endpunkt mit rclone oder der aws CLI.
Einmal-Upload-Links
Mit einem Upload-Link lädt die NUI eines Spielers, oder irgendein Browser, eine Datei hoch, ohne je dein Token zu sehen. Dein Server erzeugt ihn mit GET /api/v3/file/presigned-url und einem Token mit files:write und reicht ihn weiter. Jeder Parameter ist optional, und wer hochlädt, kann keinen davon ändern.
| Parameter | Was er tut |
|---|---|
| expiresAt | Wann der Link aufhört zu funktionieren, in Unix-Sekunden. Standard sind 15 Minuten ab jetzt (5 bei /api/v2/presigned-url), höchstens 1 Stunde. |
| singleUse | true lässt den Link genau einen erfolgreichen Upload annehmen; danach antwortet er mit 410 Gone. Ein abgelehnter Versuch, etwa wegen falschen Typs oder zu großer Datei, verbraucht ihn nicht. |
| maxSize | Die größte Datei, die der Link annimmt, in Bytes, bis zum Limit pro Datei. Eine größere Datei bekommt 413. |
| fileType | image, video oder audio. Jede andere Art Datei bekommt 415. |
| contentType | Genau ein Typ, etwa video/webm. Jeder andere Typ bekommt 415. |
| path | Der Ordner, in dem die Datei landet. |
| key | Speichert die Datei unter diesem Key, sie bekommt also einen festen Link und ersetzt, was dort lag. |
curl "https://api.fivedock.dev/api/v3/file/presigned-url?singleUse=true&fileType=video&contentType=video/webm&maxSize=52428800&path=/mdt/videos" \
-H "Authorization: Bearer fd_live_…"Die Antwort enthält den Link und wiederholt, was er erlaubt:
{
"status": "ok",
"data": {
"presignedUrl": "https://api.fivedock.dev/api/v3/file/presigned-url/eyJ…",
"expiresAt": "2026-09-29T12:15:00Z",
"singleUse": true,
"maxSize": 52428800,
"fileType": "video",
"contentType": "video/webm",
"path": "/mdt/videos"
}
}Zum Hochladen schickst du POST an presignedUrl als multipart/form-data, mit der Datei im Feld file und, wenn du willst, filename und metadata. Schick keinen Authorization-Header mit. Die Antwort ist dieselbe wie bei POST /api/v3/file.
# no Authorization header: the link is the credential
curl -X POST "https://api.fivedock.dev/api/v3/file/presigned-url/eyJ…" \
-F "[email protected]"Einen Clip in der NUI aufnehmen
Ein Bodycam-Clip für ein MDT: Der Server erzeugt einen Einmal-Link für eine webm-Datei, das Client-Skript reicht ihn an die NUI, und die NUI nimmt mit MediaRecorder auf und lädt den Blob direkt zu FiveDock hoch.
-- server.lua: the token stays on the server
local TOKEN = GetConvar('mdt_upload_token', '') -- an API token (fd_live_…) with files:write
local function mintUploadLink(cb)
local query = ('singleUse=true&fileType=video&contentType=video/webm&maxSize=%d&expiresAt=%d&path=/mdt/videos')
:format(50 * 1024 * 1024, os.time() + 600)
PerformHttpRequest('https://api.fivedock.dev/api/v3/file/presigned-url?' .. query, function(status, body)
if status ~= 200 then return cb(nil) end
cb(json.decode(body).data.presignedUrl)
end, 'GET', '', { ['Authorization'] = TOKEN })
end
RegisterNetEvent('mdt:requestUploadLink', function()
local src = source
-- check that this player may attach evidence before minting anything
mintUploadLink(function(url)
TriggerClientEvent('mdt:uploadLink', src, url)
end)
end)
RegisterNetEvent('mdt:clipUploaded', function(url)
-- save url with the case: it plays straight away
end)Erzeug pro Upload einen eigenen Link und halte seine Laufzeit kurz. Mit singleUse=true bekommt jeder, der den Link aus der NUI kopiert, 410, sobald dein Clip angekommen ist.
Auflisten, lesen und löschen
GET /api/v3/file listet deine Dateien, 50 pro Seite, sofern du nicht limit setzt, höchstens 100. page wählt die Seite, type lässt nur image, video oder audio übrig, und path einen Ordner. Häng die ID einer Datei an die Adresse, um sie zu lesen, oder schick DELETE an dieselbe Adresse, um sie zu löschen. Jede Datei kommt mit url und fileUrl: Bei einer Datei mit Schlüssel ist url ihr fester Link und key steht dabei; sonst sind beide der /f/-Link.
# the second page of videos in /evidence
curl "https://api.fivedock.dev/api/v3/file?type=video&path=/evidence&page=2&limit=20" \
-H "Authorization: Bearer fd_live_…"
# one file
curl https://api.fivedock.dev/api/v3/file/8Kq2nR4t \
-H "Authorization: Bearer fd_live_…"
# delete it: the token needs files:delete
curl -X DELETE https://api.fivedock.dev/api/v3/file/8Kq2nR4t \
-H "Authorization: Bearer fd_live_…"{
"status": "ok",
"data": [
{
"id": "8Kq2nR4t",
"uuid": "8Kq2nR4t",
"filename": "evidence-3.webm",
"url": "https://cdn.fivedock.dev/f/8Kq2nR4t.webm",
"originalUrl": "https://cdn.fivedock.dev/f/8Kq2nR4t.webm",
"type": "video",
"mime_type": "video/webm",
"size": 4821377,
"hash": "c41d…",
"path": "/evidence",
"metadata": { "caseId": 1042 },
"created_at": "2026-09-29T12:00:00Z",
"updated_at": "2026-09-29T12:00:00Z"
}
],
"pagination": { "page": 2, "limit": 20, "total": 21, "pages": 2, "hasMore": false }
}Eine gelöschte Datei wird sofort nicht mehr ausgeliefert. 7 Tage lang lässt sie sich im Panel wiederherstellen. Zum Löschen braucht das Token files:delete.
Limits und Dateitypen
Der Typ wird aus den Bytes der Datei gelesen, nicht aus ihrem Namen oder dem Content-Type, den du schickst. Angenommen werden:
video/webm wird angenommen, ein MediaRecorder-Clip geht also so hoch, wie er ist. Der S3-Endpunkt speichert auch andere Typen und liefert sie als Download aus.
Browser und NUI (CORS)
Die Upload-API antwortet Browsern von jedem Origin, mit Access-Control-Allow-Origin: * und ohne Cookies. Das schließt FiveM-NUI-Seiten ein, deren Origin https://cfx-nui- plus der Name der Resource ist. Das gilt für /api/v3/file…, /api/v2/… und /v1/media/…. Der Preflight erlaubt GET, POST und OPTIONS mit den Headern Authorization und Content-Type.
CORS entscheidet, welche Seiten anfragen dürfen, nicht, wer den Key hat. Alles, was an Spieler ausgeliefert wird, lässt sich lesen, eine NUI sollte also über einen Einmal-Link hochladen und nie ein Token halten. Auflisten und löschen machst du von deinem Server aus.
Wann ein Link funktioniert
Sofort. Der Link aus der Antwort funktioniert, sobald die API geantwortet hat: Dateien werden beim Upload freigegeben, es gibt also keine Warteschlange. Nimmt die Moderation eine Datei später herunter, antwortet ihr Link mit 404.
Ein Video läuft von Anfang an so, wie es hochgeladen wurde. Optimierte Kopien kommen dazu, wenn die Verarbeitung fertig ist, und fivedock.status wechselt von pending zu ready. Darauf musst du nicht warten.
Lädst du über einen Key hoch, bekommen Spieler die neue Datei nach etwa 5 Minuten, weil Links mit Key 5 Minuten gecacht werden. Der Inhalt von /f/-Links ändert sich nie, sie werden also ein Jahr lang gecacht.
Rate Limits
Die Upload-API hat heute kein festes Request-Limit pro Token. Was Uploads stoppt, ist die Speichergrenze: Liegt ein Workspace bei aktiver harter Grenze über seinem Speicherlimit, bekommen Uploads 429, bis Speicher frei wird oder die Grenze steigt. Auch die CDN-Auslieferung über der Traffic-Grenze antwortet mit 429.
Nimm bei jedem 429 das Tempo raus, und wenn Retry-After mitkommt, warte mindestens so lange.
Fehler
Ein Fehler kommt mit seinem Statuscode und einem JSON-Body zurück; error und message sagen dasselbe.
{
"error": "that file type is not accepted",
"message": "that file type is not accepted"
}| Status | Wann |
|---|---|
| 400 | Der Request ist fehlerhaft: kein Dateiteil, base64, das sich nicht dekodieren lässt, oder ein Key, der gegen die Regeln verstößt. |
| 401 | Kein Token oder ein ungültiges. Ein Agent-Key fda_live_… bekommt das auch. |
| 403 | Dem Token fehlt der Scope für diese Adresse, oder der Upload-Link ist ungültig oder abgelaufen. |
| 404 | In deinem Workspace gibt es keine Datei mit dieser ID. |
| 410 | Der Einmal-Link wurde schon benutzt. |
| 413 | Die Datei liegt über dem Größenlimit oder über maxSize des Links. |
| 415 | Der Dateityp wird nicht angenommen oder passt nicht zu fileType oder contentType des Links, oder das Archiv ist weder zip noch rar. |
| 422 | Die Datei wurde von der Moderation heruntergenommen und kann nicht erneut hochgeladen werden, das Archiv lässt sich nicht lesen, oder keine Datei im Archiv ließ sich speichern. |
| 429 | Der Workspace liegt über seiner Speichergrenze. Nimm das Tempo raus und halte dich an Retry-After. |
| 503 | Dieser Teil der API ist auf dem Server, den du aufgerufen hast, nicht eingerichtet. |