JavaScript Node.js 18+

OnlyFans API JavaScript SDK

Full JavaScript and Node.js integration for the OnlyFans API. Uses native fetch (Node 18+) with async/await patterns. No extra dependencies required.

Installation

The OnlyFans API uses standard HTTP/REST. In Node.js 18 and later, fetch is available globally — no additional HTTP client is required. For Node.js 16 or earlier, install node-fetch:

Terminal Shell
# Node 16 or earlier only — Node 18+ has fetch built-in
npm install node-fetch

# For ESM usage with node-fetch v3
npm install node-fetch@3
Recommended: Use Node.js 18+ and skip the install entirely. All examples on this page use the native global fetch.

Configuration

All requests require your API key passed as the x-api-key header. Store your key in an environment variable — never hard-code it in source files.

ofapi.js JavaScript
// ofapi.js — shared API client config

const BASE_URL = 'http://157.180.79.226:4024/api/v1';

const headers = {
  'x-api-key': process.env.OFAPI_KEY,
  'Content-Type': 'application/json',
};

async function ofapi(path, options = {}) {
  const res = await fetch(`${BASE_URL}${path}`, {
    ...options,
    headers: { ...headers, ...options.headers },
  });

  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(err.message ?? `HTTP ${res.status}`);
  }

  return res.json();
}

export { ofapi, BASE_URL, headers };
.env Shell
OFAPI_KEY=your_api_key_here

Get Revenue Statistics

Retrieve a full revenue breakdown for any creator on your account — subscriptions, tips, PPV messages, and post unlocks. Ideal for building P&L dashboards and payout calculators.

GET /statistics/overview

revenue.js JavaScript
import { ofapi } from './ofapi.js';

async function getRevenueStats(creatorId) {
  const data = await ofapi(
    `/statistics/overview?creator_id=${creatorId}`
  );
  return data;
}

// Usage
const stats = await getRevenueStats('creator_abc123');
console.log(`Total earnings: $${stats.total_earnings}`);
console.log(`Subscriptions: $${stats.subscriptions}`);
console.log(`Tips: $${stats.tips}`);
console.log(`PPV: $${stats.ppv_messages + stats.ppv_posts}`);
Response JSON
{
  "creator_id": "creator_abc123",
  "total_earnings": 12847.50,
  "subscriptions": 4200.00,
  "tips": 3100.25,
  "ppv_messages": 3847.00,
  "ppv_posts": 1200.25,
  "referrals": 500.00,
  "chargebacks": -0.00,
  "period": "all_time",
  "currency": "USD"
}

List Top Fans

Return a ranked list of your highest-spending fans by total spend. Use this to power VIP fan identification, high-value outreach campaigns, and churn-risk monitoring for big spenders.

GET /stats/fans/top

top-fans.js JavaScript
import { ofapi } from './ofapi.js';

async function getTopFans(creatorId, limit = 10) {
  const data = await ofapi(
    `/stats/fans/top?creator_id=${creatorId}&limit=${limit}`
  );
  return data.fans;
}

// Get top 5 fans for a creator
const fans = await getTopFans('creator_abc123', 5);

fans.forEach((fan, i) => {
  console.log(
    `#${i + 1} ${fan.username} — $${fan.total_spent} total`
  );
});
Response JSON
{
  "fans": [
    {
      "user_id": "fan_001",
      "username": "bigspender99",
      "total_spent": 3420.00,
      "tips_total": 1800.00,
      "ppv_total": 1420.00,
      "subscriptions_total": 200.00,
      "subscriber_since": "2024-03-15",
      "is_active": true
    },
    {
      "user_id": "fan_002",
      "username": "whale_dan",
      "total_spent": 2150.50,
      "tips_total": 950.50,
      "ppv_total": 1100.00,
      "subscriptions_total": 100.00,
      "subscriber_since": "2024-07-02",
      "is_active": true
    }
  ],
  "total_count": 847
}

Track Payouts

Retrieve the full payout transaction history for a creator. Use this to reconcile earnings, automate agency commission splits, and build payout forecasting tools.

GET /payouts/transactions

payouts.js JavaScript
import { ofapi } from './ofapi.js';

async function getPayouts(creatorId, page = 1) {
  const data = await ofapi(
    `/payouts/transactions?creator_id=${creatorId}&page=${page}&limit=20`
  );
  return data;
}

// Calculate total paid out this month
const { transactions } = await getPayouts('creator_abc123');
const now = new Date();
const thisMonth = transactions.filter(t => {
  const d = new Date(t.date);
  return d.getMonth() === now.getMonth()
      && d.getFullYear() === now.getFullYear();
});
const total = thisMonth.reduce((sum, t) => sum + t.amount, 0);
console.log(`Paid out this month: $${total.toFixed(2)}`);
Response JSON
{
  "transactions": [
    {
      "id": "txn_8f3a91",
      "amount": 3240.00,
      "status": "completed",
      "method": "bank_transfer",
      "date": "2026-03-03T09:00:00Z",
      "period_start": "2026-02-24",
      "period_end": "2026-03-02"
    },
    {
      "id": "txn_7c2b14",
      "amount": 2980.50,
      "status": "completed",
      "method": "bank_transfer",
      "date": "2026-02-24T09:00:00Z",
      "period_start": "2026-02-17",
      "period_end": "2026-02-23"
    }
  ],
  "page": 1,
  "total_pages": 8,
  "total_count": 156
}

Get Subscriber Analytics

Pull ranked subscriber data sorted by spend. Combine with fan behavior data to identify high-LTV profiles and build automated retention workflows.

GET /subscribers/top

subscribers.js JavaScript
import { ofapi } from './ofapi.js';

async function getTopSubscribers(creatorId, limit = 20) {
  const data = await ofapi(
    `/subscribers/top?creator_id=${creatorId}&limit=${limit}`
  );
  return data.subscribers;
}

// Flag subscribers who haven't purchased PPV in 14+ days
const subs = await getTopSubscribers('creator_abc123');
const cutoff = Date.now() - 14 * 24 * 60 * 60 * 1000;

const atRisk = subs.filter(s =>
  s.total_spent > 200 &&
  new Date(s.last_purchase_at).getTime() < cutoff
);

console.log(`${atRisk.length} high-value subs at churn risk`);
Response JSON
{
  "subscribers": [
    {
      "user_id": "fan_001",
      "username": "bigspender99",
      "total_spent": 3420.00,
      "subscription_price": 14.99,
      "subscribed_since": "2024-03-15",
      "last_purchase_at": "2026-03-01T14:22:10Z",
      "rebill_count": 24,
      "is_active": true
    }
  ],
  "total": 847
}

Send a Message

Send a direct message to a fan — optionally with a PPV price lock and media attachment. Power your chatting team's automation tools, drip sequences, and re-engagement blasts.

POST /messages/send

send-message.js JavaScript
import { ofapi } from './ofapi.js';

async function sendMessage({ creatorId, fanId, text, ppvPrice, mediaIds }) {
  const body = {
    creator_id: creatorId,
    fan_id: fanId,
    text,
    ...(ppvPrice && { ppv_price: ppvPrice }),
    ...(mediaIds?.length && { media_ids: mediaIds }),
  };

  const result = await ofapi('/messages/send', {
    method: 'POST',
    body: JSON.stringify(body),
  });

  return result;
}

// Send a PPV re-engagement message
const result = await sendMessage({
  creatorId: 'creator_abc123',
  fanId: 'fan_001',
  text: 'Hey! I made something special just for you 🔥',
  ppvPrice: 12,
  mediaIds: ['media_xyz789'],
});

console.log(`Message sent: ${result.message_id}`);
Response JSON
{
  "message_id": "msg_4d9f2a",
  "status": "sent",
  "creator_id": "creator_abc123",
  "fan_id": "fan_001",
  "ppv_price": 12,
  "has_media": true,
  "sent_at": "2026-03-05T11:42:00Z"
}

TypeScript Types

If you are using TypeScript, add these interfaces to a types/ofapi.d.ts file for full type safety across all API calls.

types/ofapi.d.ts TypeScript
/** Revenue overview for a single creator */
export interface RevenueStats {
  creator_id: string;
  total_earnings: number;
  subscriptions: number;
  tips: number;
  ppv_messages: number;
  ppv_posts: number;
  referrals: number;
  chargebacks: number;
  period: string;
  currency: string;
}

/** A single fan ranked by total spend */
export interface TopFan {
  user_id: string;
  username: string;
  total_spent: number;
  tips_total: number;
  ppv_total: number;
  subscriptions_total: number;
  subscriber_since: string;
  is_active: boolean;
}

/** A payout transaction record */
export interface PayoutTransaction {
  id: string;
  amount: number;
  status: 'pending' | 'completed' | 'failed';
  method: string;
  date: string;
  period_start: string;
  period_end: string;
}

/** Outbound message result */
export interface MessageResult {
  message_id: string;
  status: 'sent' | 'queued' | 'failed';
  creator_id: string;
  fan_id: string;
  ppv_price?: number;
  has_media: boolean;
  sent_at: string;
}

Error Handling

All API errors return a JSON body with a message field and a standard HTTP status code. Wrap calls in try/catch and branch on the status code for retries and alerting.

error-handling.js JavaScript
import { BASE_URL, headers } from './ofapi.js';

async function safeApiCall(path, options = {}) {
  try {
    const res = await fetch(`${BASE_URL}${path}`, {
      ...options,
      headers: { ...headers, ...options.headers },
    });

    if (res.status === 401) {
      throw new Error('Invalid API key — check OFAPI_KEY env var');
    }
    if (res.status === 403) {
      throw new Error('Creator not on your account');
    }
    if (res.status === 429) {
      // Respect rate limits — back off and retry
      const retryAfter = res.headers.get('Retry-After') ?? '5';
      await new Promise(r => setTimeout(r, parseInt(retryAfter) * 1000));
      return safeApiCall(path, options); // one retry
    }
    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      throw new Error(err.message ?? `HTTP ${res.status}`);
    }

    return res.json();
  } catch (err) {
    if (err.name === 'TypeError') {
      // Network error — API unreachable
      throw new Error(`Network error reaching OFAPI: ${err.message}`);
    }
    throw err;
  }
}
Status Meaning Action
200 OK Parse and use the response body
400 Bad Request Check required params; read message
401 Unauthorized Verify x-api-key header is correct
403 Forbidden Creator not linked to your API account
429 Rate Limited Back off using Retry-After header
500 Server Error Retry with exponential backoff; contact support