Upload media to the vault
Upload a photo, video, gif or audio file to the account's vault, and get back a reference you can attach to a post, message or story.
Upload a photo, video, gif or audio file to the account's vault, and get back a reference you can attach to a post, message or story.
Two ways to call it:
multipart/form-datawith afilepart — raw bytes.application/jsonwith{"source_url": "https://…"}— we fetch the file server-side. Use this when your media already lives somewhere public (Drive, S3, a CDN); it saves you building a multipart request at all.
What happens under the hood. OnlyFans has no single upload endpoint. The web client runs a four-stage pipeline and so do we: POST /api2/v2/upload/signed/create → PUT the bytes straight to the returned pre-signed S3 URL (5 MiB parts for files ≥ 5 MiB) → POST /api2/v2/upload/signed/finish → hand the S3 descriptor to OnlyFans' converter host. You do not have to orchestrate any of that.
Using the result. Put the returned media object into the post/message's mediaFiles array — not media, which OnlyFans silently ignores for freshly uploaded files. Pass the object through whole; trimming it to just processId also silently attaches nothing:
{ "text": "new set 🔥", "mediaFiles": [ { "processId": "0ifuov…", "host": "convert4.onlyfans.com", "thumbId": 1, "name": "IMG_2676.HEIC", "extra": "…" } ], "isScheduled": 1, "scheduledDate": "2026-08-20T12:00:00+00:00" }⚠️ Two things that fail silently, both confirmed on a live account:
- Using
mediainstead ofmediaFiles, or trimming the object down to{processId}, returns200with no media attached. postedAtdoes not schedule anything. In either format (…Zor…+00:00) OnlyFans ignores it and publishes immediately — verified live. Scheduling requires two fields instead:
{ "isScheduled": 1, "scheduledDate": "2026-08-20T12:00:00+00:00" }Always confirm the post actually queued by checking GET /api2/v2/schedules for its id — a post that published instead of queueing looks identical in the create response apart from postedAt.
Media already in the vault is still referenced by plain integer id — list those with GET /api2/v2/vault/media.
An upload does not by itself create a vault item, and that matches OnlyFans exactly. OnlyFans has no upload-to-vault endpoint: its own vault page is browse/organise only (list, hide, folders, attach), and the uploader is wired solely into the post/message composer. The vault entry is created when a post, message or story consumes the processId. So immediately after this call the file will not appear in GET /api2/v2/vault/media, nor in GET /api2/v2/vault/media/processing, nor under GET /api2/v2/vault/media/hash?h={md5}&size={bytes} — that last one is the lookup OnlyFans' own client uses to decide whether a file is already in the vault, and it answers 404 Media Not Found. Publish or schedule with the processId and the vault row appears. This is the same behaviour you get clicking upload in the OnlyFans web app.
Working example: a runnable Python client covering upload, immediate posting and scheduling — including every silent-failure case above — is at theonlyapi-posting-example.zip.
Requires writes to be enabled for the account (PATCH /accounts/{of_user_id}/polling {"allow_of_write_actions": true}), because uploading acts as the creator.
OnlyFans only. The Fansly upload pipeline is not wired yet; a Fansly account returns 501.
(This replaces the long-documented POST /api2/v2/media, which never existed.)
Authorization
apiKey Your CRM panel API key
In: header
Path Parameters
Creator account ID of the connected account (OnlyFans or Fansly). Use GET /accounts to list connected accounts and their IDs.
Header Parameters
Proxy URL — HTTP or SOCKS5 (e.g. http://user:pass@host:port or socks5://user:pass@host:port). Optional on post-connection routes, where it overrides the proxy saved at login time. Required on the connection routes (POST /accounts/login, POST /accounts/login/cookies, POST /accounts/login/verify-otp) for OnlyFans accounts; optional for Fansly.
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
curl -X POST "https://example.com/accounts/string/media" \ -H "Content-Type: application/json" \ -d '{ "source_url": "http://example.com" }'{ "success": true, "media": { "processId": "string", "host": "string", "name": "string", "extra": "string" }, "data": {}}