> ## Documentation Index
> Fetch the complete documentation index at: https://docs.onlyfansapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Endpoint Data Sources (/introduction/essentials/endpoint-data-sources)

import {DatabaseIcon, DatabaseZapIcon, ZapIcon} from "lucide-react";
import { Tabs, Tab } from "fumadocs-ui/components/tabs";

We classify every endpoint as one of three types:

<Cards className="grid grid-cols-3 gap-4 @container">
  <Card icon={<DatabaseZapIcon className="text-fd-primary" />} title="Hybrid">
    <span className="font-medium text-fd-primary">We make a live request to OnlyFans and then enriches the response</span>
    with fields from the our database (timestamps, tags, computed classifications, etc.).
  </Card>

  <Card icon={<DatabaseIcon className="text-fd-primary" />} title="Computed">
    <span className="font-medium text-fd-primary">We never call OnlyFans during the request.</span>
    The response is built entirely from data we have stored, synced, or calculated.
  </Card>

  <Card icon={<ZapIcon className="text-fd-primary" />} title="Live passthrough">
    <span className="font-medium text-fd-primary">Anything not listed as Hybrid or Computed.</span>
    The response is straight from OnlyFans, with no enrichment.
  </Card>
</Cards>

## Quick answers to common questions

* `GET /{account}/statistics/overview` → **Live passthrough.** The numbers come straight from OnlyFans.
* `GET /{account}/fans/active` → **Hybrid.** OnlyFans response plus a `lastReplyAt` timestamp on each fan, sourced from the OFAPI database.
* `GET /{account}/transactions` → **Hybrid.** OnlyFans response plus a computed `type` field (`subs`, `tips`, `post`, `chat_messages`, `stream`).

## Hybrid endpoints

These endpoints call OnlyFans live and then enrich the response with extra fields from the OFAPI database. The "Enriched with" column lists exactly what OFAPI adds on top of the OnlyFans payload.

| HTTP  | Path                                         | Enriched by OFAPI                                                                                        |
| ----- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| `GET` | `/{account}/chats`                           | `lastReplyAt` per chat                                                                                   |
| `GET` | `/{account}/chats/{chat_id}/messages`        | `lastReplyAt` per participant                                                                            |
| `GET` | `/{account}/fans/all`                        | `lastReplyAt` per fan                                                                                    |
| `GET` | `/{account}/fans/active`                     | `lastReplyAt` per fan                                                                                    |
| `GET` | `/{account}/fans/expired`                    | `lastReplyAt` per fan                                                                                    |
| `GET` | `/{account}/fans/latest`                     | `lastReplyAt` per fan                                                                                    |
| `GET` | `/{account}/fans/top`                        | `lastReplyAt` per fan                                                                                    |
| `GET` | `/{account}/users/{username}`                | `lastReplyAt` (if the user is a fan)                                                                     |
| `GET` | `/{account}/transactions`                    | Computed `type` (`subs` / `tips` / `post` / `chat_messages` / `stream`), derived from the OF description |
| `GET` | `/{account}/tracking-links`                  | Tags, cached revenue & spender counts, full campaign URL prefixed with the creator's profile URL         |
| `GET` | `/{account}/trial-links`                     | Tags, cached revenue & spender counts, full campaign URL                                                 |
| `GET` | `/{account}/engagement/messages/top-message` | Engagement stats joined onto the message metadata                                                        |

## Computed endpoints

These endpoints never hit OnlyFans during the request. The response is built entirely from data OFAPI has stored, synced, or calculated.

### Smart Links (analytics + management)

| HTTP     | Path                                       | Notes                                                        |
| -------- | ------------------------------------------ | ------------------------------------------------------------ |
| `GET`    | `/smart-links`                             | List smart links                                             |
| `POST`   | `/smart-links`                             | Create a smart link                                          |
| `GET`    | `/smart-links/{smart_link_id}`             | Fetch a single smart link                                    |
| `DELETE` | `/smart-links/{smart_link_id}`             | Delete a smart link                                          |
| `GET`    | `/smart-links/{smart_link_id}/stats`       | Clicks, subs, revenue, spenders aggregated from OFAPI tables |
| `GET`    | `/smart-links/{smart_link_id}/cohort-arps` | ARPS by acquisition cohort                                   |
| `GET`    | `/smart-links/{smart_link_id}/fans`        | Attributed fans with aggregate metrics                       |
| `GET`    | `/smart-links/{smart_link_id}/spenders`    | Fans where spend `> 0`                                       |
| `GET`    | `/smart-links/{smart_link_id}/clicks`      | Raw click rows                                               |
| `GET`    | `/smart-links/{smart_link_id}/conversions` | Raw conversion rows                                          |

### Stored (cache) link inventory

Explicitly free — these never call OnlyFans.

| HTTP  | Path                                      | Notes                          |
| ----- | ----------------------------------------- | ------------------------------ |
| `GET` | `/{account}/stored/tracking-links`        | Cached tracking links          |
| `GET` | `/{account}/stored/trial-links`           | Cached free trial links        |
| `GET` | `/{account}/stored/shared-tracking-links` | Cached shared tracking links   |
| `GET` | `/{account}/stored/shared-trial-links`    | Cached shared free trial links |

### Webhooks

| HTTP     | Path                                                     | Notes                         |
| -------- | -------------------------------------------------------- | ----------------------------- |
| `GET`    | `/{account}/webhooks`                                    | List webhooks                 |
| `POST`   | `/{account}/webhooks`                                    | Create a webhook              |
| `GET`    | `/{account}/webhooks/{webhook_id}`                       | Fetch a single webhook        |
| `PUT`    | `/{account}/webhooks/{webhook_id}`                       | Update a webhook              |
| `DELETE` | `/{account}/webhooks/{webhook_id}`                       | Delete a webhook              |
| `GET`    | `/{account}/webhooks/events`                             | Enum of available event types |
| `GET`    | `/{account}/webhooks/{webhookEventType}/example-payload` | Static payload generator      |

### Data exports

| HTTP     | Path                                   | Notes                      |
| -------- | -------------------------------------- | -------------------------- |
| `GET`    | `/data-exports`                        | List data exports          |
| `POST`   | `/data-exports`                        | Create a data export       |
| `GET`    | `/data-exports/{data_export_id}`       | Fetch a single data export |
| `POST`   | `/data-exports/{data_export_id}/start` | Start a data export        |
| `DELETE` | `/data-exports/{data_export_id}`       | Delete a data export       |
| `POST`   | `/data-exports/{data_export_id}/retry` | Retry a data export        |

### Link tags

Tags live entirely in the OFAPI database — none of these endpoints touch OnlyFans.

| HTTP     | Path                                                              | Notes                                     |
| -------- | ----------------------------------------------------------------- | ----------------------------------------- |
| `GET`    | `/link-tags`                                                      | List every tag in your org                |
| `GET`    | `/{account}/tracking-links/{tracking_link_id}/tags`               | List tags on a tracking link              |
| `POST`   | `/{account}/tracking-links/{tracking_link_id}/tags`               | Add tags to a tracking link               |
| `DELETE` | `/{account}/tracking-links/{tracking_link_id}/tags`               | Remove tags from a tracking link          |
| `GET`    | `/{account}/trial-links/{trial_link_id}/tags`                     | List tags on a free trial link            |
| `POST`   | `/{account}/trial-links/{trial_link_id}/tags`                     | Add tags to a free trial link             |
| `DELETE` | `/{account}/trial-links/{trial_link_id}/tags`                     | Remove tags from a free trial link        |
| `GET`    | `/{account}/shared-tracking-links/{shared_tracking_link_id}/tags` | List tags on a shared tracking link       |
| `POST`   | `/{account}/shared-tracking-links/{shared_tracking_link_id}/tags` | Add tags to a shared tracking link        |
| `DELETE` | `/{account}/shared-tracking-links/{shared_tracking_link_id}/tags` | Remove tags from a shared tracking link   |
| `GET`    | `/{account}/shared-trial-links/{shared_trial_link_id}/tags`       | List tags on a shared free trial link     |
| `POST`   | `/{account}/shared-trial-links/{shared_trial_link_id}/tags`       | Add tags to a shared free trial link      |
| `DELETE` | `/{account}/shared-trial-links/{shared_trial_link_id}/tags`       | Remove tags from a shared free trial link |

### Auth & accounts

The request itself only reads or writes OFAPI rows; the actual OnlyFans login runs asynchronously in a queued job.

| HTTP     | Path                                               | Notes                             |
| -------- | -------------------------------------------------- | --------------------------------- |
| `GET`    | `/whoami`                                          | Identify the current API key      |
| `GET`    | `/accounts`                                        | List connected OF accounts        |
| `DELETE` | `/accounts/{account}`                              | Disconnect an OF account          |
| `POST`   | `/authenticate`                                    | Start an authentication attempt   |
| `GET`    | `/authenticate/{attempt_id}`                       | Poll attempt status               |
| `PUT`    | `/authenticate/{attempt_id}`                       | Submit credentials / 2FA          |
| `POST`   | `/authenticate/{attempt_id}/send-email-to-creator` | Trigger the OF email-code helper  |
| `POST`   | `/authenticate/{account_id}/reauthenticate`        | Re-auth an existing account       |
| `POST`   | `/client-sessions`                                 | Mint a short-lived client session |

### Media upload status

| HTTP  | Path                                      | Notes                            |
| ----- | ----------------------------------------- | -------------------------------- |
| `GET` | `/{account}/media/upload/{upload}/status` | Poll an in-progress media upload |

## Important clarifications

A couple of endpoints sound like they should be Hybrid or Computed, but aren't. Calling them out here to preempt confusion:

<Callout title="These sound calculated, but are Live passthrough" type="warn">
  The following endpoints return numbers and metrics, but the response is a straight passthrough of what OnlyFans returns — OFAPI does not compute or enrich them:

  * `/statistics/overview`
  * `/statistics/total-transactions`
  * `/statistics/subscriber-metrics`
  * `/me/model-start-date`
  * `/me/top-percentage`
  * `/payouts/balances`
  * `/payouts/eligibility`
  * `/payouts/list-earning-statistics`
  * `/payouts/list-payout-requests`
</Callout>

## Media URLs & file downloads

Media files from both platforms — everything served from `https://cdn*.onlyfans.com/` and `https://cdn*.fansly.com/` — get special handling. Once we have our own copy of a file, subsequent requests for it are served from our own CDN at `https://cdn.fansapi.com/` instead of contacting OnlyFans or Fansly.

Two reasons this matters for you:

* **It saves credits.** Once a file is cached, fetching it again does not consume any credits — the upstream platform is never contacted.
* **It's significantly faster.** Our CDN is geographically distributed, so cached files are served from a location close to your end users instead of round-tripping through OnlyFans or Fansly every time.

The two platforms differ in **when** a file enters the cache. OnlyFans caches eagerly (any endpoint that surfaces a CDN URL starts saving a copy); Fansly caches on demand (only an explicit download populates the cache). The sections below spell out both.

<Callout title="Automatic media caching is a team setting" type="info">
  Eager caching on OnlyFans follows the **Automatic media caching** switch in your team settings in the [dashboard](https://app.onlyfansapi.com), which is **on** by default. Turning it off stops us caching media just because a URL appeared in a response; media you download explicitly is still cached on demand. It does not change Fansly behaviour, which is on-demand either way.
</Callout>

### Downloading or scraping media files

<Tabs items={["OnlyFans", "Fansly"]}>
  <Tab value="OnlyFans">
    The endpoints that hand you back actual media bytes — [Download Media](/api-reference/media/download-media-from-the-only-fans-cdn) and the deprecated [Scrape Media](/api-reference/media/deprecated-scrape-media-from-the-only-fans-cdn) — first check whether we already have that file in our own CDN.

    * **If we have it cached** → you get a URL on our own `cdn.fansapi.com`. Download Media returns a `302` redirect to it (most HTTP clients follow redirects automatically; `curl` needs `-L`). Scrape Media returns it as `temporary_url` in the response body. **No credits are charged in this case**, because OnlyFans is never contacted.
    * **If we don't have it cached yet** → we stream the bytes from OnlyFans straight back to you as the response, and at the same time start saving our own copy. The next time anyone asks for that same file, it will be served from our CDN.
  </Tab>

  <Tab value="Fansly">
    [Download Media from the Fansly CDN](/api-reference/fansly/media/download-media-from-the-fansly-cdn) always answers with a `302` redirect — it never streams bytes through us. Where it points depends on whether the file is cached:

    * **If we have it cached** → a `302` to a signed `cdn.fansapi.com` URL, valid for **1 hour**. Fansly is never contacted.
    * **If we don't have it cached yet** → a `302` to the original signed `cdn*.fansly.com` URL, so your download starts immediately, and we queue a background job to save our own copy. The next call for that same file gets the `cdn.fansapi.com` redirect instead.

    Because it is always a redirect, your HTTP client **must follow redirects** (`curl` needs `-L`).

    <Callout title="Pass the signed URL through unchanged" type="warn">
      Fansly CDN URLs are signed and time-limited. On a cache miss we need `Expires`, `Key-Pair-Id` and `Signature` intact to fetch the file — if any are missing the endpoint returns a `422` listing which ones. Fetch the URL from the API and download it promptly rather than storing it and retrying later.
    </Callout>

    Streaming manifests (`.m3u8` and `.mpd`) are redirected but never cached — a manifest is a playlist of separately-signed segment URLs rather than a media file, so a cached copy would be useless.
  </Tab>
</Tabs>

### Endpoints that return media URLs in their response

<Tabs items={["OnlyFans", "Fansly"]}>
  <Tab value="OnlyFans">
    Any endpoint that returns OnlyFans CDN URLs in its response body — List Chat Messages, List Posts, List Vault Media, and so on — follows the same caching pattern, applied per-URL:

    * Each `https://cdn*.onlyfans.com/*` URL in the response is checked against our CDN.
    * **URLs we already have cached** are replaced with the equivalent `https://cdn.fansapi.com/*` URL before the response is returned to you.
    * **URLs we don't have cached yet** are left as the original `https://cdn*.onlyfans.com/*` URL, and we start saving our own copy. Future responses that include the same URL will return the `cdn.fansapi.com` version instead.

    This rewriting is applied per-URL, so a single response can contain any mix of OnlyFans CDN URLs and rewritten `cdn.fansapi.com` URLs depending on what we've already cached.
  </Tab>

  <Tab value="Fansly">
    These Fansly endpoints rewrite `https://cdn*.fansly.com/*` URLs in their response body:

    | HTTP   | Path                                                  |
    | ------ | ----------------------------------------------------- |
    | `GET`  | `/fansly/{fanslyAccount}/chats`                       |
    | `GET`  | `/fansly/{fanslyAccount}/chats/{chat_id}`             |
    | `GET`  | `/fansly/{fanslyAccount}/chats/{chat_id}/messages`    |
    | `POST` | `/fansly/{fanslyAccount}/chats/{chat_id}/messages`    |
    | `GET`  | `/fansly/{fanslyAccount}/account-media/{ids}`         |
    | `GET`  | `/fansly/{fanslyAccount}/followers`                   |
    | `GET`  | `/fansly/{fanslyAccount}/subscribers`                 |
    | `GET`  | `/fansly/{fanslyAccount}/subscription-tiers`          |
    | `GET`  | `/fansly/{fanslyAccount}/top-supporters`              |
    | `GET`  | `/fansly/{fanslyAccount}/top-supporters/{account_id}` |
    | `GET`  | `/fansly/{fanslyAccount}/lists`                       |
    | `GET`  | `/fansly/{fanslyAccount}/lists/{list_id}/items`       |
    | `GET`  | `/fansly/{fanslyAccount}/users/{usernames}`           |

    * **URLs we already have cached** are replaced with a signed `https://cdn.fansapi.com/*` URL, valid for **15 minutes**.
    * **URLs we don't have cached yet** are passed through untouched, as the original signed `cdn*.fansly.com` URL.

    <Callout title="Rewriting does not trigger caching on Fansly" type="warn">
      Unlike OnlyFans, seeing a URL in a response does **not** start saving a copy. A Fansly file only enters our CDN when you explicitly fetch it through [Download Media from the Fansly CDN](/api-reference/fansly/media/download-media-from-the-fansly-cdn). Until then, every response keeps returning the raw Fansly URL.

      The one exception is the avatar of a connected Fansly account, which we cache when the account is connected.
    </Callout>

    Rewriting is per-URL, so a single response can mix both kinds. Treat every media URL as opaque and download it promptly either way — both the raw Fansly URLs and our rewritten ones expire.
  </Tab>
</Tabs>

## Anything not listed here

If an endpoint isn't listed on this page, **treat it as a Live passthrough.** The response is the OnlyFans or Fansly payload, unmodified by OFAPI — apart from the media URL rewriting described above.

{/*
  Maintainer note — to keep this page accurate, in the OFAPI app repo:

  - The Hybrid list is exactly the set of `formatWith(...)` callsites under
  `app/Http/Controllers/`. Run:
    grep -rn "formatWith" app --include="*.php"
  to find them.
  - The Fansly media-URL-rewriting table is the set of controllers that pass
  `FanslyCdnUrlFormatter` to `formatWith(...)`. Run:
    grep -rn "FanslyCdnUrlFormatter" app/Http/Controllers --include="*.php"
  to find them.
  - The Computed list is API controllers (registered in `routes/api.php`) that do
  not inject `ApiOnlyFansClientForAccount`. Run:
    grep -rL "ApiOnlyFansClientForAccount" app/Http/Controllers/Api
  to find candidates.
  */}