ofapisv1
capability

Updated

OnlyFans Vault Media API

The vault is the creator's asset library, and in this API it is the only place a media id can come from. Nothing in the public API accepts a file inline: feed posts, DMs, PPV previews and scheduled sends all take arrays of numeric vault ids. So the vault read is the index you build everything else on — and the cheapest way to avoid paying 5 credits for a file the creator already has.

Two read routes, no query parameters

GET /api/public/v1/vault/media
GET /api/public/v1/accounts/{accountId}/vault/media

Neither takes a single query parameter. No limit, no offset, no cursor, no type filter, no since. The response is a flat JSON array of every item in that vault, and one call returns all of it:

{
  "data": [
    { "id": 777888999, "type": "photo", "isReady": true, "canView": true, "duration": 0,
      "files": { "full": { "url": "https://cdn3.onlyfans.com/...", "width": 1080, "height": 1920 } } }
  ]
}

This is worth internalising because it inverts the usual advice. Most list endpoints in this API take limit and offset and reward narrow pages; the vault read rewards doing it once. There is no way to ask for "the ten newest" — asking at all means pulling the whole library, for one credit. Filtering, sorting and paging are your job, client-side, after the array lands.

The unscoped route resolves to the token's first active linked account, which is fine for a single-creator token and wrong for an agency managing several creators. If you manage more than one creator, always use the accountId form; the unscoped one silently answers about whichever account happens to sort first.

What an item carries

Field Type Notes
id integer the handle; this is what every other endpoint wants
type string photo, video or audio
isReady boolean false while OnlyFans is still processing the asset
canView boolean whether the creator themself can view it
duration integer seconds for video and audio, 0 for photos
files.full object url, width, height for the full-size rendition

isReady is the one that bites. A file uploaded seconds ago can appear in the vault with isReady: false, and an asset in that state is not something to attach to a drop that goes out immediately. Re-read the vault rather than guessing a sleep duration — a read is one credit regardless of how many items come back, so polling the whole array is still cheaper than one re-upload.

files.full.url is a CDN address, not an identity. Store the id in your own database and resolve the URL at render time from a fresh read; an id stays valid, a URL is a detail of the response you happened to get.

ids are the reference everywhere

Where Field What it does
Scheduled feed post mediaFiles attachments on the post
Direct or scheduled message mediaFiles attachments on the DM
Priced message previews free thumbnails shown before unlock

The same integer works in all three. One vault item can back a feed post, a reply sent through the messaging API, a send dated for next Friday in the scheduled queue, and the locked half of a drop built with the PPV API, simultaneously and with no copy made. Nothing in the API consumes or retires an id when you use it.

Reuse is a 5-credit saving, every time

Here is the economic shape of the whole pipeline:

Action Credits
Read the entire vault 1
Upload one new file 5

An agency reposting last month's best-performing set across four creators' feeds pays 5 credits once and 0 for every subsequent attachment — or 20 credits, if each creator's pipeline uploads its own copy. Over a hundred-asset library recycled monthly that is the difference between a rounding error and a plan upgrade.

There is a subtler version of the same waste. The upload endpoint finds the id of the file it just stored by looking it up in the vault by MD5 hash and byte size. Upload byte-identical content twice and the second call resolves to the same vault item you already had — and still bills 5 credits for the privilege. Deduplication on your side is not an optimisation; it is the only place it can happen.

Keep a local id map

The pattern that falls out of all of the above: hash locally, keep your own table, and only call the API when the hash is genuinely new. A Python collector is the usual home for it; if the creative team needs to see which asset is which, mirror the same table into an Airtable base rather than re-reading the vault from the UI.

import hashlib, httpx

BASE = "https://api.ofapis.com/api/public/v1"
HEADERS = {"Authorization": "Bearer ofapis_sk_..."}

def digest(path: str) -> str:
    h = hashlib.md5()
    with open(path, "rb") as fh:
        for chunk in iter(lambda: fh.read(1 << 20), b""):
            h.update(chunk)
    return h.hexdigest()

def ready_ids(account_id: int) -> set[int]:
    r = httpx.get(f"{BASE}/accounts/{account_id}/vault/media", headers=HEADERS, timeout=60)
    r.raise_for_status()
    return {m["id"] for m in r.json()["data"] if m.get("isReady")}

def resolve(account_id: int, path: str, cache: dict[str, int]) -> int:
    """Return a vault id for `path`, uploading only if we have never seen these bytes."""
    key = digest(path)
    known = cache.get(key)
    if known and known in ready_ids(account_id):   # 1 credit
        return known
    with open(path, "rb") as fh:                    # 5 credits
        r = httpx.post(f"{BASE}/accounts/{account_id}/media", headers=HEADERS,
                       files={"file": (path.split("/")[-1], fh, "video/mp4")}, timeout=120)
    r.raise_for_status()
    cache[key] = r.json()["data"]["mediaId"]
    return cache[key]

Cache the vault read for the duration of a batch instead of calling ready_ids per file, and the whole run costs one credit plus five per genuinely new asset.

Errors on the scoped read

404 ACCOUNT_NOT_FOUND means the account id does not belong to this token — check it before you blame the vault. 409 ACCOUNT_NOT_ACTIVE means the linked account is in some state other than ACTIVE, and is refunded. 422 OF_AUTH_INVALID means the stored OnlyFans session is no longer good; that one is charged, because only 409, 424 and 5xx responses get their credits back. Re-link the account rather than retrying in a loop. The complete list is on API errors, and per-minute ceilings on rate limits.

FAQ

How do I page through a large vault?

You don't — the endpoint takes no query parameters and returns the complete array in one response. Pull it once, hold it in memory or your own store, and page in your UI. The call costs one credit whether the creator has twelve assets or twelve hundred.

Why is a file I just uploaded showing isReady false?

OnlyFans processes media after the bytes are stored, and the vault item exists before processing finishes. Re-read the vault until the flag flips rather than sleeping a fixed interval; attaching an unready asset to a send that fires immediately is the usual cause of a post going out with nothing visible in it.

Can two creators share one vault item?

No. Every vault is scoped to a single linked account, and an id from account 42 is meaningless to account 43. Running the same asset across several creators means one upload per account, each at 5 credits.

What is the difference between the two list routes?

/vault/media resolves to the token's first active linked account; /accounts/{accountId}/vault/media names the account explicitly. They return the same shape. Use the scoped form in anything that handles more than one creator.

Does deleting content from the vault happen over the API?

There is no delete route for vault media in the public API — removal is done in the OnlyFans interface. The API's write side for media is the single upload endpoint; everything else here is read and reference.

Get a free token · Media upload API · Full API reference

Related guides

All guides
Start free — 250 credits, no card
Generate a token and make your first call in minutes.
Get started