Skip to main content
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",
    filesize: 1024000,      // File size in bytes
    mimeType: "image/jpeg"
  })
})

const data = await response.json()
console.log("Upload ID:", data.mediaUploadId)

// Check headers for upload info
const totalParts = response.headers.get("x-ofauth-upload-total-parts")
const partSize = response.headers.get("x-ofauth-upload-part-size")
Response Headers:
  • x-ofauth-upload-total-parts - Number of chunks needed (1 for small files)
  • x-ofauth-upload-part-size - Size of each chunk (for multi-part uploads)

Step 2: Upload the File

Single-Part Upload (Small Files)

For files that fit in one part (check x-ofauth-upload-total-parts = 1):
const fileBuffer = fs.readFileSync("photo.jpg")

const response = await fetch(`https://api.ofauth.com/v2/access/uploads/${mediaUploadId}`, {
  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)

Multi-Part Upload (Large Files)

For larger files, upload in chunks:
const PART_SIZE = parseInt(response.headers.get("x-ofauth-upload-part-size"))
const totalParts = parseInt(response.headers.get("x-ofauth-upload-total-parts"))

const fileBuffer = fs.readFileSync("video.mp4")

for (let partNumber = 1; partNumber <= totalParts; partNumber++) {
  const start = (partNumber - 1) * PART_SIZE
  const end = Math.min(start + PART_SIZE, fileBuffer.length)
  const chunk = fileBuffer.slice(start, end)
  
  await fetch(
    `https://api.ofauth.com/v2/access/uploads/${mediaUploadId}/parts/${partNumber}`,
    {
      method: "PUT",
      headers: {
        apikey: "YOUR_API_KEY",
        "x-connection-id": "conn_abc123",
        "Content-Type": "video/mp4"
      },
      body: chunk
    }
  )
  
  console.log(`Uploaded part ${partNumber}/${totalParts}`)
}

Step 3: Complete Upload

After all parts are uploaded, finalize:
const response = await fetch("https://api.ofauth.com/v2/access/uploads/complete", {
  method: "POST",
  headers: {
    apikey: "YOUR_API_KEY",
    "x-connection-id": "conn_abc123",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    mediaUploadId: mediaUploadId
  })
})

const result = await response.json()
console.log("Media ready! ID:", result.media.id)
For single-part uploads using PUT /uploads/{mediaUploadId}, the upload is automatically completed. You don’t need to call /complete separately.

Complete Example

Full upload workflow with progress tracking:
const fs = require("fs")

const API_KEY = "YOUR_API_KEY"
const CONNECTION_ID = "conn_abc123"

async function uploadMedia(filePath, mimeType) {
  const headers = {
    apikey: API_KEY,
    "x-connection-id": CONNECTION_ID,
    "Content-Type": "application/json"
  }
  
  // Read file
  const fileBuffer = fs.readFileSync(filePath)
  const filename = filePath.split("/").pop()
  
  console.log(`Uploading ${filename} (${fileBuffer.length} bytes)...`)
  
  // Step 1: Initialize
  const initResponse = await fetch("https://api.ofauth.com/v2/access/uploads/init", {
    method: "POST",
    headers,
    body: JSON.stringify({
      filename,
      filesize: fileBuffer.length,
      mimeType
    })
  })
  
  const { mediaUploadId } = await initResponse.json()
  const totalParts = parseInt(initResponse.headers.get("x-ofauth-upload-total-parts") || "1")
  const partSize = parseInt(initResponse.headers.get("x-ofauth-upload-part-size") || String(fileBuffer.length))
  
  console.log(`Upload ID: ${mediaUploadId}, Parts: ${totalParts}`)
  
  // Step 2: Upload
  if (totalParts === 1) {
    // Single part upload (auto-completes)
    const uploadResponse = await fetch(
      `https://api.ofauth.com/v2/access/uploads/${mediaUploadId}`,
      {
        method: "PUT",
        headers: {
          apikey: API_KEY,
          "x-connection-id": CONNECTION_ID,
          "Content-Type": mimeType
        },
        body: fileBuffer
      }
    )
    
    const result = await uploadResponse.json()
    console.log("✅ Upload complete! Media ID:", result.media?.id || result.mediaUploadId)
    return result
  } else {
    // Multi-part upload
    for (let part = 1; part <= totalParts; part++) {
      const start = (part - 1) * partSize
      const end = Math.min(start + partSize, fileBuffer.length)
      const chunk = fileBuffer.slice(start, end)
      
      await fetch(
        `https://api.ofauth.com/v2/access/uploads/${mediaUploadId}/parts/${part}`,
        {
          method: "PUT",
          headers: {
            apikey: API_KEY,
            "x-connection-id": CONNECTION_ID,
            "Content-Type": mimeType
          },
          body: chunk
        }
      )
      
      console.log(`  Part ${part}/${totalParts} uploaded`)
    }
    
    // Step 3: Complete
    const completeResponse = await fetch("https://api.ofauth.com/v2/access/uploads/complete", {
      method: "POST",
      headers,
      body: JSON.stringify({ mediaUploadId })
    })
    
    const result = await completeResponse.json()
    console.log("✅ Upload complete! Media ID:", result.media?.id)
    return result
  }
}

// Usage
uploadMedia("photo.jpg", "image/jpeg")
uploadMedia("video.mp4", "video/mp4")

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. You have two options: Pass the mediaUploadId string directly to mediaItems - the system resolves it automatically:
const { mediaUploadId } = await initUpload(...)
await uploadFile(mediaUploadId, fileBuffer)
await completeUpload({ mediaUploadId })

// Use mediaUploadId directly - no need to extract vault ID!
await fetch("https://api.ofauth.com/v2/access/posts", {
  method: "POST",
  headers,
  body: JSON.stringify({
    text: "New content! 📸",
    mediaItems: [mediaUploadId]  // "ofauth_upload_abc123..."
  })
})
Upload references are automatically resolved to vault media IDs by the API. This is the simplest approach.

Option 2: Use Vault Media ID

If you need the vault ID (e.g., to store it for later use), extract it from the complete response:
const result = await completeUpload({ mediaUploadId })
const vaultMediaId = result.media.id  // Numeric vault ID

// Use the vault 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

FormatExampleDescription
Vault Media ID12345Direct OnlyFans vault media ID
Vault Media ID (string)"12345"Auto-converted to number
Upload Reference"ofauth_upload_abc123..."Resolved automatically
URL"https://example.com/img.jpg"External HTTP/HTTPS URL
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 Upload Status

Poll for upload status if needed:
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({
    mediaUploadId: mediaUploadId
  })
})

const status = await response.json()
console.log("Status:", status.status)  // "pending", "processing", "ready", "failed"

Error Handling

try {
  const response = await fetch("https://api.ofauth.com/v2/access/uploads/init", {
    method: "POST",
    headers,
    body: JSON.stringify({ filename, filesize, mimeType })
  })
  
  if (!response.ok) {
    const error = await response.json()
    
    switch (response.status) {
      case 400:
        console.error("Invalid file:", 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...
} 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: Always upload media before creating posts or messages. You cannot attach external URLs directly.
Session timeout: Upload sessions expire after a period of inactivity. Start the complete flow and finish promptly.

Next Steps