Upload images, audio and video
Small files go through one multipart call; videos go straight to the CDN with a three-step TUS flow.
Three media types, two paths: images and audio go through one multipart request; video is too big for that and uploads straight to the CDN.
Images — one multipart call#
Send fileType=image, originalName, and both an original (displayFile) and a thumbnail (thumbnailFile) part. The whole multipart body must stay under 4 MB — compress client-side before sending.
curl -X POST "https://www.dropfans.io/api/external/vault" \
-H "Authorization: Bearer $DROPFANS_API_KEY" \
-F "fileType=image" \
-F "originalName=beach.jpg" \
-F "[email protected]" \
-F "thumbnailFile=@beach_thumb.jpg"import { openAsBlob } from 'node:fs';
const form = new FormData();
form.set('fileType', 'image');
form.set('originalName', 'beach.jpg');
form.set('displayFile', await openAsBlob('beach.jpg'), 'beach.jpg');
form.set('thumbnailFile', await openAsBlob('beach_thumb.jpg'), 'beach_thumb.jpg');
const res = await fetch('https://www.dropfans.io/api/external/vault', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.DROPFANS_API_KEY },
body: form,
});
const { item } = await res.json();import os, requests
HEADERS = {"Authorization": f"Bearer {os.environ['DROPFANS_API_KEY']}"}
res = requests.post(
"https://www.dropfans.io/api/external/vault",
headers=HEADERS,
data={"fileType": "image", "originalName": "beach.jpg"},
files={
"displayFile": open("beach.jpg", "rb"),
"thumbnailFile": open("beach_thumb.jpg", "rb"),
},
)
item = res.json()["item"]The response is { "success": true, "item": { … } }. The item starts PENDING — see Moderation & statuses.
Audio — same call, one file part#
Send fileType=audio with a single file part (≤ 20 MB, else 413) and an optional durationSeconds (capped at 3600 seconds):
curl -X POST "https://www.dropfans.io/api/external/vault" \
-H "Authorization: Bearer $DROPFANS_API_KEY" \
-F "fileType=audio" \
-F "originalName=voice-note.ogg" \
-F "durationSeconds=42" \
-F "[email protected]"Video — three steps via TUS#
Videos (up to 500 MB) never touch our servers directly — you upload straight to the Bunny CDN with the resumable TUS protocol.
Step 1 — start#
curl -X POST "https://www.dropfans.io/api/external/vault/video-upload" \
-H "Authorization: Bearer $DROPFANS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "originalName": "clip.mp4", "fileSize": 104857600 }'{
"videoId": "9f2c…",
"tusEndpoint": "https://video.bunnycdn.com/tusupload",
"libraryId": 572783,
"signature": "…",
"expires": 1755640000000,
"completionToken": "…"
}Keep completionToken — it is valid for 8 hours and step 3 requires it.
Step 2 — TUS upload to the CDN#
Send the file to tusEndpoint with these TUS metadata headers: AuthorizationSignature (= signature), AuthorizationExpire (= expires), VideoId (= videoId), LibraryId (= libraryId). With tus-js-client:
import * as tus from 'tus-js-client';
import fs from 'node:fs';
await new Promise((resolve, reject) => {
const upload = new tus.Upload(fs.createReadStream('clip.mp4'), {
endpoint: creds.tusEndpoint,
chunkSize: 50 * 1024 * 1024, // 50 MB chunks
retryDelays: [0, 1500, 4000, 8000, 15000],
headers: {
AuthorizationSignature: creds.signature,
AuthorizationExpire: String(creds.expires),
VideoId: creds.videoId,
LibraryId: String(creds.libraryId),
},
metadata: { filetype: 'video/mp4', title: 'clip.mp4' },
uploadSize: fs.statSync('clip.mp4').size,
onError: reject,
onSuccess: resolve,
});
upload.start();
});Step 3 — complete#
curl -X POST "https://www.dropfans.io/api/external/vault/video-upload/complete" \
-H "Authorization: Bearer $DROPFANS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "videoId": "9f2c…", "originalName": "clip.mp4", "completionToken": "…" }'A 409 means the CDN has not surfaced the finished upload yet — this is expected, retry on a ladder of roughly 2 s, 4 s, 6 s, 10 s, 15 s, 20 s, 30 s (give up after ~8 attempts). Retry 409 and 5xx only; a 403 means the completion token expired (restart from step 1) and a 413 means the file was over the cap. The call is idempotent — completing twice returns the same item.
Then poll processing#
POST /vault/video-status (up to 50 ids) reports { isReady, isProcessing, isFailed } per video. Poll every 10–15 seconds until isReady or isFailed. Processing is separate from moderation — after isReady, the item still clears review like everything else.
Folders and tags#
- Create folders with
POST /vault/folders({ "name": … }, ≤ 50 chars, duplicate names409); move items withPATCH /vault/{id}/folder; passfolderIdat upload time to file directly. - Tag items with
PATCH /vault/{id}/tags—{ "contentTags": [ … ] }, up to 50 tags of ≤ 64 characters each.
Next: Sell a drop end-to-end or Moderation & statuses.
Questions? [email protected]
