Upload an image, small video or voice message
API reference

Upload an image, small video or voice message

Uploads one file into the creator's vault as `multipart/form-data`.

View as MarkdownUpdated Aug 19, 2026
POST/api/external/vault

Uploads one file into the creator's vault as multipart/form-data.

Three kinds, three shapes: images need two parts — a pre-compressed displayFile and a thumbnailFile (both JPEG); audio (voice messages) sends one file part (≤20MB, ≤60 min); video sends one file part — but the whole request must stay under ~4MB (a platform body cap rejects bigger requests before the app runs), so for real videos use the three-step TUS flow starting at POST /api/external/vault/video-upload instead.

Every upload enters the same moderation pipeline as a dashboard upload: images are scored immediately, videos asynchronously, audio goes to human review (or auto-approves for trusted creators). The item only shows up in the default vault list once APPROVED.

Authentication

Send the creator's API key as a bearer token: Authorization: Bearer dpfn_…. See Authentication & API keys.

Request body

Content type: multipart/form-data

NameTypeRequiredDescription
fileTypestringRequiredWhat you are uploading.
One of: image, video, audio
originalNamestringRequiredThe file’s name, stored as fileName.
folderIdstringOptionalOptional folder to file the item into.
durationSecondsintegerOptionalAudio only — the voice message length in seconds (capped at 3600).
Max: 3600
displayFilefileOptionalImages only (required): the pre-compressed JPEG display copy.
thumbnailFilefileOptionalImages only (required): the JPEG thumbnail.
filefileOptionalVideo/audio only (required): the media file. Audio ≤20MB; video effectively ≤~4MB here — use the TUS flow for anything bigger.
Image upload (display + thumbnail parts) (form fields)
fileType=image
originalName=beach-set-01.jpg
[email protected]
[email protected]
Voice message (form fields)
fileType=audio
originalName=voice-note.ogg
durationSeconds=42
[email protected]

Responses

200Uploaded. The item starts PENDING moderation (not included in this shape — poll the list with includePending=true, or wait for it to appear in the default list).
NameTypeDescription
successbooleanAlways true.
itemVaultItemUploadedThe new vault item.
Show child attributes
idstringThe new vault item id.
fileNamestringThe originalName you sent.
filePathstringCDN URL of the stored display asset (images/audio) or the Stream playback URL (videos).
thumbnailPathstring | nullThumbnail URL. Null for audio.
fileTypestringMedia kind, echoing the request.
One of: image, video, audio
fileSizeinteger | nullStored size in bytes.
durationSecondsinteger | nullVoice-message length in seconds (audio only, when you sent it). Null otherwise.
bunnyStreamIdstring | nullBunny Stream GUID (video uploads only).
createdAtstringCreation time.
Format: date-time
Example response
{
  "success": true,
  "item": {
    "id": "clxv1a2b30001item",
    "fileName": "beach-set-01.jpg",
    "filePath": "https://cdn.dropfans.io/valeria/vault/1721990000-ab12cd.jpg",
    "thumbnailPath": "https://cdn.dropfans.io/valeria/thumbnails/1721990000-ab12cd.jpg",
    "fileType": "image",
    "fileSize": 482113,
    "durationSeconds": null,
    "bunnyStreamId": null,
    "createdAt": "2026-08-01T10:15:00.000Z"
  }
}

Errors

StatusBodyWhen
400{"error":"Missing required fields: fileType and originalName"}Either required form field is absent.
400{"error":"Only images, videos and audio are allowed"}`fileType` is anything other than image, video or audio.
400{"error":"Missing displayFile or thumbnailFile for image upload"}Image upload without both file parts.
400{"error":"Missing video file"}fileType=video without a `file` part.
400{"error":"Missing audio file"}fileType=audio without a `file` part.
401{"error":"Unauthorized","code":"unauthorized"}Missing or invalid API key.
413{"error":"Audio too large"}Audio file over 20MB.
500{"error":"Failed to upload file"}Storage or moderation kickoff failed — retry later.

Rate limiting

Every response carries the X-RateLimit-Tier header and, on limited tiers, the per-minute and per-day trios — read X-RateLimit-Remaining and X-RateLimit-Reset instead of hardcoding limits. Details in Rate limits.

Code samples

curl -X POST "https://www.dropfans.io/api/external/vault" \
  -H "Authorization: Bearer $DROPFANS_API_KEY" \
  -F "fileType=image" \
  -F "originalName=beach-set-01.jpg" \
  -F "[email protected]" \
  -F "[email protected]"

Notes

The success shape here differs from the video-complete endpoint's item (that one adds moderationStatus, moderationTags and aiEnhanced and omits durationSeconds). OPTIONS on this path is an unauthenticated CORS preflight for browser uploads.

Questions? [email protected]