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/uploads
Method & Path
Purpose

GET /

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 /start returns 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).


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 / UploadPart response object exposes the ETag directly (e.g. response.ETag in the JS SDK).

  • fetch / browser: read response.headers.get('ETag') (CORS-allowed by default for S3 presigned URLs that include ETag in Access-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=true query 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". message exports targeting a specific fan require export_fan_id (resolve via Step 2).

Assumption: the lifecycle from uploadedprocessingprocessedexportingexported is 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

Status
Meaning
Client action

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 /start regardless of file size.

  • Presigned URL expiry. If a PUT to S3 fails with a signature/expiry error, abandon the upload and restart from /start. The expired=true query on the list endpoint surfaces uploads whose URLs lapsed before /finish or /export.

  • Concurrent multipart parts. PUTs to parts[].put_url can run in parallel. The parts array sent to /finish does not have to match upload order, but each part integer must match the id from /start.

  • get_url provenance. Pass through the same get_url returned by /start to /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 /export is 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 via GET /uploads and reconcile by id / key before deciding whether to retry. /uploads/{id}/retry is itself idempotent — repeating the call on the same uploaded or processed upload has no additional effect beyond the first.

    Assumption: the idempotency claim for /uploads/{id}/retry is based on its observable behaviour as a "nudge" the worker, not on a verified implementation guarantee.

  • Fan-link verification. Verify before /export so a bad fan link surfaces upfront rather than after a wasted upload-and-export cycle.


  • 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?