CDN API
Upload files to the FiveDash CDN from scripts with upload keys: endpoints, auth, formats, responses, errors and examples in cURL, Lua and JS.
Upload files to the FiveDash CDN from scripts with upload keys: endpoints, auth, formats, responses, errors and examples in cURL, Lua and JS.
The FiveDash CDN keeps your city’s images, videos and sounds on a fast public link. For scripts that upload files on their own (screenshot-basic screenshots, camera photos, clips), there’s a simple API with upload keys: each key uploads files to one folder only. This page is that API’s reference, for script developers.
402.fdm_ and is shown in full only once.Go to CDN → Settings and scroll to Upload keys (/<org>/<server>/cdn/configuracoes).
Click New key. In New upload key, give it a name (e.g. Game camera).
Under Uploads to, pick the Key folder. The key only uploads to that folder: if it leaks, the damage stays there.
Check Can also delete only if the script needs to clean up what it uploaded.
Click Create key. In Key created, click Copy key. Afterwards, only the start and the end are shown. The same dialog has ready-made code in two tabs: screenshot-basic (Lua) and HTTP (cURL), already with the right address.
The Upload keys list shows the name, the folder, Can delete (when it can), how many uploads the key made and when it was used. To cut a key off, use Revoke: scripts using it stop uploading right away, and files already uploaded stay. Each server can have up to 50 keys.
Use the address shown in the Key created dialog. On the CDN’s default address:
https://cdn.fivex.sh/api/v1/upload
| Method | Path | What it does | Key needs |
|---|---|---|---|
POST | /api/v1/upload | Uploads a file to the key’s folder | any key |
DELETE | /api/v1/files/<id> | Deletes a file this same key uploaded | Can also delete |
There’s no base64 upload or presigned URL for scripts: the upload is always a multipart/form-data with the file. (The large multipart uploads the panel does are panel-only.)
| Header | Required | What it is |
|---|---|---|
Authorization | Yes | Bearer fdm_<64 hex characters> |
Content-Type | Yes, on upload | multipart/form-data; boundary=… (cURL, fetch and screenshot-basic build it for you) |
Idempotency-Key | No | 8 to 128 characters: letters, numbers, _, . and -. Repeating the same operation with the same ID within 24 hours doesn’t duplicate the file |
The key sets the server and the folder. You can’t pick another folder in the form.
POST /api/v1/upload
The form must have a single field, the file, named file (or files[]). Other fields make the upload be refused.
| Rule | Value |
|---|---|
| Files per call | 1 |
| Size per call | up to 20 MB (and never above the max size in the Uploads settings section) |
| File name | The name in the form, 1 to 180 characters, without /, \, %, ?, # or leading/trailing spaces. If a file with that name already exists in the folder, -1, -2… is added before the extension |
| File type | The Content-Type of the file part, one of: image/png, image/jpeg, image/webp, image/gif, image/svg+xml, video/mp4, video/webm, video/quicktime, audio/mpeg, audio/ogg, audio/wav, audio/mp4 |
| Content | Must match the declared type. A .png that isn’t a PNG is refused (415) |
| Time | The body must arrive within 2 minutes |
PNG, JPEG and WebP may be optimized on upload, following the Images settings section (quality, max width and metadata removal). GIF, SVG, audio and video stay as uploaded.
200 OK:
{
"ok": true,
"id": "6f1d0c9e-2b7a-4c1e-9f3a-1a2b3c4d5e6f",
"url": "https://cdn.fivex.sh/example-city/screenshots/print-0001.webp",
"file": {
"id": "6f1d0c9e-2b7a-4c1e-9f3a-1a2b3c4d5e6f",
"name": "print-0001.webp",
"folderId": "folder_example",
"kind": "image",
"mime": "image/webp",
"size": 183204,
"width": 1920,
"height": 1080,
"duration": null,
"src": "https://cdn.fivex.sh/example-city/screenshots/print-0001.webp",
"thumb": null,
"optimizedSize": 120544,
"uploadedBy": "Chave: Game camera",
"createdAt": "2026-11-09T18:00:00.000Z",
"starred": false,
"trashedAt": null,
"hits": 0
}
}
| Field | What it is |
|---|---|
id | The file’s ID. Keep it if the script will delete it later |
url | The file’s public link. Always use this one, don’t build the link by hand (the name may have gained a -1) |
file.kind | image, video or audio |
file.size | Bytes uploaded |
file.optimizedSize | Bytes after optimization, when it happened |
file.duration | Seconds, video and audio only |
file.uploadedBy | Chave: <key name> |
The link follows https://cdn.fivex.sh/<cdn-address>/<folders>/<name>. With a custom domain (Ultra plan), the link uses your domain.
DELETE /api/v1/files/<id>
Only works with a key that has Can also delete, and only reaches files this same key uploaded to its folder. The file is deleted right away, without going to the trash, and the link stops responding. Repeating the same DELETE with the same Idempotency-Key answers { "ok": true } again.
curl -X DELETE "https://cdn.fivex.sh/api/v1/files/6f1d0c9e-2b7a-4c1e-9f3a-1a2b3c4d5e6f" \
-H "Authorization: Bearer fdm_xxxxxxxx"
200 OK response:
{ "ok": true }
Errors come as { "ok": false, "code": "…", "message": "…" }, with the message in Portuguese.
| Status | code | Message | When |
|---|---|---|---|
400 | invalid_request | “Envie um formulário multipart válido.” | The body isn’t multipart/form-data |
400 | invalid_request | “Envie um único arquivo por formulário.” | More than one field in the form |
400 | invalid_request | “Envie um arquivo no campo file.” | No file, empty file or larger than 20 MB |
400 | “Dados inválidos.” | File type not accepted or invalid name | |
400 | invalid_request | “A chave de repetição é inválida.” | Idempotency-Key in the wrong format |
401 | invalid_request | “Chave de envio inválida.” | No Authorization, wrong format or a key that doesn’t exist |
401 | invalid_request | “A chave de envio foi revogada.” | The key was revoked in the panel |
402 | invalid_request | “O plano da CDN venceu.” | The CDN plan isn’t active |
403 | invalid_request | “Essa chave não pode apagar arquivos.” | DELETE without Can also delete |
404 | invalid_request | “A pasta da chave não está disponível.” | The key’s folder was deleted or is in the trash |
404 | invalid_request | “Arquivo não encontrado.” | DELETE of a file that isn’t this key’s or doesn’t exist |
405 | invalid_request | “Operação não permitida.” | A method or path that doesn’t exist |
408 | invalid_request | “O envio demorou demais. Tente novamente.” | The body took more than 2 minutes |
409 | invalid_request | “A solicitação já foi usada com outros dados.” | Same Idempotency-Key with another file |
409 | invalid_request | “A chave de repetição expirou. Use uma nova chave.” | Idempotency-Key older than 24 hours |
413 | invalid_request | “A solicitação é grande demais.” / “O arquivo ultrapassa o tamanho permitido.” / “Imagens podem ter até 20 MB.” | File too large |
415 | invalid_request | “O conteúdo não corresponde ao formato informado.” | The content isn’t the declared type |
429 | quota_exceeded | “A franquia de armazenamento ou envios foi atingida.” | The server hit the plan’s limit |
429 | busy | “Há muitos envios em andamento. Tente novamente em instantes.” | Too many uploads at once |
503 | unavailable | “A CDN está indisponível. Tente novamente.” | Temporary failure |
curl -X POST https://cdn.fivex.sh/api/v1/upload \
-H "Authorization: Bearer fdm_xxxxxxxx" \
-H "Idempotency-Key: print-20261109-0001" \
-F "file=@print.png"
For video and audio, set the type on the file part (cURL doesn’t detect all of them):
curl -X POST https://cdn.fivex.sh/api/v1/upload \
-H "Authorization: Bearer fdm_xxxxxxxx" \
-F "file=@clip.mp4;type=video/mp4"
The same code the panel shows in Key created:
exports['screenshot-basic']:requestScreenshotUpload('https://cdn.fivex.sh/api/v1/upload', 'file', {
headers = { ['Authorization'] = 'Bearer fdm_xxxxxxxx' },
encoding = 'webp'
}, function(data)
local res = json.decode(data)
print(res.url) -- https://cdn.fivex.sh/example-city/screenshots/print-….webp
end)
To record the screenshot in a log, send res.url to the server with one of your script’s events and call the logs export there, with image = url.
In a JavaScript server_script, FXServer’s Node 22 already has fetch, FormData and Blob. That way the key stays on the server:
// server.js — the key comes from a convar: set cdn_upload_key "fdm_xxxxxxxx"
const KEY = GetConvar("cdn_upload_key", "");
async function uploadToCdn(bytes, name, type) {
const form = new FormData();
form.append("file", new Blob([bytes], { type }), name);
const response = await fetch("https://cdn.fivex.sh/api/v1/upload", {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Idempotency-Key": `upload-${name}` },
body: form,
});
const result = await response.json();
if (!result.ok) throw new Error(`${response.status} ${result.message}`);
return result.url;
}
exports("uploadToCdn", uploadToCdn);
The API accepts calls from any origin (CORS open for Authorization, Content-Type and Idempotency-Key), so an NUI page can upload directly. Remember: the key is visible on the client.
// In the NUI: uploads a canvas as WebP
async function sendCanvas(canvas: HTMLCanvasElement, key: string) {
const blob: Blob = await new Promise((resolve) => canvas.toBlob((b) => resolve(b!), "image/webp", 0.9));
const form = new FormData();
form.append("file", blob, `photo-${Date.now()}.webp`);
const response = await fetch("https://cdn.fivex.sh/api/v1/upload", {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: form,
});
const result = await response.json();
if (!result.ok) throw new Error(result.message);
return result.url as string;
}
Can I upload to another folder with the same key? No. The folder belongs to the key. Create one key per folder (or per script).
Can I upload base64?
No. Convert it to bytes and send it as multipart/form-data in the file field.
Does the file’s link change if I rename the file or folder in the panel? Yes, the link follows the current path. If you change the CDN’s Address, old links stop responding: update them in your scripts.
I revoked the key. Do the files disappear? No. Files already uploaded stay. Only new uploads with that key are refused.