Zum Inhalt springen
Auf dieser Seite

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

  • fd_live_… ist ein API-Token. Du erstellst es im Panel unter Tokens und wählst seine Scopes. Schick es als Authorization: fd_live_… oder Authorization: Bearer fd_live_…. Es gilt für diese API, für die Logs-API und, als Schlüsselpaar, für den S3-Endpunkt.
  • fda_live_… ist ein Agent-Key. Er gehört als set fivedock_key für die Resource fivedock in die server.cfg und nirgendwo sonst hin. Die Upload-API nimmt ihn nicht an.
curl
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.

Adressen der Upload-API
AdresseScopeWas sie tut
Dateien
POST /api/v3/fileScope · files:writeLädt eine Datei als multipart/form-data hoch.
POST /api/v3/file/base64Scope · files:writeLädt eine Datei hoch, die als base64 in einem JSON-Body steckt.
POST /api/v3/file/archiveScope · files:writeLädt eine .zip oder .rar hoch und speichert jede Datei darin. Nur bei FiveDock.
GET /api/v3/fileScope · files:readListet deine Dateien, seitenweise.
GET /api/v3/file/{id}Scope · files:readLiest die Details einer Datei.
DELETE /api/v3/file/{id}Scope · files:deleteLöscht eine Datei.
Upload-Links
GET /api/v3/file/presigned-urlScope · files:writeErzeugt 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 BerechtigungLädt über einen oben erzeugten Link hoch, ohne Token.
GET /api/v2/presigned-urlScope · files:writeErzeugt 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 BerechtigungLädt über einen auf die ältere Art erzeugten Link hoch, ohne Token.
Fivemanage v2
POST /api/v2/imageScope · files:writeLädt ein Bild hoch. Die Datei steckt im Feld image oder in file.
POST /api/v2/videoScope · files:writeLädt ein Video hoch. Die Datei steckt im Feld video oder in file.
POST /api/v2/audioScope · files:writeLädt einen Sound hoch. Die Datei steckt im Feld audio oder in file.
Fivemerr
POST /v1/media/imagesScope · files:writeLädt ein Bild hoch. Die Datei steckt in file oder data.
POST /v1/media/videosScope · files:writeLädt ein Video hoch. Die Datei steckt in file oder data.
POST /v1/media/audiosScope · files:writeLädt einen Sound hoch. Die Datei steckt in file oder data.
Logs
POST /api/logsScope · logs:writeSchickt ein Log-Ereignis.
POST /api/logs/batchScope · logs:writeSchickt Log-Ereignisse gebündelt.
POST /api/v3/logsScope · logs:writeSchickt 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.

Upload-Felder
FeldWas es tut
fileDie Datei selbst. Eine pro Request.
filenameDer Name, unter dem sie gespeichert wird. Standardmäßig der Name aus dem Dateiteil.
pathDer Zielordner, etwa /mdt/mugshots. Wird bei Bedarf angelegt; standardmäßig der Stammordner.
keyEin 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.
metadataEin 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"}'

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.

URL
https://cdn.fivedock.dev/o/{workspace}/{key}
https://cdn.fivedock.dev/o/my-city/items/burger.png
  • Ein Key hat 1 bis 512 Zeichen, in Segmenten, getrennt durch /. Keine leeren, .- oder ..-Segmente und kein / am Anfang.
  • Erlaubt sind Buchstaben, Ziffern und . _ - ~ @ + : ( ) , = !. Ein Key mit etwas anderem, etwa einem Leerzeichen, wird mit 400 abgelehnt.
  • Ein Upload auf einen vergebenen Key ersetzt die Datei hinter dem Link. Die alte Datei wird noch 7 Tage aufbewahrt und dann endgültig gelöscht, wie nach dem Löschen.
  • Links mit Key kommen mit Cache-Control: public, max-age=300, stale-while-revalidate=60 und einem ETag, eine ersetzte Datei erreicht Spieler also nach etwa 5 Minuten, und eine unveränderte wird günstig mit 304 bestätigt.
  • Im Panel steht eine Datei mit Key im Ordner ihres Keys: items/burger.png liegt unter /items.
curl
# 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)
end

Die 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.

JSON
{
  "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"
  }
}
  • url und data.url sind der Link, den du benutzt. Bei einem Upload mit Key ist das der feste /o/-Link.
  • fivedock.fileUrl ist der eigene /f/-Link der Datei. Sein Inhalt ändert sich nie, und er wird ein Jahr lang gecacht.
  • fivedock.key ist der Key, wenn die Datei einen hat.
  • fivedock.deduplicated ist true, wenn dieselben Bytes schon gespeichert waren, es wurde also nichts Neues berechnet.
  • fivedock.status ist pending, solange optimierte Kopien entstehen, und ready, sobald sie fertig sind. Der Link funktioniert in beiden Fällen.

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
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.

JSON
{
  "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 eines Upload-Links
ParameterWas er tut
expiresAtWann 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.
singleUsetrue 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.
maxSizeDie größte Datei, die der Link annimmt, in Bytes, bis zum Limit pro Datei. Eine größere Datei bekommt 413.
fileTypeimage, video oder audio. Jede andere Art Datei bekommt 415.
contentTypeGenau ein Typ, etwa video/webm. Jeder andere Typ bekommt 415.
pathDer Ordner, in dem die Datei landet.
keySpeichert die Datei unter diesem Key, sie bekommt also einen festen Link und ersetzt, was dort lag.
curl
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:

JSON
{
  "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.

curl
# 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_…"
JSON
{
  "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

  • Eine Datei darf bis zu 512 MiB groß sein.
  • Ein base64-JSON-Upload trägt bis zu 16 MiB dekodierte Datei.
  • Ein Archiv darf bis zu 512 MiB groß sein, mit höchstens 5.000 Dateien und 4 GiB entpackt.
  • Ein Upload-Request darf bis zu 5 Minuten dauern.

Der Typ wird aus den Bytes der Datei gelesen, nicht aus ihrem Namen oder dem Content-Type, den du schickst. Angenommen werden:

  • Bilder: PNG, JPEG, WebP, AVIF und GIF.
  • Video: MP4, WebM und QuickTime (.mov).
  • Audio: MP3, OGG und WAV.

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.

JSON
{
  "error": "that file type is not accepted",
  "message": "that file type is not accepted"
}
Fehler-Statuscodes
StatusWann
400Der Request ist fehlerhaft: kein Dateiteil, base64, das sich nicht dekodieren lässt, oder ein Key, der gegen die Regeln verstößt.
401Kein Token oder ein ungültiges. Ein Agent-Key fda_live_… bekommt das auch.
403Dem Token fehlt der Scope für diese Adresse, oder der Upload-Link ist ungültig oder abgelaufen.
404In deinem Workspace gibt es keine Datei mit dieser ID.
410Der Einmal-Link wurde schon benutzt.
413Die Datei liegt über dem Größenlimit oder über maxSize des Links.
415Der Dateityp wird nicht angenommen oder passt nicht zu fileType oder contentType des Links, oder das Archiv ist weder zip noch rar.
422Die 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.
429Der Workspace liegt über seiner Speichergrenze. Nimm das Tempo raus und halte dich an Retry-After.
503Dieser Teil der API ist auf dem Server, den du aufgerufen hast, nicht eingerichtet.