Uploading files
Large files & chunked uploads
Split a file into parts, send each part to CDNForge, then complete the upload. The server binds size, MIME type, storage target, and public id to an upload session so clients cannot override those values at the end.
When to use chunked uploads
- Files that are too large to send as one multipart body through a serverless request limit.
- Video and audio from integrations that always prefer chunked (including the official FiveM resource).
- Same-origin uploads when you do not want to depend on storage CORS for a client PUT.
For browser or NUI flows that can PUT directly to storage, presigned URLs remain a valid alternative.
Size limits
| Limit | Value | Notes |
|---|---|---|
| Maximum upload size | 100 MB | Enforced by the API for all upload paths (MAX_UPLOAD_BYTES). |
| Chunk / part size | 4 MB | Returned as partSize from init. Each part must be 1 byte–4 MB. |
| Session TTL | 1 hour | Default. Overridable with server env UPLOAD_SESSION_TTL_SEC. |
3.5 MB is not an API hard limit
POST /api/v1/upload and POST /api/v1/file) accept files up to 100 MB when the request body can reach the API. The common 3.5 MB threshold is a client/platform guidance used by the dashboard helpers and the FiveM resource so uploads stay under typical serverless body limits (~4.5 MB). Prefer chunked (or presigned) once you are near that size.Two API families
- Media —
/api/v1/upload/chunked/*withcnf_img_,cnf_vid_, orcnf_aud_. - Generic files —
/api/v1/file/chunked/*withcnf_fil_only. Bytes are stored as-is (no image compression).
Flow
- Init — declare filename, MIME type, and total size. CDNForge creates a session and returns
uploadId. - Part — upload each chunk as multipart form data with the session id and part number.
- Complete — send only the
uploadId. The session supplies public id, MIME, size, and storage target.
1) Init
curl -X POST https://app.back2rp.org/api/v1/upload/chunked/init \
-H "Authorization: Bearer cnf_vid_your_key" \
-H "Content-Type: application/json" \
-d '{
"filename": "clip.mp4",
"mimeType": "video/mp4",
"size": 18432000
}'{
"uploadId": "V1StGXR8_Z5jdHi6B-myT",
"publicId": "abc123xyz.mp4",
"mimeType": "video/mp4",
"originalName": "clip.mp4",
"storageBucket": "cdnforge-media",
"partSize": 4194304,
"expiresAt": "2026-08-31T23:00:00.000Z"
}Optional fields on init: folder, folderId, and metadata. They are stored on the session and applied when the upload completes.
uploadId is server-generated
uploadId returned by init. Do not invent session ids. Treat publicId, mimeType, and storageBucket from the response as informative; the session remains authoritative on complete.2) Upload parts
Multipart form fields: uploadId, partNumber (starts at 1), and chunk (the binary part).
curl -X POST https://app.back2rp.org/api/v1/upload/chunked/part \
-H "Authorization: Bearer cnf_vid_your_key" \
-F uploadId="$UPLOAD_ID" \
-F partNumber=1 \
-F chunk=@part1.bin{
"ok": true,
"partNumber": 1,
"size": 4194304
}- Use the
partSizefrom init (4 MB) for all but possibly the last part. - Parts that would exceed the declared total size return HTTP 413.
- Invalid or missing chunks return HTTP 400.
3) Complete
Send only the session id. Do not send publicId, mimeType, size, or storageBucket on complete — the API ignores client overrides and uses the upload session.
curl -X POST https://app.back2rp.org/api/v1/upload/chunked/complete \
-H "Authorization: Bearer cnf_vid_your_key" \
-H "Content-Type: application/json" \
-d '{"uploadId":"V1StGXR8_Z5jdHi6B-myT"}'{
"id": "fil_...",
"url": "https://cdn.back2rp.org/u/abc123xyz.mp4",
"publicId": "abc123xyz.mp4",
"size": 18432000,
"mimeType": "video/mp4",
"originalName": "clip.mp4"
}Generic file chunked routes
Same protocol with a file key and the /api/v1/file/chunked/* paths:
POST /api/v1/file/chunked/initPOST /api/v1/file/chunked/partPOST /api/v1/file/chunked/complete
curl -X POST https://app.back2rp.org/api/v1/file/chunked/init \
-H "Authorization: Bearer cnf_fil_your_key" \
-H "Content-Type: application/json" \
-d '{
"filename": "pack.zip",
"mimeType": "application/zip",
"size": 52428800
}'Errors & retries
| Status | Meaning |
|---|---|
| 400 | Invalid part, missing chunk, no parts found, or assembled size mismatch. |
| 404 | Unknown session, or session belonging to another organization. |
| 409 | Session already completed, or file already saved. |
| 410 | Upload session expired. |
| 413 | Part would exceed the size declared at init, or file exceeds 100 MB. |
| 415 | Declared filename/MIME is a blocked type (for example HTML, SVG, or executable). |
| 429 / 5xx | Rate limited or temporary failure — safe to retry the same part. Do not invent a new uploadId. |
The official FiveM resource retries 429/5xx up to three times and keeps at most two uploads in flight. Custom clients should retry transient failures on the same session and part number.
Sessions expire
Pending sessions expire after the TTL (default one hour). Expired sessions return HTTP 410. Start a new init if that happens; previous parts for that session are no longer usable.
