Saltar al contenido
En esta página

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

  • fd_live_… es un token de API. Lo creas en el panel, en Tokens, y eliges sus scopes. Mándalo como Authorization: fd_live_… o Authorization: Bearer fd_live_…. Sirve para esta API, para la API de logs y, como par de claves, para el endpoint S3.
  • fda_live_… es una clave de agente. Va en server.cfg como set fivedock_key para el recurso fivedock, y en ningún otro sitio. La API de subida no la acepta.
curl
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.

Direcciones de la API de subida
DirecciónScopeQué hace
Archivos
POST /api/v3/fileScope · files:writeSube un archivo como multipart/form-data.
POST /api/v3/file/base64Scope · files:writeSube un archivo enviado en base64 dentro de un cuerpo JSON.
POST /api/v3/file/archiveScope · files:writeSube un .zip o .rar y guarda cada archivo que contiene. Solo en FiveDock.
GET /api/v3/fileScope · files:readLista tus archivos, página a página.
GET /api/v3/file/{id}Scope · files:readLee los detalles de un archivo.
DELETE /api/v3/file/{id}Scope · files:deleteBorra un archivo.
Enlaces de subida
GET /api/v3/file/presigned-urlScope · files:writeCrea 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 credencialSube por un enlace creado arriba, sin token.
GET /api/v2/presigned-urlScope · files:writeCrea 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 credencialSube por un enlace creado a la antigua, sin token.
Fivemanage v2
POST /api/v2/imageScope · files:writeSube una imagen. El archivo va en el campo image o en file.
POST /api/v2/videoScope · files:writeSube un vídeo. El archivo va en el campo video o en file.
POST /api/v2/audioScope · files:writeSube un sonido. El archivo va en el campo audio o en file.
Fivemerr
POST /v1/media/imagesScope · files:writeSube una imagen. El archivo va en file o data.
POST /v1/media/videosScope · files:writeSube un vídeo. El archivo va en file o data.
POST /v1/media/audiosScope · files:writeSube un sonido. El archivo va en file o data.
Logs
POST /api/logsScope · logs:writeEnvía un evento de log.
POST /api/logs/batchScope · logs:writeEnvía eventos de log en lote.
POST /api/v3/logsScope · logs:writeEnví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.

Campos de subida
CampoQué hace
fileEl archivo en sí. Uno por petición.
filenameEl nombre con el que se guarda. Por defecto, el nombre de la parte del archivo.
pathLa carpeta donde cae, como /mdt/mugshots. Se crea al vuelo; por defecto, la raíz.
keyUn 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.
metadataUn 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"}'

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.

URL
https://cdn.fivedock.dev/o/{workspace}/{key}
https://cdn.fivedock.dev/o/my-city/items/burger.png
  • Una clave tiene de 1 a 512 caracteres, en segmentos separados por /. Sin segmentos vacíos, . ni .., y sin / al principio.
  • Se permiten letras, dígitos y . _ - ~ @ + : ( ) , = !. Una clave con cualquier otra cosa, como un espacio, se rechaza con 400.
  • Subir a una clave ocupada reemplaza el archivo detrás del enlace. El archivo anterior se conserva 7 días y después se purga, como tras un borrado.
  • Los enlaces con clave se sirven con Cache-Control: public, max-age=300, stale-while-revalidate=60 y un ETag, así que un archivo reemplazado llega a los jugadores en unos 5 minutos y uno sin cambios se revalida con un 304 barato.
  • En el panel, un archivo con clave aparece en la carpeta de su clave: items/burger.png está en /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"

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

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

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 y data.url son el enlace que debes usar. En una subida con clave, es el enlace estable /o/.
  • fivedock.fileUrl es el enlace /f/ propio del archivo. Su contenido nunca cambia y se guarda en caché un año.
  • fivedock.key es la clave, cuando el archivo tiene una.
  • fivedock.deduplicated es true cuando esos mismos bytes ya estaban guardados, así que no se facturó nada nuevo.
  • fivedock.status es pending mientras se hacen las copias optimizadas y ready cuando están listas. El enlace funciona en los dos casos.

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

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

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ámetros del enlace de subida
ParámetroQué hace
expiresAtCuá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.
singleUsetrue 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.
maxSizeEl archivo más grande que acepta el enlace, en bytes, hasta el límite por archivo. Un archivo mayor recibe 413.
fileTypeimage, video o audio. Cualquier otro tipo de archivo recibe 415.
contentTypeUn tipo exacto, como video/webm. Cualquier otro tipo recibe 415.
pathLa carpeta donde cae el archivo.
keyGuarda el archivo con esta clave, así que recibe un enlace estable y reemplaza lo que hubiera.
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_…"

La respuesta trae el enlace y repite lo que 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 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.

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

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

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

  • Un archivo puede pesar hasta 512 MiB.
  • Una subida JSON en base64 puede llevar hasta 16 MiB de archivo decodificado.
  • Un comprimido puede pesar hasta 512 MiB, con 5000 archivos como máximo y 4 GiB descomprimido.
  • Una petición de subida puede durar hasta 5 minutos.

El tipo se lee de los bytes del archivo, no de su nombre ni del Content-Type que envías. Se aceptan:

  • Imágenes: PNG, JPEG, WebP, AVIF y GIF.
  • Vídeo: MP4, WebM y QuickTime (.mov).
  • Audio: MP3, OGG y WAV.

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.

JSON
{
  "error": "that file type is not accepted",
  "message": "that file type is not accepted"
}
Códigos de estado de error
EstadoCuándo
400La petición está mal formada: sin parte de archivo, base64 que no se decodifica o una clave que rompe las reglas.
401No hay token, o no es válido. Una clave de agente fda_live_… también recibe esto.
403Al token le falta el scope de esta dirección, o el enlace de subida no es válido o ha caducado.
404No hay ningún archivo con ese id en tu espacio de trabajo.
410El enlace de un solo uso ya se usó.
413El archivo supera el límite de tamaño, o el maxSize del enlace.
415El 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.
422Moderació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.
429El espacio de trabajo supera su tope de almacenamiento. Espacia los reintentos y respeta Retry-After.
503Esa parte de la API no está configurada en el servidor al que llamaste.