Przejdź do treści
Na tej stronie

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

  • fd_live_… to token API. Tworzysz go w panelu na stronie Tokeny i wybierasz mu zakresy. Wysyłaj go jako Authorization: fd_live_… albo Authorization: Bearer fd_live_…. Działa w tym API, w API logów i, jako para kluczy, w endpoincie S3.
  • fda_live_… to klucz agenta. Wpisujesz go w server.cfg jako set fivedock_key dla zasobu fivedock i nigdzie indziej. API wysyłania go nie przyjmuje.
curl
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.

Adresy API wysyłania
AdresZakresCo robi
Pliki
POST /api/v3/fileZakres · files:writeWysyła jeden plik jako multipart/form-data.
POST /api/v3/file/base64Zakres · files:writeWysyła jeden plik zakodowany w base64 w treści JSON.
POST /api/v3/file/archiveZakres · files:writeWysyła .zip albo .rar i zapisuje każdy plik ze środka. Tylko w FiveDock.
GET /api/v3/fileZakres · files:readListuje Twoje pliki, strona po stronie.
GET /api/v3/file/{id}Zakres · files:readOdczytuje szczegóły jednego pliku.
DELETE /api/v3/file/{id}Zakres · files:deleteUsuwa jeden plik.
Linki do wysyłki
GET /api/v3/file/presigned-urlZakres · files:writeGeneruje 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 linkWysyła przez link wygenerowany wyżej, bez tokenu.
GET /api/v2/presigned-urlZakres · files:writeGeneruje 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 linkWysyła przez link wygenerowany po staremu, bez tokenu.
Fivemanage v2
POST /api/v2/imageZakres · files:writeWysyła obraz. Plik idzie w polu image albo file.
POST /api/v2/videoZakres · files:writeWysyła wideo. Plik idzie w polu video albo file.
POST /api/v2/audioZakres · files:writeWysyła dźwięk. Plik idzie w polu audio albo file.
Fivemerr
POST /v1/media/imagesZakres · files:writeWysyła obraz. Plik idzie w polu file albo data.
POST /v1/media/videosZakres · files:writeWysyła wideo. Plik idzie w polu file albo data.
POST /v1/media/audiosZakres · files:writeWysyła dźwięk. Plik idzie w polu file albo data.
Logi
POST /api/logsZakres · logs:writeWysyła jedno zdarzenie logu.
POST /api/logs/batchZakres · logs:writeWysyła zdarzenia logów w paczce.
POST /api/v3/logsZakres · logs:writeWysył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.

Pola wysyłki
PoleCo robi
fileSam plik. Jeden na żądanie.
filenameNazwa, pod którą go zapiszemy. Domyślnie nazwa z części z plikiem.
pathFolder, do którego trafi, na przykład /mdt/mugshots. Tworzony w locie; domyślnie folder główny.
keyStał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.
metadataObiekt 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"}'

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.

URL
https://cdn.fivedock.dev/o/{workspace}/{key}
https://cdn.fivedock.dev/o/my-city/items/burger.png
  • Klucz ma od 1 do 512 znaków, w segmentach rozdzielonych /. Bez pustych segmentów, bez . i .. oraz bez / na początku.
  • Dozwolone są litery, cyfry i . _ - ~ @ + : ( ) , = !. Klucz z czymkolwiek innym, na przykład ze spacją, zostanie odrzucony z kodem 400.
  • Wysłanie pliku z zajętym już kluczem podmienia plik pod linkiem. Stary plik jest trzymany jeszcze 7 dni, a potem usuwany, tak jak po usunięciu.
  • Linki z kluczem są wysyłane z Cache-Control: public, max-age=300, stale-while-revalidate=60 i ETagiem, więc podmieniony plik dociera do graczy po około 5 minutach, a niezmieniony jest tanio sprawdzany odpowiedzią 304.
  • W panelu plik z kluczem widać w folderze wynikającym z klucza: items/burger.png leży w /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"

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)
end

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

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 i data.url to link, którego należy używać. Przy wysyłce z kluczem to stały link /o/.
  • fivedock.fileUrl to własny link pliku, /f/. Jego zawartość nigdy się nie zmienia, więc cache trzyma go przez rok.
  • fivedock.key to klucz, jeśli plik go ma.
  • fivedock.deduplicated ma wartość true, gdy te same bajty już u nas były, więc nic nowego nie doliczyliśmy.
  • fivedock.status to pending, dopóki powstają zoptymalizowane kopie, i ready, gdy są gotowe. Link działa w obu przypadkach.

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

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

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.

Parametry linku do wysyłki
ParametrCo robi
expiresAtKiedy link przestaje działać, w sekundach uniksowych. Domyślnie za 15 minut (5 na /api/v2/presigned-url), najpóźniej za 1 godzinę.
singleUsetrue 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.
maxSizeNajwiększy plik, jaki link przyjmie, w bajtach, najwyżej do limitu na plik. Większy plik dostaje 413.
fileTypeimage, video albo audio. Każdy inny rodzaj pliku dostaje 415.
contentTypeJeden konkretny typ, na przykład video/webm. Każdy inny typ dostaje 415.
pathFolder, do którego trafi plik.
keyZapisuje plik pod tym kluczem, więc dostaje on stały link i podmienia to, co tam było.
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_…"

Odpowiedź zawiera link i powtarza, na co on pozwala:

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"
  }
}

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

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]"

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_…"
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 }
}

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

  • Jeden plik może mieć do 512 MiB.
  • Wysyłka base64 w JSON może zawierać do 16 MiB pliku po zdekodowaniu.
  • Archiwum może mieć do 512 MiB, najwyżej 5000 plików i 4 GiB po rozpakowaniu.
  • Żądanie wysyłki może trwać do 5 minut.

Typ rozpoznajemy po bajtach pliku, a nie po nazwie czy wysłanym Content-Type. Przyjmujemy:

  • Obrazy: PNG, JPEG, WebP, AVIF i GIF.
  • Wideo: MP4, WebM i QuickTime (.mov).
  • Dźwięk: MP3, OGG i WAV.

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.

JSON
{
  "error": "that file type is not accepted",
  "message": "that file type is not accepted"
}
Kody błędów
StatusKiedy
400Żądanie jest błędne: brak części z plikiem, base64, którego nie da się zdekodować, albo klucz niezgodny z zasadami.
401Brak tokenu albo token nieważny. Klucz agenta fda_live_… też to dostaje.
403Token nie ma zakresu wymaganego przez ten adres albo link do wysyłki jest nieważny lub wygasł.
404W Twojej organizacji nie ma pliku o tym identyfikatorze.
410Jednorazowy link został już użyty.
413Plik przekracza limit rozmiaru albo maxSize linku.
415Typ pliku nie jest przyjmowany albo nie pasuje do fileType lub contentType linku, albo archiwum nie jest zipem ani rarem.
422Plik 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.
429Organizacja przekroczyła limit miejsca. Wstrzymaj kolejne próby i respektuj Retry-After.
503Ta część API nie jest skonfigurowana na serwerze, do którego się zwracasz.