Nesta página
- URL base e chaves
- Duas chaves, dois trabalhos
- Scopes
- Endereços
- Enviar um arquivo
- Links fixos com key
- Enviar em base64
- O que um upload responde
- Enviar um arquivo compactado
- Links de upload de uso único
- Gravando um clipe na NUI
- Listar, ler e apagar
- Limites e tipos de arquivo
- Navegadores e NUI (CORS)
- Quando um link funciona
- Limites de requisição
- Erros
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
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ço | Scope | O que faz |
|---|---|---|
| Arquivos | ||
| POST /api/v3/file | Scope · files:write | Envia um arquivo como multipart/form-data. |
| POST /api/v3/file/base64 | Scope · files:write | Envia um arquivo em base64 dentro de um corpo JSON. |
| POST /api/v3/file/archive | Scope · files:write | Envia um .zip ou .rar e guarda cada arquivo dentro dele. Só no FiveDock. |
| GET /api/v3/file | Scope · files:read | Lista seus arquivos, uma página por vez. |
| GET /api/v3/file/{id} | Scope · files:read | Lê os detalhes de um arquivo. |
| DELETE /api/v3/file/{id} | Scope · files:delete | Apaga um arquivo. |
| Links de upload | ||
| GET /api/v3/file/presigned-url | Scope · files:write | Cria 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 credencial | Envia por um link criado acima, sem token. |
| GET /api/v2/presigned-url | Scope · files:write | Cria 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 credencial | Envia por um link criado do jeito antigo, sem token. |
| Fivemanage v2 | ||
| POST /api/v2/image | Scope · files:write | Envia uma imagem. O arquivo vai no campo image ou em file. |
| POST /api/v2/video | Scope · files:write | Envia um vídeo. O arquivo vai no campo video ou em file. |
| POST /api/v2/audio | Scope · files:write | Envia um som. O arquivo vai no campo audio ou em file. |
| Fivemerr | ||
| POST /v1/media/images | Scope · files:write | Envia uma imagem. O arquivo vai em file ou data. |
| POST /v1/media/videos | Scope · files:write | Envia um vídeo. O arquivo vai em file ou data. |
| POST /v1/media/audios | Scope · files:write | Envia um som. O arquivo vai em file ou data. |
| Logs | ||
| POST /api/logs | Scope · logs:write | Envia um evento de log. |
| POST /api/logs/batch | Scope · logs:write | Envia eventos de log em lote. |
| POST /api/v3/logs | Scope · logs:write | Envia 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.
| Campo | O que faz |
|---|---|
| file | O próprio arquivo. Um por requisição. |
| filename | O nome com que ele é guardado. Por padrão, o nome da parte do arquivo. |
| path | A pasta onde ele cai, como /mdt/mugshots. Criada na hora; por padrão, a raiz. |
| key | Um 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. |
| metadata | Um 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"}'Links fixos com key
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.
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"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)
endO 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.
{
"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"
}
}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 -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.
{
"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âmetro | O que faz |
|---|---|
| expiresAt | Quando 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. |
| singleUse | true 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. |
| maxSize | O maior arquivo que o link aceita, em bytes, até o limite por arquivo. Um arquivo maior recebe 413. |
| fileType | image, video ou audio. Qualquer outro tipo de arquivo recebe 415. |
| contentType | Um tipo exato, como video/webm. Qualquer outro tipo recebe 415. |
| path | A pasta onde o arquivo cai. |
| key | Guarda o arquivo com essa chave, então ele ganha um link fixo e substitui o que estava lá. |
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:
{
"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.
# 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_…"{
"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
O tipo é lido dos bytes do arquivo, não do nome nem do Content-Type que você envia. São aceitos:
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.
{
"error": "that file type is not accepted",
"message": "that file type is not accepted"
}| Status | Quando |
|---|---|
| 400 | A requisição está malformada: sem parte de arquivo, base64 que não decodifica ou uma chave que quebra as regras. |
| 401 | Sem token, ou com um token inválido. Uma chave de agente fda_live_… também recebe isso. |
| 403 | Falta ao token o scope deste endereço, ou o link de upload é inválido ou expirou. |
| 404 | Não existe arquivo com esse id no seu workspace. |
| 410 | O link de uso único já foi usado. |
| 413 | O arquivo passa do limite de tamanho, ou do maxSize do link. |
| 415 | O 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. |
| 422 | O 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. |
| 429 | O workspace passou do teto de armazenamento. Diminua o ritmo e respeite Retry-After. |
| 503 | Essa parte da API não está configurada no servidor que você chamou. |