Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.ofauth.com/llms.txt

Use this file to discover all available pages before exploring further.

Learn how to upload media to the OnlyFans vault so you can use it in posts and messages.

Prerequisites

You have a connected OnlyFans account with a valid connectionId

Upload Flow Overview

Uploading media is a multi-step process:

Step 1: Initialize Upload

Tell OFAuth about the file you want to upload:
const response = await fetch("https://api.ofauth.com/v2/access/uploads/init", {
  method: "POST",
  headers: {
    apikey: "YOUR_API_KEY",
    "x-connection-id": "conn_abc123",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    filename: "photo.jpg",
    size: 1024000,
    contentType: "image/jpeg"
  })
})

const upload = await response.json()
console.log("Upload ID:", upload.mediaUploadId)
console.log("Parts:", upload.totalParts)
Request body:
FieldTypeRequiredDescription
filenamestringYesOriginal file name, including extension
sizenumberYesFile size in bytes
contentTypestringYesMIME type, such as image/jpeg or video/mp4
vaultUploadobjectNoOptions for automatic vault upload workflows
Example response:
{
  "mediaUploadId": "upload:550e8400-e29b-41d4-a716-446655440000",
  "uploadType": "multipart",
  "totalParts": 2,
  "partSize": 5242880,
  "expiresAt": "2026-04-29T10:18:48.831Z",
  "parts": [
    {
      "partNumber": 1,
      "uploadUrl": "https://api.ofauth.com/v2/access/uploads/upload%3A550e8400-e29b-41d4-a716-446655440000/parts/1",
      "byteRange": {
        "start": 0,
        "end": 5242879
      }
    },
    {
      "partNumber": 2,
      "uploadUrl": "https://api.ofauth.com/v2/access/uploads/upload%3A550e8400-e29b-41d4-a716-446655440000/parts/2",
      "byteRange": {
        "start": 5242880,
        "end": 6825498
      }
    }
  ],
  "completeUrl": "https://api.ofauth.com/v2/access/uploads/complete"
}
Response fields:
FieldTypeDescription
mediaUploadIdstringUpload session ID. You can later pass this value directly in mediaItems.
uploadType"single" or "multipart"Whether the upload uses one request or multiple part requests.
totalPartsnumberNumber of file parts to upload.
partSizenumberMaximum bytes per part.
expiresAtstringISO timestamp when the upload session expires.
partsarrayOrdered upload steps. Upload each byteRange to its matching uploadUrl.
completeUrlstringURL to call after multipart uploads. Omitted for single-part uploads.
The response also includes x-ofauth-upload-total-parts and x-ofauth-upload-part-size headers for backward compatibility. Prefer the response body for new integrations.

Step 2: Upload the File

Single-Part Upload (Small Files)

For files that fit in one part (upload.uploadType is "single"), upload the whole file to the first uploadUrl:
const fileBuffer = fs.readFileSync("photo.jpg")
const [part] = upload.parts

const response = await fetch(part.uploadUrl, {
  method: "PUT",
  headers: {
    apikey: "YOUR_API_KEY",
    "x-connection-id": "conn_abc123",
    "Content-Type": "image/jpeg"
  },
  body: fileBuffer
})

// Single-part upload auto-completes, returns media info directly
const result = await response.json()
console.log("Media ID:", result.media?.id)
Single-part PUT response:
{
  "mediaUploadId": "upload:550e8400-e29b-41d4-a716-446655440000",
  "media": {
    "id": 12345,
    "type": "photo",
    "files": {}
  }
}

Multi-Part Upload (Large Files)

For larger files, upload each parts[] entry. Slice the file using the provided byteRange, then send that chunk to the entry’s uploadUrl:
const fileBuffer = fs.readFileSync("video.mp4")

for (const part of upload.parts) {
  const chunk = fileBuffer.slice(part.byteRange.start, part.byteRange.end + 1)
  
  const response = await fetch(part.uploadUrl, {
    method: "PUT",
    headers: {
      apikey: "YOUR_API_KEY",
      "x-connection-id": "conn_abc123",
      "Content-Type": "video/mp4"
    },
    body: chunk
  })

  if (!response.ok) {
    throw new Error(`Part ${part.partNumber} failed: ${await response.text()}`)
  }
  
  console.log(`Uploaded part ${part.partNumber}/${upload.totalParts}`)
}
Multipart part PUT response:
{
  "mediaUploadId": "upload:550e8400-e29b-41d4-a716-446655440000",
  "partNumber": 1,
  "etag": "\"abc123\""
}

Step 3: Complete Upload (Multi-Part Only)

For multi-part uploads, call completeUrl after all chunks are uploaded:
const response = await fetch(upload.completeUrl, {
  method: "POST",
  headers: {
    apikey: "YOUR_API_KEY",
    "x-connection-id": "conn_abc123",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    mediaUploadId
  })
})

const result = await response.json()
console.log("Use this in mediaItems:", result.mediaUploadId)
Request body:
{
  "mediaUploadId": "upload:550e8400-e29b-41d4-a716-446655440000"
}
Response body:
{
  "mediaUploadId": "upload:550e8400-e29b-41d4-a716-446655440000"
}
Use the returned mediaUploadId in a post or message:
{
  "text": "New video",
  "mediaItems": ["upload:550e8400-e29b-41d4-a716-446655440000"]
}
Single-part uploads auto-complete. When uploadType is "single", the PUT response returns the media directly. Do not call /complete.

Minimal JavaScript Multipart Example

import { readFileSync } from "node:fs"

const API_KEY = "YOUR_API_KEY"
const CONNECTION_ID = "conn_abc123"
const CONTENT_TYPE = "video/mp4"
const FILENAME = "video.mp4"
const file = readFileSync(FILENAME)

const jsonHeaders = {
  apikey: API_KEY,
  "x-connection-id": CONNECTION_ID,
  "Content-Type": "application/json"
}

const initResponse = await fetch("https://api.ofauth.com/v2/access/uploads/init", {
  method: "POST",
  headers: jsonHeaders,
  body: JSON.stringify({
    filename: FILENAME,
    size: file.length,
    contentType: CONTENT_TYPE
  })
})

if (!initResponse.ok) throw new Error(await initResponse.text())

const upload = await initResponse.json()

if (upload.uploadType !== "multipart") {
  throw new Error("This example expects a multipart upload. Use a larger file.")
}

for (const part of upload.parts) {
  const chunk = file.subarray(part.byteRange.start, part.byteRange.end + 1)

  const partResponse = await fetch(part.uploadUrl, {
    method: "PUT",
    headers: {
      apikey: API_KEY,
      "x-connection-id": CONNECTION_ID,
      "Content-Type": CONTENT_TYPE
    },
    body: chunk
  })

  if (!partResponse.ok) {
    throw new Error(`Part ${part.partNumber} failed: ${await partResponse.text()}`)
  }
}

const completeResponse = await fetch(upload.completeUrl, {
  method: "POST",
  headers: jsonHeaders,
  body: JSON.stringify({ mediaUploadId: upload.mediaUploadId })
})

if (!completeResponse.ok) throw new Error(await completeResponse.text())

const result = await completeResponse.json()

console.log({
  mediaItems: [result.mediaUploadId]
})

Supported Formats

TypeFormatsMax Size
ImagesJPEG, PNG, GIF, WebP~50MB
VideosMP4, MOV~5GB
AudioMP3, M4A~50MB
Check OnlyFans’ current guidelines for exact size limits, as they may change.

Using Uploaded Media

Once uploaded, use the media in posts or messages.

Use Upload Reference Directly

Pass the mediaUploadId string directly to mediaItems - the system resolves it automatically:
const { mediaUploadId } = result

await fetch("https://api.ofauth.com/v2/access/posts", {
  method: "POST",
  headers,
  body: JSON.stringify({
    text: "New content! 📸",
    mediaItems: [mediaUploadId]  // e.g. "upload:550e8400-e29b-41d4-a716-446655440000"
  })
})
Upload references are automatically resolved to vault media IDs by the API. This is the simplest approach.

Use Vault Media ID

If your upload workflow returns media, you can use the numeric vault media ID instead:
const vaultMediaId = result.media.id

await fetch("https://api.ofauth.com/v2/access/posts", {
  method: "POST",
  headers,
  body: JSON.stringify({
    text: "New content! 📸",
    mediaItems: [vaultMediaId]  // 12345
  })
})

What mediaItems Accepts

See the mediaItems reference.
Single-use: Upload references are consumed when used. Once you include a mediaUploadId in a post or message, it cannot be reused. If you need to use the same media multiple times, store the vault media ID instead.

Check for Existing Uploaded Media

Use /uploads/check to see if media with a known upload ETag and size already exists in the vault:
const response = await fetch("https://api.ofauth.com/v2/access/uploads/check", {
  method: "POST",
  headers: {
    apikey: "YOUR_API_KEY",
    "x-connection-id": "conn_abc123",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    etag: "\"abc123\"",
    size: 1024000
  })
})

const result = await response.json()
console.log("Exists:", result.exists)
console.log("Media:", result.media)
Request body:
FieldTypeRequiredDescription
etagstringYesETag returned by a previous upload response
sizenumberYesUploaded file size in bytes
Response body:
{
  "exists": true,
  "media": {
    "id": 12345,
    "type": "photo",
    "files": {}
  }
}

Error Handling

try {
  const response = await fetch("https://api.ofauth.com/v2/access/uploads/init", {
    method: "POST",
    headers,
    body: JSON.stringify({ filename, size: fileBuffer.length, contentType: mimeType })
  })
  
  if (!response.ok) {
    const error = await response.json()
    
    switch (response.status) {
      case 400:
        console.error("Invalid upload request:", error.error || error.message)
        break
      case 413:
        console.error("File too large")
        break
      case 415:
        console.error("Unsupported file type")
        break
      default:
        console.error("Upload failed:", error)
    }
    return
  }
  
  // Continue with upload...
  const upload = await response.json()
  for (const part of upload.parts) {
    // Upload each byte range to part.uploadUrl
  }
} catch (err) {
  console.error("Network error:", err)
}

Tips & Best Practices

Retry failed chunks: If a chunk upload fails, you can retry just that chunk without restarting the entire upload.
Upload to vault first for reuse: Vault uploads give you a media ID or upload reference that you can attach to posts and messages.
Session timeout: Upload sessions expire after a period of inactivity. Start the complete flow and finish promptly.

Next Steps

Create a Post

Use uploaded media in posts

Send a Message

Send media in direct messages