ofapisv1
integration

Updated

OnlyFans + Power Automate

There is no OnlyFans connector in Power Automate, so the route in is the generic HTTP action. That is enough — ofapis is plain Bearer-auth REST — but the action is premium, and the rest of this page assumes that is sorted. The worked example is the one most Microsoft-shop teams want: a scheduled cloud flow that pulls the full subscriber roster every morning and writes a dated snapshot to a SharePoint list or an Excel table, so growth and churn chart themselves without anyone exporting a CSV. It is the Microsoft-shop version of automating the account instead of logging in to it.

The premium licence, said plainly

The HTTP action — and the Azure Key Vault connector — sit in the premium tier, so the flow needs a Power Automate Premium per-user or per-flow licence. Without one it saves fine and fails at run time. A custom connector is also premium, and the wizard expects a Swagger 2.0 definition, so the OpenAPI 3 document at https://api.ofapis.com/v3/api-docs/public would need converting first. For one API with one header, the raw HTTP action is less work. If the licence is the thing standing in the way rather than a line item you already pay, the identical daily export runs for nothing as an Apps Script in Google Sheets — you lose the SharePoint write and the Teams alert, and that is the whole difference.

Keep the token in Key Vault, fetch it with Get secret, and turn on the HTTP action's Settings → Secure Inputs and Secure Outputs: the first keeps the Authorization header out of run history, the second keeps the fan records out. See authentication.

Flow skeleton

Step Action Setting
1 Recurrence Day / 1, 06:00 local
2–3 Initialize variable offset Integer 0; hasMore Boolean true
4 Scope "Pull subscribers" holds steps 5–9
5 Do until @equals(variables('hasMore'), false)
6 HTTP the paged GET
7 Parse JSON schema below
8 Apply to each over data.list
9 Set variable offset, hasMore
10 Scope "On failure" Configure run after

The paged GET

Reads take limit (default 20, clamped server-side to 1–100) and offset (default 0) — ask for the maximum:

https://api.ofapis.com/api/public/v1/accounts/42/subscribers?type=active&limit=100&offset=@{variables('offset')}

Headers: Authorization = Bearer @{body('Get_secret')?['value']} and Content-Type = application/json. The type parameter takes active, expired or all; anything else is silently treated as active rather than rejected, so a typo gives wrong data, not an error. Two exceptions before copying this pattern elsewhere: /accounts/{id}/lists and /accounts/{id}/lists/{listId}/users default to limit=10, and chat history pages on the cursor olderThanMessageId. Full route list in the API documentation.

Parse JSON, and the null that breaks it

Every success body is {"data": …}; every error body is {"error": {"code", "message"}}. Paste a schema rather than using Generate from sample.

{
  "type": "object",
  "properties": {
    "data": {
      "type": "object",
      "properties": {
        "list": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "id": { "type": "integer" },
              "username": { "type": "string" },
              "name": { "type": ["string", "null"] },
              "avatar": { "type": ["string", "null"] },
              "subscribedOn": { "type": "boolean" }
            }
          }
        },
        "hasMore": { "type": "boolean" },
        "nextOffset": { "type": "integer" }
      }
    }
  }
}

That is the whole subscriber record — id, username, name, avatar, subscribedOn. There is no spend or earnings field, so do not design a snapshot column for one; the fan fields the API exposes are identity and subscription state and nothing more. The ["string", "null"] unions matter: Generate from sample infers "type": "string" from a page where every fan has an avatar, and the first fan without one fails validation three weeks later.

Apply to each, and why not to parallelise it

Point Apply to each at body('Parse_JSON')?['data']?['list'] and put Create item (SharePoint) or Add a row into a table (Excel Online) inside, with a SnapshotDate column of @{utcNow('yyyy-MM-dd')}.

Leave Concurrency Control off. Power Automate variables are not safe under concurrent iteration — a Set variable inside a parallel Apply to each is wrong intermittently rather than every time, the worst kind of bug to chase. Keep the loop sequential and let the Do until carry the paging. After it, set the next page:

offset   @{body('Parse_JSON')?['data']?['nextOffset']}
hasMore  @{body('Parse_JSON')?['data']?['hasMore']}

Do until defaults to a Count of 60 and a Timeout of PT1H — at 100 records a page, a 6,000-subscriber cap. Raise the count for a bigger roster, because a Do until that hits either limit exits quietly with a partial snapshot instead of failing.

Retry policy, set per action and on purpose

The HTTP action's Settings → Retry Policy defaults to exponential interval with four retries. That is wrong for half of what you do here, because an ofapis retry is not always free.

Status Credits Retry?
401, 402, 403, 429 refused before metering — free 429 yes, the rest no
409, 424, 5xx debited then refunded yes
400, 404, 413, 415, 422 charged, not refunded no

For the paged GET, Fixed Interval with Count 3 and Interval PT20S is sensible: a 429 carries Retry-After in seconds, and the platform honours that header on 429 and 503 rather than using your interval. For any POST, set the policy to None and add an Idempotency-Key header instead — a replay returns the original response with Idempotency-Replayed: true and is not charged twice, while a concurrent duplicate gets 409 IDEMPOTENCY_CONFLICT. A blind retry against a 400 or a 422 buys the same error four more times and pays for each. See rate limits and error codes.

Scope plus Configure run after

Wrap steps 5–9 in a Scope called Pull subscribers. Add a second Scope after it, open Configure run after, tick has failed, is skipped and has timed out, and post to Teams from inside with the detail that makes the alert actionable:

Status:  @{outputs('HTTP')?['statusCode']}
Code:    @{outputs('HTTP')?['body']?['error']?['code']}
Message: @{outputs('HTTP')?['body']?['error']?['message']}
Request: @{outputs('HTTP')?['headers']?['X-Request-Id']}

X-Request-Id is echoed on every response — yours if you sent one, otherwise generated — and it is the most useful thing to quote in a support ticket. The error expressions only resolve for the gateway's own refusals: 401, 402, 403, 429 and the idempotency 409. Anything the API itself rejects — 400, 404, 422, 424 — comes back as a bare string with no envelope and no code, so add @{outputs('HTTP')?['body']} to the message as a fallback or half your alerts will read Code: and nothing else.

Branch on 403 NO_ACTIVE_SUBSCRIPTION, 402 INSUFFICIENT_CREDITS and the 422 raised when no OnlyFans account is linked to the token, all of which need a human, versus 429, which needs patience. Ceilings are per token per minute — 60 on Free, 100 on Starter, 500 on Pro — and a 6,000-fan snapshot is 60 calls, so a daily run has headroom on any plan. See pricing, the subscribers API, and CRM sync.

FAQ

Does the HTTP action really need a premium licence?

Yes. The generic HTTP action is a premium connector, so the flow needs a Power Automate Premium per-user or per-flow licence. A custom connector built from the ofapis spec is premium too, so there is no standard-tier route to this API.

What schema should Parse JSON use for a subscriber page?

Model the envelope: an object with data, holding list (records of id, username, name, avatar, subscribedOn) plus hasMore and nextOffset. Declare name and avatar as ["string", "null"] — a sample-generated schema types them as strings and fails on the first fan without an avatar.

How do I page past the first 100 subscribers?

Wrap the HTTP action in a Do until that stops when hasMore is false, and at the end of each iteration set offset from data.nextOffset. Raise the Do until Count above its default of 60 if the roster exceeds 6,000, since the loop exits silently at the limit.

Should the HTTP action retry automatically?

For reads, yes — three fixed-interval tries is fine, and the platform respects Retry-After on a 429. For writes, set the policy to None and use an Idempotency-Key header, because a 400, 404, 413, 415 or 422 is charged on every attempt and will never succeed by repetition.

How do I catch an expired OnlyFans session?

Put the paging in a Scope, add a second Scope whose Configure run after includes has failed, and branch on outputs('HTTP')?['statusCode'] inside it. A 424 is an expired OnlyFans session and a 422 is an account that is not linked at all; both need re-linking rather than a retry, so alert a human and stop. Read the raw body rather than body.error.code for these — neither one carries the error envelope.

Related: Pipedream · Zapier · Google Sheets · all integrations

Related guides

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