Na tej stronie
- Adres bazowy i klucze
- Dwa klucze, dwa zadania
- Zakresy
- Adresy
- Wysyłanie pliku
- Stałe linki z kluczem
- Wysyłka w base64
- Co odpowiada wysyłka
- Wysyłka archiwum
- Jednorazowe linki do wysyłki
- Nagrywanie klipu w NUI
- Listowanie, odczyt i usuwanie
- Limity i typy plików
- Przeglądarki i NUI (CORS)
- Kiedy link działa
- Limity zapytań
- Błędy
Dokumentacja / Dokumentacja API
Dokumentacja API wysyłania
Wszystkie adresy, na które odpowiada API wysyłania, pola, które czyta, i to, co odsyła. Mówi dialektem Fivemanage, więc skrypt napisany pod Fivemanage zadziała po zmianie bazowego adresu URL i klucza.
Adres bazowy i klucze
Wszystkie adresy na tej stronie są względne wobec https://api.fivedock.dev, a pliki serwujemy z https://cdn.fivedock.dev.
Dwa klucze, dwa zadania
curl "https://api.fivedock.dev/api/v3/file?limit=1" \
-H "Authorization: Bearer fd_live_…"Trzymaj token API na serwerze, nad którym masz kontrolę. Wszystko, co działa w grze albo w przeglądarce gracza, powinno wysyłać pliki przez jednorazowy link, żeby nigdy nie zobaczyć tokenu.
Zakresy
Każdy adres wymaga jednego zakresu, podanego w tabeli niżej, a token bez niego dostaje 403. files:read listuje i odczytuje pliki, files:write wysyła i generuje linki do wysyłki, files:delete usuwa, folders:write tworzy foldery, usage:read czyta zużycie miejsca i transferu, a logs:write wysyła logi.
Adresy
Ścieżki i nazwy pól są takie jak w Fivemanage, a do tego dochodzi nasze własne wysyłanie archiwów. Odpowiadamy też na adresy mediów z Fivemerr.
| Adres | Zakres | Co robi |
|---|---|---|
| Pliki | ||
| POST /api/v3/file | Zakres · files:write | Wysyła jeden plik jako multipart/form-data. |
| POST /api/v3/file/base64 | Zakres · files:write | Wysyła jeden plik zakodowany w base64 w treści JSON. |
| POST /api/v3/file/archive | Zakres · files:write | Wysyła .zip albo .rar i zapisuje każdy plik ze środka. Tylko w FiveDock. |
| GET /api/v3/file | Zakres · files:read | Listuje Twoje pliki, strona po stronie. |
| GET /api/v3/file/{id} | Zakres · files:read | Odczytuje szczegóły jednego pliku. |
| DELETE /api/v3/file/{id} | Zakres · files:delete | Usuwa jeden plik. |
| Linki do wysyłki | ||
| GET /api/v3/file/presigned-url | Zakres · files:write | Generuje jednorazowy link do wysyłki. Działa 15 minut, chyba że poprosisz o krócej albo dłużej. |
| POST /api/v3/file/presigned-url/{ticket} | Zakres · Brak, poświadczeniem jest sam link | Wysyła przez link wygenerowany wyżej, bez tokenu. |
| GET /api/v2/presigned-url | Zakres · files:write | Generuje link do wysyłki po staremu. Działa 5 minut, chyba że poprosisz o krócej albo dłużej. |
| POST /api/v2/presigned-url/{ticket} | Zakres · Brak, poświadczeniem jest sam link | Wysyła przez link wygenerowany po staremu, bez tokenu. |
| Fivemanage v2 | ||
| POST /api/v2/image | Zakres · files:write | Wysyła obraz. Plik idzie w polu image albo file. |
| POST /api/v2/video | Zakres · files:write | Wysyła wideo. Plik idzie w polu video albo file. |
| POST /api/v2/audio | Zakres · files:write | Wysyła dźwięk. Plik idzie w polu audio albo file. |
| Fivemerr | ||
| POST /v1/media/images | Zakres · files:write | Wysyła obraz. Plik idzie w polu file albo data. |
| POST /v1/media/videos | Zakres · files:write | Wysyła wideo. Plik idzie w polu file albo data. |
| POST /v1/media/audios | Zakres · files:write | Wysyła dźwięk. Plik idzie w polu file albo data. |
| Logi | ||
| POST /api/logs | Zakres · logs:write | Wysyła jedno zdarzenie logu. |
| POST /api/logs/batch | Zakres · logs:write | Wysyła zdarzenia logów w paczce. |
| POST /api/v3/logs | Zakres · logs:write | Wysyła logi na adres v3 z Fivemanage. |
Adresy logów przyjmują formaty, które wysyłają klienci Fivemanage, w tym logger Fivemanage z ox_lib (set ox:logger "fivemanage"), i wymagają tokenu z logs:write. ox_lib trzyma adres Fivemanage w imports/logger/providers/fivemanage.lua, więc zmień tam linijkę endpoint na adres FiveDock, także po każdej aktualizacji ox_lib.
Wysyłanie pliku
Wyślij POST /api/v3/file jako multipart/form-data, z plikiem w części file. Wszystkie pozostałe pola są opcjonalne.
| Pole | Co robi |
|---|---|
| file | Sam plik. Jeden na żądanie. |
| filename | Nazwa, pod którą go zapiszemy. Domyślnie nazwa z części z plikiem. |
| path | Folder, do którego trafi, na przykład /mdt/mugshots. Tworzony w locie; domyślnie folder główny. |
| key | Stała nazwa, na przykład items/burger.png. Plik dostaje link, który się nie zmienia, a ponowne wysłanie z tym samym kluczem go podmienia. Jeśli klucz jest ustawiony, ma pierwszeństwo przed path i filename. |
| metadata | Obiekt JSON zapisany razem z plikiem i odsyłany przy odczycie, na przykład sprawa albo postać, do której plik należy. Wszystko, co nie jest obiektem JSON, zapisujemy jako pusty obiekt. |
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"}'Stałe linki z kluczem
Plik z kluczem jest dostępny pod /o/, potem krótką nazwą Twojej organizacji, a potem kluczem. Krótka nazwa nigdy się nie zmienia i jest też nazwą bucketu w endpoincie S3.
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"Wysyłka w base64
POST /api/v3/file/base64 przyjmuje treść JSON zamiast formularza. base64 to plik, jako data URI albo czyste base64, a filename, path, key i metadata działają tak samo jak w formularzu. To najkrótsza droga z Lua, bo screenshot-basic od razu daje Ci 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)
endPlik po zdekodowaniu może mieć do 16 MiB. Wszystko większe wysyłaj jako multipart albo przez link do wysyłki.
Co odpowiada wysyłka
Każdy adres wysyłki odpowiada 200 w tym samym kształcie. Pola najwyższego poziomu to te, które wysyła Fivemanage; w fivedock są nasze dodatki.
{
"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"
}
}Wysyłka archiwum
POST /api/v3/file/archive przyjmuje .zip albo .rar w części file i zapisuje każdy plik ze środka. path to folder, do którego rozpakujemy archiwum, domyślnie główny.
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"Przy keys=true, czyli domyślnie, każdy plik dostaje klucz: folder docelowy plus ścieżkę wewnątrz archiwum. path=items i plik burger.png dają items/burger.png. Przy keys=false pliki dostają losowe identyfikatory i trafiają do folderu docelowego, a w nim do tych samych podfolderów co w archiwum.
Foldery z archiwum zostają zachowane: archiwum z images/burger.png rozpakowane do items daje items/images/burger.png. Żeby pominąć folder, spakuj jego zawartość, a nie sam folder.
{
"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
}
}Foldery, linki, __MACOSX/, pliki ukryte, Thumbs.db i desktop.ini pomijamy bez słowa. Każdy inny plik, którego nie da się zapisać, trafia na listę skipped z powodem: unsupported_type, too_large, encrypted, unsafe_path, too_many_files, archive_too_large, storage_full, duplicate albo failed. Odpowiedź to 200, gdy zapisaliśmy choć jeden plik, nawet jeśli część pominęliśmy; gdy nie dało się zapisać żadnego, dostajesz 422 z tą samą listą skipped. Jeśli archiwum jest uszkodzone w połowie, incomplete ma wartość true: pliki sprzed uszkodzenia są zapisane, a dalszej części nie odczytaliśmy.
Archiwum może mieć do 512 MiB, najwyżej 5000 plików i 4 GiB po rozpakowaniu, a każdy plik musi być w formacie, który przyjmujemy. Czytamy RAR 4 i RAR 5; pozycje zabezpieczone hasłem pomijamy jako encrypted, a archiwów wieloczęściowych nie obsługujemy. Coś, co nie jest zipem ani rarem, dostaje 415, a archiwum, którego nie da się odczytać, 422. Większe zbiory synchronizuj przez endpoint S3 w rclone albo aws CLI.
Jednorazowe linki do wysyłki
Link do wysyłki pozwala NUI gracza albo dowolnej przeglądarce wysłać plik bez oglądania Twojego tokenu. Twój serwer generuje go przez GET /api/v3/file/presigned-url tokenem z files:write i przekazuje dalej. Wszystkie parametry są opcjonalne, a wysyłający nie może zmienić żadnego z nich.
| Parametr | Co robi |
|---|---|
| expiresAt | Kiedy link przestaje działać, w sekundach uniksowych. Domyślnie za 15 minut (5 na /api/v2/presigned-url), najpóźniej za 1 godzinę. |
| singleUse | true sprawia, że link przyjmie dokładnie jedną udaną wysyłkę; potem odpowiada 410 Gone. Próba odrzucona, na przykład przez zły typ albo za duży plik, go nie zużywa. |
| maxSize | Największy plik, jaki link przyjmie, w bajtach, najwyżej do limitu na plik. Większy plik dostaje 413. |
| fileType | image, video albo audio. Każdy inny rodzaj pliku dostaje 415. |
| contentType | Jeden konkretny typ, na przykład video/webm. Każdy inny typ dostaje 415. |
| path | Folder, do którego trafi plik. |
| key | Zapisuje plik pod tym kluczem, więc dostaje on stały link i podmienia to, co tam było. |
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_…"Odpowiedź zawiera link i powtarza, na co on pozwala:
{
"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"
}
}Żeby wysłać plik, zrób POST na presignedUrl jako multipart/form-data, z plikiem w polu file i, jeśli chcesz, z filename i metadata. Nie wysyłaj nagłówka Authorization. Odpowiedź jest taka sama jak dla 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]"Nagrywanie klipu w NUI
Klip z kamery nasobnej do MDT: serwer generuje jednorazowy link na jeden plik webm, skrypt klienta przekazuje go do NUI, a NUI nagrywa przez MediaRecorder i wysyła Bloba prosto do FiveDock.
-- 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)Generuj osobny link na każdą wysyłkę i dawaj mu krótki czas życia. Z singleUse=true każdy, kto skopiuje link z NUI, dostanie 410, gdy Twój klip już dotrze.
Listowanie, odczyt i usuwanie
GET /api/v3/file listuje Twoje pliki, po 50 na stronę, chyba że ustawisz limit, maksymalnie 100. page wybiera stronę, type zostawia tylko image, video albo audio, a path jeden folder. Dopisz do adresu identyfikator pliku, żeby go odczytać, albo wyślij DELETE na ten sam adres, żeby go usunąć. Każdy plik ma url i fileUrl: dla pliku z kluczem url to jego stały link, a obok jest też key; w pozostałych przypadkach oba to link /f/.
# 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 }
}Usunięty plik od razu przestaje być serwowany. Przez 7 dni można go przywrócić z panelu. Usuwanie wymaga tokenu z files:delete.
Limity i typy plików
Typ rozpoznajemy po bajtach pliku, a nie po nazwie czy wysłanym Content-Type. Przyjmujemy:
video/webm jest przyjmowany, więc klip z MediaRecorder wysyłasz tak, jak jest. Endpoint S3 zapisuje też inne typy i serwuje je do pobrania.
Przeglądarki i NUI (CORS)
API wysyłania odpowiada przeglądarkom z dowolnego originu, z Access-Control-Allow-Origin: * i bez ciasteczek. Dotyczy to też stron NUI w FiveM, których origin to https://cfx-nui- i nazwa zasobu. Obejmuje /api/v3/file…, /api/v2/… i /v1/media/…. Zapytanie preflight dopuszcza GET, POST i OPTIONS oraz nagłówki Authorization i Content-Type.
CORS decyduje, które strony mogą wysyłać zapytania, a nie kto ma klucz. Wszystko, co trafia do graczy, da się odczytać, więc NUI powinno wysyłać pliki przez jednorazowy link i nigdy nie trzymać tokenu. Listuj i usuwaj pliki ze swojego serwera.
Kiedy link działa
Od razu. Link z odpowiedzi działa, gdy tylko API odpowie: pliki są zatwierdzane przy wysyłce, więc nie ma kolejki, na którą trzeba czekać. Jeśli moderacja zdejmie plik później, jego link odpowie 404.
Wideo odtwarza się od początku w wysłanej postaci. Zoptymalizowane kopie dochodzą, gdy skończy się przetwarzanie, a fivedock.status zmienia się z pending na ready. Nie musisz na to czekać.
Gdy wyślesz plik pod istniejący klucz, gracze dostaną nowy plik po około 5 minutach, bo linki z kluczem są trzymane w cache przez 5 minut. Zawartość linków /f/ nigdy się nie zmienia, więc cache trzyma je przez rok.
Limity zapytań
API wysyłania nie ma dziś stałego limitu zapytań na token. Wysyłkę zatrzymuje limit miejsca: gdy organizacja przekroczy swój limit przy włączonej twardej blokadzie, wysyłki dostają 429, dopóki nie zwolnisz miejsca albo nie podniesiesz limitu. Serwowanie z CDN po przekroczeniu limitu transferu też odpowiada 429.
Przy każdym 429 ponawiaj z coraz dłuższymi przerwami, a jeśli jest nagłówek Retry-After, odczekaj co najmniej tyle, ile podaje.
Błędy
Błąd wraca z kodem statusu i treścią JSON; error i message mówią to samo.
{
"error": "that file type is not accepted",
"message": "that file type is not accepted"
}| Status | Kiedy |
|---|---|
| 400 | Żądanie jest błędne: brak części z plikiem, base64, którego nie da się zdekodować, albo klucz niezgodny z zasadami. |
| 401 | Brak tokenu albo token nieważny. Klucz agenta fda_live_… też to dostaje. |
| 403 | Token nie ma zakresu wymaganego przez ten adres albo link do wysyłki jest nieważny lub wygasł. |
| 404 | W Twojej organizacji nie ma pliku o tym identyfikatorze. |
| 410 | Jednorazowy link został już użyty. |
| 413 | Plik przekracza limit rozmiaru albo maxSize linku. |
| 415 | Typ pliku nie jest przyjmowany albo nie pasuje do fileType lub contentType linku, albo archiwum nie jest zipem ani rarem. |
| 422 | Plik został zdjęty przez moderację i nie można go wysłać ponownie, archiwum nie da się odczytać albo nie dało się zapisać żadnego pliku z archiwum. |
| 429 | Organizacja przekroczyła limit miejsca. Wstrzymaj kolejne próby i respektuj Retry-After. |
| 503 | Ta część API nie jest skonfigurowana na serwerze, do którego się zwracasz. |