Vault Media Upload via Onlymonster API
What this doc is: the upload process — the sequence of calls, the branching points, and the operational concerns (polling, retries, idempotency).
What this doc is not: a field reference. For request/response schemas, exact validation rules, error codes, and field-level constraints, see the OpenAPI reference. This document does not duplicate the OpenAPI reference and will not be kept in sync with field-level changes.
Overview
The upload flow lets an OnlyFans creator upload a media file to S3 (via presigned URLs), then export it to the OnlyFans vault as a post or message attachment. Onlymonster API orchestrates the upload but never sees the file bytes — clients PUT directly to S3.
The flow spans six Onlymonster API endpoints plus a direct client→S3 step, all rooted at:
/api/v0/accounts/{account_id}/vault/medias/uploadsGET /
List existing uploads
POST /fans/verify
Resolve a fan link → fan_id (optional, before export)
POST /start
Reserve S3 key + return presigned URL(s)
(client → S3)
PUT the file bytes
POST /finish
Finalize a multipart upload (multipart only)
POST /export
Queue the upload for processing & vault export
POST /{upload_id}/retry
Re-run the export pipeline (from uploaded or processed)
The optional /fans/verify step exists as a preflight: it lets the UI surface a bad fan link to the user immediately, before committing them to a full upload-and-export cycle that would have failed at the end.
Restrictions: only the onlyfans platform is supported. Requests against accounts on other platforms are rejected.
Process Flow
The diagram below shows the happy-path lifecycle. Optional steps are noted inline.
Assumption: the threshold at which
/startreturns the multipart variant instead of the single-PUT variant is a server-side decision and is not exposed through the API. Treat the response shape — not the file size — as the trigger for which upload strategy to use.
Step 1 — List uploads (optional)
Use this to discover existing uploads (e.g. on app start, or to poll status after /export).
The endpoint supports an expired query flag — when true, the response includes uploads whose presigned URLs lapsed before /finish or /export. This is the recovery hook for the URL-expiry edge case (see Edge Cases).
Step 2 — Verify fan link (optional)
Translates an OnlyFans fan profile URL into the fan_id you pass as export_fan_id in Step 6. Calling this before /export lets the UI catch a malformed or unknown fan link upfront, instead of letting the user complete a long upload only to fail at the export step.
Step 3 — Start upload
Reserves an S3 object key and returns presigned URL(s). The response has two distinct shapes — single-PUT or multipart — and the client must inspect which fields are present and branch accordingly. This branch is the core decision point of the flow.
Variant A — single PUT
Detect: response has put_url. Action: issue a single PUT to put_url with the full file body.
Variant B — multipart
Detect: response has upload_id and parts[]. Action: PUT each chunk to its parts[i].put_url, capture each ETag from the response header, then proceed to Step 5 (/finish).
Step 4 — Upload bytes to S3
This step does not involve Onlymonster API. Bytes flow client → S3 directly.
Capturing the ETag
S3 returns the object's ETag in the ETag: HTTP response header on every successful PUT. You will need it as e_tag in Steps 5 and 6.
curl: add-D -to dump headers, then grep:AWS SDK (Node, Python, Go, etc.): the
PutObject/UploadPartresponse object exposes the ETag directly (e.g.response.ETagin the JS SDK).fetch/ browser: readresponse.headers.get('ETag')(CORS-allowed by default for S3 presigned URLs that includeETaginAccess-Control-Expose-Headers).
4a — Single PUT
PUT the full file body to put_url, capture the ETag: header value for Step 6 as e_tag. No /finish call is required for this branch.
4b — Multipart
PUT each chunk to its corresponding parts[i].put_url. PUTs may run concurrently. Collect each ETag and assemble [{ part: i+1, e_tag: <etag> }, ...] for Step 5. Order in the parts array sent to /finish does not have to match upload order, but each part number must match the corresponding parts[].id from the start response.
Assumption: the presigned URL TTL is set server-side and is not exposed through the API. If a PUT fails after a long delay (e.g. expired signature), restart from Step 3. Use the
expired=truequery in Step 1 to surface in-flight uploads whose presigned URLs lapsed.
Step 5 — Finish multipart upload
Skip this step for the single-PUT variant — there is no multipart to assemble.
On success the response is { "e_tag": "<combined-etag>" }. Use that combined ETag as the e_tag field in Step 6.
Step 6 — Export to vault
Queues the uploaded object for processing and posting/messaging on OnlyFans. On success, returns the upload record with status=uploaded and media_id=null. See the OpenAPI reference for the full request/response schema.
Process-decisive notes:
e_tag: from Step 4a (single-PUT ETag) or Step 5 (multipart combined ETag).get_url: pass through the presigned GET URL returned by/start. Substituting any other URL will be rejected.export_type:"post"or"message".messageexports targeting a specific fan requireexport_fan_id(resolve via Step 2).
Assumption: the lifecycle from
uploaded→processing→processed→exporting→exportedis driven by background processing. The transition triggers and timing are not directly observable; treat status as advisory and poll until terminal.
Step 7 — Track status to terminal
Two ways to observe status transitions: subscribe to webhooks (preferred for production) or poll. See webhook event types for the webhook flow. The rest of this section covers polling.
Polling (In case using webhooks is not possible)
Re-call GET /uploads (Step 1) and find the upload by id. The status field drives client behaviour.
Polling cadence
Endpoints are limited to 3 RPS per token, so do not poll in a tight loop. Recommended: exponential backoff starting at 2 seconds, doubling up to a 30-second cap, with optional jitter. In typical conditions, polling for up to several minutes is sufficient. Clients should set a reasonable upper bound on total polling time and surface a timeout to the user if no terminal status is reached. On 429, fall back to the same backoff.
Stop polling as soon as status is exported, failed, or unprocessable.
Status state machine
uploaded
Queued; awaiting processing
Keep polling
processing
The file is being processed in the background
Keep polling
processed
File ready, awaiting OnlyFans export
Keep polling
exporting
Pushing to OnlyFans
Keep polling
exported
Terminal happy. media_id is now set
Stop polling; surface success
failed
Terminal. Background processing error
Surface to user; start a new upload if needed
unprocessable
Terminal unrecoverable. File is corrupt or unsupported
Surface to user; do not retry
Decision flow
Step 8 — Retry export
Re-runs the export pipeline for an upload that has already passed the bytes-uploaded stage. Retry is accepted only when status is uploaded or processed. Calling retry on any other status returns an error.
On success, the upload is re-queued for processing (from uploaded) or re-queued for OnlyFans export (from processed). Resume polling as in Step 7.
Practical use: the typical trigger for retry is a stuck or stalled upload — the client sees uploaded or processed for far longer than expected and chooses to nudge the pipeline. Retry is not a recovery mechanism for failed or unprocessable — those are terminal. A failed upload cannot be recovered through retry; the client must start a new upload from /start.
Edge Cases & Recommendations
5 GiB hard cap. Larger files are rejected at
/start. Most large videos will fall on the multipart path — design the client to handle both response variants of/startregardless of file size.Presigned URL expiry. If a PUT to S3 fails with a signature/expiry error, abandon the upload and restart from
/start. Theexpired=truequery on the list endpoint surfaces uploads whose URLs lapsed before/finishor/export.Concurrent multipart parts. PUTs to
parts[].put_urlcan run in parallel. Thepartsarray sent to/finishdoes not have to match upload order, but eachpartinteger must match theidfrom/start.get_urlprovenance. Pass through the sameget_urlreturned by/startto/export. Substituting any other URL (CDN, proxy, separately signed URL) will be rejected.Network interruption mid single-PUT. No resume protocol — restart from
/start.Idempotency on network timeout. None of
/start,/finish, or/exportis safe to blindly retry after a network timeout — each call may have already created a fresh upload, completed a multipart, or queued a duplicate vault entry. On timeout, re-fetch state viaGET /uploadsand reconcile byid/keybefore deciding whether to retry./uploads/{id}/retryis itself idempotent — repeating the call on the sameuploadedorprocessedupload has no additional effect beyond the first.Assumption: the idempotency claim for
/uploads/{id}/retryis based on its observable behaviour as a "nudge" the worker, not on a verified implementation guarantee.Fan-link verification. Verify before
/exportso a bad fan link surfaces upfront rather than after a wasted upload-and-export cycle.
Related
OpenAPI reference — full schema definitions, validation rules, error codes, and try-it-out console.
Webhook event types — alternative to polling; subscribe to upload status transitions.
Last updated
Was this helpful?