En esta página
- URL base y claves
- Dos claves, dos trabajos
- Scopes
- Direcciones
- Subir un archivo
- Enlaces estables con key
- Subir en base64
- Qué responde una subida
- Subir un archivo comprimido
- Enlaces de subida de un solo uso
- Grabar un clip en la NUI
- Listar, leer y borrar
- Límites y tipos de archivo
- Navegadores y NUI (CORS)
- Cuándo funciona un enlace
- Límites de peticiones
- Errores
Documentación / Referencia
Referencia de la API de subida
Todas las direcciones a las que responde la API de subida, los campos que lee y lo que devuelve. Habla el dialecto de Fivemanage, así que un script escrito para Fivemanage funciona en cuanto cambias la URL base y la clave.
URL base y claves
Todas las direcciones de esta página son relativas a https://api.fivedock.dev, y los archivos se sirven desde https://cdn.fivedock.dev.
Dos claves, dos trabajos
curl "https://api.fivedock.dev/api/v3/file?limit=1" \
-H "Authorization: Bearer fd_live_…"Guarda el token de API en un servidor que controles. Todo lo que corre en el juego o en el navegador de un jugador debería subir por un enlace de un solo uso, para no ver nunca el token.
Scopes
Cada dirección necesita un scope, indicado en la tabla de abajo, y un token sin él recibe 403. files:read lista y lee archivos, files:write sube y crea enlaces de subida, files:delete borra, folders:write crea carpetas, usage:read consulta almacenamiento y tráfico, y logs:write envía logs.
Direcciones
Las rutas y los nombres de campo son los de Fivemanage, más una subida de archivos comprimidos propia de FiveDock. También respondemos a las direcciones de medios de Fivemerr.
| Dirección | Scope | Qué hace |
|---|---|---|
| Archivos | ||
| POST /api/v3/file | Scope · files:write | Sube un archivo como multipart/form-data. |
| POST /api/v3/file/base64 | Scope · files:write | Sube un archivo enviado en base64 dentro de un cuerpo JSON. |
| POST /api/v3/file/archive | Scope · files:write | Sube un .zip o .rar y guarda cada archivo que contiene. Solo en FiveDock. |
| GET /api/v3/file | Scope · files:read | Lista tus archivos, página a página. |
| GET /api/v3/file/{id} | Scope · files:read | Lee los detalles de un archivo. |
| DELETE /api/v3/file/{id} | Scope · files:delete | Borra un archivo. |
| Enlaces de subida | ||
| GET /api/v3/file/presigned-url | Scope · files:write | Crea un enlace de subida de un solo uso. Dura 15 minutos, salvo que pidas menos o más. |
| POST /api/v3/file/presigned-url/{ticket} | Scope · Ninguno, el enlace es la credencial | Sube por un enlace creado arriba, sin token. |
| GET /api/v2/presigned-url | Scope · files:write | Crea un enlace de subida a la antigua. Dura 5 minutos, salvo que pidas menos o más. |
| POST /api/v2/presigned-url/{ticket} | Scope · Ninguno, el enlace es la credencial | Sube por un enlace creado a la antigua, sin token. |
| Fivemanage v2 | ||
| POST /api/v2/image | Scope · files:write | Sube una imagen. El archivo va en el campo image o en file. |
| POST /api/v2/video | Scope · files:write | Sube un vídeo. El archivo va en el campo video o en file. |
| POST /api/v2/audio | Scope · files:write | Sube un sonido. El archivo va en el campo audio o en file. |
| Fivemerr | ||
| POST /v1/media/images | Scope · files:write | Sube una imagen. El archivo va en file o data. |
| POST /v1/media/videos | Scope · files:write | Sube un vídeo. El archivo va en file o data. |
| POST /v1/media/audios | Scope · files:write | Sube un sonido. El archivo va en file o data. |
| Logs | ||
| POST /api/logs | Scope · logs:write | Envía un evento de log. |
| POST /api/logs/batch | Scope · logs:write | Envía eventos de log en lote. |
| POST /api/v3/logs | Scope · logs:write | Envía logs a la dirección v3 de Fivemanage. |
Las direcciones de logs aceptan los formatos que envían los clientes de Fivemanage, incluido el logger de Fivemanage de ox_lib (set ox:logger "fivemanage"), y necesitan un token con logs:write. ox_lib guarda la dirección de Fivemanage en imports/logger/providers/fivemanage.lua, así que cambia ahí la línea endpoint por la dirección de FiveDock, también después de cada actualización de ox_lib.
Subir un archivo
Envía POST /api/v3/file como multipart/form-data, con el archivo en la parte file. Todos los demás campos son opcionales.
| Campo | Qué hace |
|---|---|
| file | El archivo en sí. Uno por petición. |
| filename | El nombre con el que se guarda. Por defecto, el nombre de la parte del archivo. |
| path | La carpeta donde cae, como /mdt/mugshots. Se crea al vuelo; por defecto, la raíz. |
| key | Un nombre estable como items/burger.png. El archivo se sirve en un enlace que nunca cambia, y volver a subir con la misma clave lo reemplaza. Si está puesta, manda sobre path y filename. |
| metadata | Un objeto JSON que se guarda con el archivo y se devuelve al leerlo, como el caso o el personaje al que pertenece. Lo que no sea un objeto JSON se guarda como un objeto vacío. |
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"}'Enlaces estables con key
Un archivo con clave se sirve bajo /o/, luego el nombre corto de tu espacio de trabajo y luego la clave. El nombre corto nunca cambia y es también el nombre del bucket en el 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"Subir en base64
POST /api/v3/file/base64 acepta un cuerpo JSON en vez de un formulario. base64 es el archivo, como data URI o base64 a secas, y filename, path, key y metadata funcionan igual que en el formulario. Es el camino corto desde Lua, porque screenshot-basic ya te da una 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)
endEl archivo decodificado puede pesar hasta 16 MiB. Lo que sea más grande va como multipart o por un enlace de subida.
Qué responde una subida
Todas las direcciones de subida responden 200 con la misma forma. Los campos de primer nivel son los que envía Fivemanage; fivedock lleva nuestros 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"
}
}Subir un archivo comprimido
POST /api/v3/file/archive acepta un .zip o .rar en la parte file y guarda cada archivo que contiene. path es la carpeta donde se descomprime, la raíz por defecto.
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"Con keys=true, el valor por defecto, cada archivo pasa a tener clave, y su clave es la carpeta de destino más su ruta dentro del comprimido: path=items y una entrada burger.png dan items/burger.png. Con keys=false, los archivos reciben ids aleatorios y caen en la carpeta de destino más su carpeta dentro del comprimido.
Las carpetas del comprimido se conservan: un archivo con images/burger.png descomprimido en items da items/images/burger.png. Para omitir una carpeta, comprime su contenido en lugar de la carpeta.
{
"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
}
}Las carpetas, los enlaces, __MACOSX/, los archivos ocultos, Thumbs.db y desktop.ini se saltan sin decir nada. Cualquier otro archivo que no se pueda guardar aparece en skipped con un motivo: unsupported_type, too_large, encrypted, unsafe_path, too_many_files, archive_too_large, storage_full, duplicate o failed. La respuesta es 200 si se guardó al menos un archivo, aunque se hayan saltado otros; si no se pudo guardar ninguno, es 422, con la misma lista skipped. Si el comprimido está dañado a mitad, incomplete es true: los archivos anteriores al daño se guardaron y lo que venía después no se leyó.
El comprimido puede pesar hasta 512 MiB, con 5000 archivos como máximo y 4 GiB descomprimido, y cada archivo tiene que ser de un tipo que aceptemos. Se leen RAR 4 y RAR 5; las entradas con contraseña se saltan como encrypted, y los comprimidos en varios volúmenes no están soportados. Lo que no sea zip ni rar recibe 415, y un comprimido que no se puede leer recibe 422. Para conjuntos más grandes, sincroniza por el endpoint S3 con rclone o la CLI de aws.
Enlaces de subida de un solo uso
Un enlace de subida deja que la NUI de un jugador, o cualquier navegador, suba un archivo sin ver nunca tu token. Tu servidor lo crea con GET /api/v3/file/presigned-url y un token con files:write, y luego lo pasa. Todos los parámetros son opcionales, y quien sube no puede cambiar ninguno.
| Parámetro | Qué hace |
|---|---|
| expiresAt | Cuándo deja de funcionar el enlace, en segundos Unix. Por defecto, dentro de 15 minutos (5 en /api/v2/presigned-url), y como mucho 1 hora. |
| singleUse | true hace que el enlace acepte exactamente una subida correcta; después responde 410 Gone. Un intento rechazado, por un tipo equivocado o un archivo demasiado grande, no lo gasta. |
| maxSize | El archivo más grande que acepta el enlace, en bytes, hasta el límite por archivo. Un archivo mayor recibe 413. |
| fileType | image, video o audio. Cualquier otro tipo de archivo recibe 415. |
| contentType | Un tipo exacto, como video/webm. Cualquier otro tipo recibe 415. |
| path | La carpeta donde cae el archivo. |
| key | Guarda el archivo con esta clave, así que recibe un enlace estable y reemplaza lo que hubiera. |
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_…"La respuesta trae el enlace y repite lo que 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 subir, haz POST a presignedUrl como multipart/form-data, con el archivo en el campo file y, si quieres, filename y metadata. No mandes cabecera Authorization. La respuesta es la misma que la 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]"Grabar un clip en la NUI
Un clip de cámara corporal para un MDT: el servidor crea un enlace de un solo uso para un archivo webm, el script de cliente se lo pasa a la NUI, y la NUI graba con MediaRecorder y sube el Blob directo a 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)Crea un enlace por subida y dale una vida corta. Con singleUse=true, quien copie el enlace desde la NUI recibe 410 en cuanto tu clip haya llegado.
Listar, leer y borrar
GET /api/v3/file lista tus archivos, 50 por página salvo que pongas limit, hasta 100. page elige la página, type deja solo image, video o audio, y path una carpeta. Añade el id de un archivo a la dirección para leerlo, o manda DELETE a la misma dirección para borrarlo. Cada archivo trae url y fileUrl: en un archivo con clave, url es su enlace estable y también aparece key; si no, los dos son el enlace /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 }
}Un archivo borrado deja de servirse al instante. Durante 7 días se puede restaurar desde el panel. Borrar necesita un token con files:delete.
Límites y tipos de archivo
El tipo se lee de los bytes del archivo, no de su nombre ni del Content-Type que envías. Se aceptan:
video/webm se acepta, así que un clip de MediaRecorder se sube tal cual. El endpoint S3 también guarda otros tipos y los sirve como descarga.
Navegadores y NUI (CORS)
La API de subida responde a navegadores de cualquier origen, con Access-Control-Allow-Origin: * y sin cookies. Eso incluye las páginas NUI de FiveM, cuyo origen es https://cfx-nui- seguido del nombre del recurso. Cubre /api/v3/file…, /api/v2/… y /v1/media/…. El preflight permite GET, POST y OPTIONS, con las cabeceras Authorization y Content-Type.
CORS decide qué páginas pueden llamar, no quién tiene la clave. Todo lo que llega a los jugadores se puede leer, así que una NUI debería subir por un enlace de un solo uso y no tener nunca un token. Lista y borra desde tu servidor.
Cuándo funciona un enlace
Al momento. El enlace de la respuesta funciona en cuanto la API ha respondido: los archivos se aprueban al subirse, así que no hay cola que esperar. Si moderación retira un archivo más tarde, su enlace responde 404.
Un vídeo se reproduce tal como se subió desde el principio. Las copias optimizadas se añaden cuando termina el procesado, y fivedock.status pasa de pending a ready. No tienes que esperarlo.
Cuando subes encima de una clave, los jugadores reciben el archivo nuevo en unos 5 minutos, porque los enlaces con clave se guardan en caché 5 minutos. El contenido de los enlaces /f/ nunca cambia, así que se guardan en caché un año.
Límites de peticiones
Hoy la API de subida no tiene un límite fijo de peticiones por token. Lo que frena las subidas es el tope de almacenamiento: cuando un espacio de trabajo supera su límite con el tope estricto activado, las subidas reciben 429 hasta que se libera espacio o se sube el tope. La entrega por CDN por encima del tope de tráfico también responde 429.
Espacia los reintentos ante cualquier 429, y si viene Retry-After, espera al menos ese tiempo.
Errores
Un error vuelve con su código de estado y un cuerpo JSON; error y message dicen lo mismo.
{
"error": "that file type is not accepted",
"message": "that file type is not accepted"
}| Estado | Cuándo |
|---|---|
| 400 | La petición está mal formada: sin parte de archivo, base64 que no se decodifica o una clave que rompe las reglas. |
| 401 | No hay token, o no es válido. Una clave de agente fda_live_… también recibe esto. |
| 403 | Al token le falta el scope de esta dirección, o el enlace de subida no es válido o ha caducado. |
| 404 | No hay ningún archivo con ese id en tu espacio de trabajo. |
| 410 | El enlace de un solo uso ya se usó. |
| 413 | El archivo supera el límite de tamaño, o el maxSize del enlace. |
| 415 | El tipo de archivo no se acepta o no coincide con el fileType o el contentType del enlace, o el comprimido no es zip ni rar. |
| 422 | Moderación retiró el archivo y no se puede volver a subir, el comprimido no se puede leer, o no se pudo guardar ningún archivo del comprimido. |
| 429 | El espacio de trabajo supera su tope de almacenamiento. Espacia los reintentos y respeta Retry-After. |
| 503 | Esa parte de la API no está configurada en el servidor al que llamaste. |