Pular para o conteúdo
Nesta página

Documentação / Referência

Referência da API de upload

Todos os endereços que a API de upload atende, os campos que ela lê e o que ela devolve. Ela fala o dialeto do Fivemanage, então um script escrito para o Fivemanage funciona assim que você troca a URL base e a chave.

URL base e chaves

Todos os endereços desta página são relativos a https://api.fivedock.dev, e os arquivos são servidos de https://cdn.fivedock.dev.

Duas chaves, dois trabalhos

  • fd_live_… é um token de API. Você cria no painel, em Tokens, e escolhe os scopes dele. Envie como Authorization: fd_live_… ou Authorization: Bearer fd_live_…. Ele serve para esta API, para a API de logs e, como par de chaves, para o endpoint S3.
  • fda_live_… é uma chave de agente. Ela vai no server.cfg como set fivedock_key para o resource fivedock, e em nenhum outro lugar. A API de upload não a aceita.
curl
curl "https://api.fivedock.dev/api/v3/file?limit=1" \
  -H "Authorization: Bearer fd_live_…"

Guarde o token de API num servidor que você controla. Tudo que roda no jogo ou no navegador de um jogador deve enviar por um link de uso único, para nunca ver o token.

Scopes

Cada endereço exige um scope, mostrado na tabela abaixo, e um token sem ele recebe 403. files:read lista e lê arquivos, files:write envia e cria links de upload, files:delete apaga, folders:write cria pastas, usage:read lê armazenamento e tráfego, e logs:write envia logs.

Endereços

Os caminhos e os nomes de campo são os do Fivemanage, mais um upload de arquivos compactados próprio do FiveDock. Os endereços de mídia do Fivemerr também são atendidos.

Endereços da API de upload
EndereçoScopeO que faz
Arquivos
POST /api/v3/fileScope · files:writeEnvia um arquivo como multipart/form-data.
POST /api/v3/file/base64Scope · files:writeEnvia um arquivo em base64 dentro de um corpo JSON.
POST /api/v3/file/archiveScope · files:writeEnvia um .zip ou .rar e guarda cada arquivo dentro dele. Só no FiveDock.
GET /api/v3/fileScope · files:readLista seus arquivos, uma página por vez.
GET /api/v3/file/{id}Scope · files:readLê os detalhes de um arquivo.
DELETE /api/v3/file/{id}Scope · files:deleteApaga um arquivo.
Links de upload
GET /api/v3/file/presigned-urlScope · files:writeCria um link de upload de uso único. Ele dura 15 minutos, a menos que você peça menos ou mais.
POST /api/v3/file/presigned-url/{ticket}Scope · Nenhum, o link é a credencialEnvia por um link criado acima, sem token.
GET /api/v2/presigned-urlScope · files:writeCria um link de upload do jeito antigo. Ele dura 5 minutos, a menos que você peça menos ou mais.
POST /api/v2/presigned-url/{ticket}Scope · Nenhum, o link é a credencialEnvia por um link criado do jeito antigo, sem token.
Fivemanage v2
POST /api/v2/imageScope · files:writeEnvia uma imagem. O arquivo vai no campo image ou em file.
POST /api/v2/videoScope · files:writeEnvia um vídeo. O arquivo vai no campo video ou em file.
POST /api/v2/audioScope · files:writeEnvia um som. O arquivo vai no campo audio ou em file.
Fivemerr
POST /v1/media/imagesScope · files:writeEnvia uma imagem. O arquivo vai em file ou data.
POST /v1/media/videosScope · files:writeEnvia um vídeo. O arquivo vai em file ou data.
POST /v1/media/audiosScope · files:writeEnvia um som. O arquivo vai em file ou data.
Logs
POST /api/logsScope · logs:writeEnvia um evento de log.
POST /api/logs/batchScope · logs:writeEnvia eventos de log em lote.
POST /api/v3/logsScope · logs:writeEnvia logs para o endereço v3 do Fivemanage.

Os endereços de logs aceitam os formatos que os clientes do Fivemanage enviam, inclusive o logger do Fivemanage no ox_lib (set ox:logger "fivemanage"), e exigem um token com logs:write. O ox_lib guarda o endereço do Fivemanage em imports/logger/providers/fivemanage.lua, então troque ali a linha endpoint pelo endereço do FiveDock, também depois de cada atualização do ox_lib.

Enviar um arquivo

Envie POST /api/v3/file como multipart/form-data, com o arquivo na parte file. Todos os outros campos são opcionais.

Campos do upload
CampoO que faz
fileO próprio arquivo. Um por requisição.
filenameO nome com que ele é guardado. Por padrão, o nome da parte do arquivo.
pathA pasta onde ele cai, como /mdt/mugshots. Criada na hora; por padrão, a raiz.
keyUm nome fixo como items/burger.png. O arquivo fica num link que nunca muda, e enviar de novo com a mesma chave o substitui. Quando definida, ela tem prioridade sobre path e filename.
metadataUm objeto JSON guardado com o arquivo e devolvido quando você o lê, como o caso ou o personagem a que ele pertence. O que não for um objeto JSON é guardado como um objeto vazio.
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"}'

Um arquivo com chave fica em /o/, depois o nome curto do seu workspace, depois a chave. O nome curto nunca muda e também é o nome do bucket no endpoint S3.

URL
https://cdn.fivedock.dev/o/{workspace}/{key}
https://cdn.fivedock.dev/o/my-city/items/burger.png
  • Uma chave tem de 1 a 512 caracteres, em segmentos separados por /. Sem segmentos vazios, . ou .., e sem / no começo.
  • São permitidos letras, dígitos e . _ - ~ @ + : ( ) , = !. Uma chave com qualquer outra coisa, como um espaço, é recusada com 400.
  • Enviar para uma chave já em uso substitui o arquivo por trás do link. O arquivo antigo é guardado por 7 dias e depois apagado de vez, como depois de apagar.
  • Links com chave são servidos com Cache-Control: public, max-age=300, stale-while-revalidate=60 e um ETag, então um arquivo substituído chega aos jogadores em uns 5 minutos e um sem mudanças é revalidado com um 304 barato.
  • No painel, um arquivo com chave aparece na pasta da chave dele: items/burger.png fica em /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"

Enviar em base64

POST /api/v3/file/base64 aceita um corpo JSON em vez de um formulário. base64 é o arquivo, como data URI ou base64 puro, e filename, path, key e metadata funcionam como no formulário. É o caminho curto a partir do Lua, porque o screenshot-basic já te entrega uma 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

O arquivo decodificado pode ter até 16 MiB. Qualquer coisa maior vai como multipart ou por um link de upload.

O que um upload responde

Todo endereço de upload responde 200 com o mesmo formato. Os campos de primeiro nível são os que o Fivemanage envia; fivedock traz nossos 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 e data.url são o link a usar. Num upload com chave, é o link fixo /o/.
  • fivedock.fileUrl é o link /f/ próprio do arquivo. O conteúdo dele nunca muda, e ele fica em cache por um ano.
  • fivedock.key é a chave, quando o arquivo tem uma.
  • fivedock.deduplicated é true quando os mesmos bytes já estavam guardados, então nada novo foi cobrado.
  • fivedock.status é pending enquanto as cópias otimizadas são feitas e ready quando ficam prontas. O link funciona nos dois casos.

Enviar um arquivo compactado

POST /api/v3/file/archive aceita um .zip ou .rar na parte file e guarda cada arquivo dentro dele. path é a pasta onde extrair, a raiz por padrão.

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"

Com keys=true, o padrão, cada arquivo ganha uma chave, que é a pasta de destino mais o caminho dele dentro do compactado: path=items e uma entrada burger.png dão items/burger.png. Com keys=false, os arquivos recebem ids aleatórios e caem na pasta de destino mais a pasta deles dentro do compactado.

As pastas do compactado são mantidas: um arquivo com images/burger.png extraído em items dá items/images/burger.png. Para deixar uma pasta de fora, compacte o conteúdo dela em vez da própria pasta.

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

Pastas, links, __MACOSX/, arquivos ocultos, Thumbs.db e desktop.ini são pulados sem aviso. Qualquer outro arquivo que não possa ser guardado aparece em skipped com um motivo: unsupported_type, too_large, encrypted, unsafe_path, too_many_files, archive_too_large, storage_full, duplicate ou failed. A resposta é 200 quando pelo menos um arquivo foi salvo, mesmo que outros tenham sido pulados; quando nenhum pôde ser salvo, é 422, com a mesma lista skipped. Se o compactado estiver danificado no meio, incomplete vem como true: os arquivos antes do dano foram salvos e nada depois dele foi lido.

O compactado pode ter até 512 MiB, com no máximo 5.000 arquivos e 4 GiB descompactado, e cada arquivo precisa ser de um tipo que aceitamos. RAR 4 e RAR 5 são lidos; entradas protegidas por senha são puladas como encrypted, e compactados em vários volumes não são suportados. O que não for zip nem rar recebe 415, e um compactado que não dá para ler recebe 422. Para conjuntos maiores, sincronize pelo endpoint S3 com rclone ou a CLI da aws.

Links de upload de uso único

Um link de upload deixa a NUI de um jogador, ou qualquer navegador, enviar um arquivo sem nunca ver o seu token. Seu servidor cria o link com GET /api/v3/file/presigned-url e um token com files:write, e repassa. Todos os parâmetros são opcionais, e quem envia não consegue mudar nenhum deles.

Parâmetros do link de upload
ParâmetroO que faz
expiresAtQuando o link para de funcionar, em segundos Unix. O padrão é daqui a 15 minutos (5 em /api/v2/presigned-url), e o máximo é 1 hora.
singleUsetrue faz o link aceitar exatamente um upload bem-sucedido; depois disso ele responde 410 Gone. Uma tentativa recusada, por tipo errado ou arquivo grande demais, não gasta o link.
maxSizeO maior arquivo que o link aceita, em bytes, até o limite por arquivo. Um arquivo maior recebe 413.
fileTypeimage, video ou audio. Qualquer outro tipo de arquivo recebe 415.
contentTypeUm tipo exato, como video/webm. Qualquer outro tipo recebe 415.
pathA pasta onde o arquivo cai.
keyGuarda o arquivo com essa chave, então ele ganha um link fixo e substitui o que estava lá.
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_…"

A resposta traz o link e repete o que ele permite:

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

Para enviar, faça POST para presignedUrl como multipart/form-data, com o arquivo no campo file e, se quiser, filename e metadata. Não envie cabeçalho Authorization. A resposta é a mesma de 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]"

Gravando um clipe na NUI

Um clipe de câmera corporal para um MDT: o servidor cria um link de uso único para um arquivo webm, o script do cliente repassa para a NUI, e a NUI grava com MediaRecorder e envia o Blob direto para o 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)

Crie um link por upload e dê a ele vida curta. Com singleUse=true, quem copiar o link da NUI recebe 410 assim que o seu clipe tiver chegado.

Listar, ler e apagar

GET /api/v3/file lista seus arquivos, 50 por página, a menos que você defina limit, até 100. page escolhe a página, type mantém só image, video ou audio, e path uma pasta. Acrescente o id de um arquivo ao endereço para ler esse arquivo, ou envie DELETE para o mesmo endereço para apagá-lo. Cada arquivo vem com url e fileUrl: num arquivo com chave, url é o link estável e key também aparece; nos outros, os dois são o 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 }
}

Um arquivo apagado para de ser servido na hora. Por 7 dias ele pode ser restaurado pelo painel. Apagar exige um token com files:delete.

Limites e tipos de arquivo

  • Um arquivo pode ter até 512 MiB.
  • Um upload JSON em base64 pode levar até 16 MiB de arquivo decodificado.
  • Um compactado pode ter até 512 MiB, com no máximo 5.000 arquivos e 4 GiB descompactado.
  • Uma requisição de upload pode levar até 5 minutos.

O tipo é lido dos bytes do arquivo, não do nome nem do Content-Type que você envia. São aceitos:

  • Imagens: PNG, JPEG, WebP, AVIF e GIF.
  • Vídeo: MP4, WebM e QuickTime (.mov).
  • Áudio: MP3, OGG e WAV.

video/webm é aceito, então um clipe do MediaRecorder sobe do jeito que está. O endpoint S3 também guarda outros tipos e os serve como download.

Navegadores e NUI (CORS)

A API de upload responde a navegadores de qualquer origem, com Access-Control-Allow-Origin: * e sem cookies. Isso inclui páginas NUI do FiveM, cuja origem é https://cfx-nui- seguido do nome do resource. Vale para /api/v3/file…, /api/v2/… e /v1/media/…. O preflight permite GET, POST e OPTIONS, com os cabeçalhos Authorization e Content-Type.

O CORS decide quais páginas podem chamar, não quem tem a chave. Tudo que é entregue aos jogadores pode ser lido, então uma NUI deve enviar por um link de uso único e nunca guardar um token. Liste e apague a partir do seu servidor.

Quando um link funciona

Na hora. O link da resposta funciona assim que a API responde: os arquivos são aprovados no upload, então não há fila para esperar. Se a moderação derrubar um arquivo depois, o link dele responde 404.

Um vídeo toca do jeito que foi enviado desde o começo. As cópias otimizadas entram quando o processamento termina, e fivedock.status passa de pending para ready. Você não precisa esperar por isso.

Quando você envia por cima de uma chave, os jogadores recebem o arquivo novo em uns 5 minutos, porque links com chave ficam em cache por 5 minutos. O conteúdo dos links /f/ nunca muda, então eles ficam em cache por um ano.

Limites de requisição

Hoje a API de upload não tem um limite fixo de requisições por token. O que para os uploads é o teto de armazenamento: quando um workspace passa do limite de armazenamento com o teto rígido ligado, os uploads recebem 429 até liberar espaço ou subir o teto. A entrega pela CDN acima do teto de tráfego também responde 429.

Diminua o ritmo a cada 429 e, quando vier Retry-After, espere pelo menos esse tempo.

Erros

Um erro volta com o código de status e um corpo JSON; error e message dizem a mesma coisa.

JSON
{
  "error": "that file type is not accepted",
  "message": "that file type is not accepted"
}
Códigos de status de erro
StatusQuando
400A requisição está malformada: sem parte de arquivo, base64 que não decodifica ou uma chave que quebra as regras.
401Sem token, ou com um token inválido. Uma chave de agente fda_live_… também recebe isso.
403Falta ao token o scope deste endereço, ou o link de upload é inválido ou expirou.
404Não existe arquivo com esse id no seu workspace.
410O link de uso único já foi usado.
413O arquivo passa do limite de tamanho, ou do maxSize do link.
415O tipo de arquivo não é aceito ou não bate com o fileType ou o contentType do link, ou o compactado não é zip nem rar.
422O arquivo foi derrubado pela moderação e não pode ser enviado de novo, o compactado não dá para ler, ou nenhum arquivo do compactado pôde ser salvo.
429O workspace passou do teto de armazenamento. Diminua o ritmo e respeite Retry-After.
503Essa parte da API não está configurada no servidor que você chamou.