ofapisv1
solution

Updated

OnlyFans Mass Message Tool

This is the build guide for the OnlyFans mass message tool your chatters will actually click — the endpoints, the error states and, above all, the arithmetic. Writes in this API cost 1 to 5 credits a call depending on the path, but a broadcast is charged per recipient: one send to 3,000 fans is a single HTTP call and about 3,000 credits. A composer that hides that number until after the click will burn a month's budget in one careless second.

The number that changes the design

The trigger call itself is metered at 0 credits. The charge happens inside it: recipientCount × perRecipientWeight, with the weight at 1 credit per recipient. So:

Blast Credits Share of a Starter month (30,000)
900-fan segment 900 3%
5,000-fan segment 5,000 17%
30,000 active subscribers 30,000 100%

Six five-thousand blasts and a Starter plan has nothing left for the chat replies, inbox reads and lookups the rest of the system needs that month. Pro's 120,000 absorbs four full-list sends; the free tier's 250 credits cannot send a single meaningful broadcast at all, which is worth saying plainly in your UI rather than letting someone discover it at the confirm step. The allowances are on pricing.

The practical consequence: segmentation is not a targeting nicety, it is cost control. Cutting a 5,000-fan list to the 900 who opened the last three drops frees 4,100 credits for five more sends in the same month. Build the audience with lists and make the segment picker the loudest control in your composer.

Create, preview, trigger, track

Four calls, in order, under https://api.ofapis.com/api/public/v1:

# 1. create the scenario — mailing ids are UUIDs
curl -X POST https://api.ofapis.com/api/public/v1/accounts/42/mailings \
  -H "Authorization: Bearer ofapis_sk_..." -H "Content-Type: application/json" \
  -d '{"name":"weekend-drop","text":"weekend set is up 🔥","listId":"9",
       "mediaIds":[777888999],"priceCents":1200,"lockedText":true}'

# 2. preview — costs 1 credit, makes a real call to count the audience
curl -X POST https://api.ofapis.com/api/public/v1/accounts/42/mailings/$MID/preview \
  -H "Authorization: Bearer ofapis_sk_..."
# → {"data":{"recipientCount":3120,"perRecipientWeight":1,"estimatedCost":3120}}

# 3. trigger — 0 credits on the call, then recipientCount × 1 debited inside it
curl -X POST https://api.ofapis.com/api/public/v1/accounts/42/mailings/$MID/trigger \
  -H "Authorization: Bearer ofapis_sk_..."
# → {"data":{"runId":"2c7f0a91-…","recipientCount":3120,"creditsDebited":3120}}

priceCents with lockedText: true turns the blast into a PPV drop: the text is hidden until the fan pays. Media comes from vault ids, so upload once and reference it across campaigns rather than attaching per send.

Preview costs a credit and is worth every one of them. Render it as a blocking confirmation that names both numbers — "reaches 3,120 fans, costs 3,120 credits, you have 11,400" — because estimatedCost is not an estimate in the usual sense; it is the amount that will be debited.

One edge case your UI must handle: a mailing with no listId falls back to every fan the creator has an open chat thread with, and preview then returns recipientCount: -1 with estimatedCost: 0, because that audience cannot be counted without enumerating it. Never show that as "free" — the trigger will enumerate it and debit one credit per fan it finds. Substitute your own subscriber count and label the figure approximate.

What the trigger actually does

It counts recipients, debits the credits in one atomic step, creates a run, and returns. Dispatch happens afterwards in the background, so the response tells you the campaign started, not that anyone received anything.

Failures at this step are specific and worth mapping to distinct UI states: 402 means the balance could not cover recipientCount × 1, 400 means the segment resolved to nobody (the OpenAPI reference still advertises 422 for this; the handler returns 400), 424 means that creator's OnlyFans session has expired and the account has to be re-linked, 409 means this mailing already has a PENDING or IN_PROGRESS run, and 404 means the mailing id does not belong to that account. All five come back as a bare text message, not as a JSON envelope with an error.code — only the gateway's own rejections (401, 402 on the call fee, 403, 429 and an idempotency 409) carry a code — so branch on the HTTP status, not on a field in the body. The errors page has the full mapping.

That 409 is the real double-send guard, and it is narrower than people assume: it fires only while a run is PENDING or IN_PROGRESS. It does not make the trigger idempotent. Trigger a mailing whose run has already COMPLETED and the recipients are recounted, the credits are debited a second time and every fan is messaged again — a 3,000-fan mailing fired twice is 6,000 credits and two identical DMs per fan. A PAUSED run does not block a new trigger either, so a mailing interrupted by an expired session can be re-triggered, charged again, and then deliver twice to the fans it already reached when the paused run resumes. If your tool has a "resend" button, gate it on your own run history, not on the API.

Tracking the run

Poll GET /accounts/{id}/mailings/{mailingId}/runs/{runId} and drive your progress bar from successCount and skippedCount against recipientCount. Fetching the mailing itself returns its ten most recent runs, which is enough to back a campaign history view without a second query.

The statuses are not decoration. PENDING is queued, IN_PROGRESS is sending, COMPLETED is done, FAILED means the scenario could not be loaded — and PAUSED is the one to surface loudly, because it means the creator's OnlyFans session expired mid-send. A paused run resumes automatically once the account is re-linked, picking up from the recipients it has not reached yet, so the correct UI is "paused, 1,840 of 3,120 sent, re-link to continue" rather than an error. Refunds only settle at COMPLETED, so a run left paused is also holding the whole debit.

Two behaviours worth telling operators about. Dispatch is deliberately throttled to one message every 500 ms, so a 3,120-recipient blast takes about 26 minutes end to end and a 30,000-fan send runs for over four hours — plan "drop is live" messaging around a window, not an instant. And recipients that are skipped (blocked, unsubscribed, closed DMs) are refunded automatically when the run completes, so creditsDebited at trigger time is a ceiling, not the final bill.

Wiring it into a campaign

A blast is rarely the whole campaign. The usual shape is a post published first so there is something to land on, the mailing triggered a few minutes later, and a follow-up to non-openers scheduled for the next day — the scheduling tool page covers that side. For an agency roster, loop the same create-preview-trigger sequence per accountId and stagger the triggers; every account on one token shares the same per-minute rate limit, though a mailing is only a handful of requests regardless of size, so the pressure is on your credit budget rather than on rpm.

Attribution needs saying honestly: there is no native tracking-link resource to mint per campaign, so measuring which blast drove which purchase means your own link layer plus a read of the affected threads afterwards, as the tracking links page sets out.

Three more limits worth knowing before you commit: a mailing has no scheduler of its own, so something on your side has to call trigger at the right minute; it sends one identical string with no merge fields, so anything that must read as written has to go through the one-to-one endpoint; and there is no update verb — a mailing is created, triggered and deleted, never edited. If your product promises per-fan copy at broadcast scale, this is the wrong primitive.

The next step that follows from this page: create one mailing against a 20-fan test list, call preview, and check estimatedCost against the allowance you are on before a full-list trigger ever exists in your UI. The free tier's 250 credits cover that whole rehearsal. Create an API key · credit allowances by plan

FAQ

Why does a 5,000-fan blast cost 5,000 credits?

Because mailings are billed per recipient rather than per API call. The trigger has a gateway weight of 0, then debits recipientCount × perRecipientWeight — one credit per recipient by default — atomically inside the call, before dispatch begins. That is why the preview step exists.

Is preview free?

No, it costs one credit, because it makes a real call to count the eligible audience. That is the cheapest credit in the whole flow — it is the only thing standing between a mistyped segment and a five-figure debit.

How long does a large send take to arrive?

Dispatch is throttled to one message every 500 milliseconds, so roughly 120 recipients a minute: about 26 minutes for 3,000 fans and over four hours for 30,000. Schedule time-sensitive campaigns with that spread in mind rather than expecting simultaneous delivery.

What happens if the creator's session expires halfway through?

The run moves to PAUSED rather than failing, keeping its place in the recipient list. Once the account is re-linked it resumes from where it stopped, so the correct response is to prompt for re-authentication, not to re-trigger the mailing.

Do I get credits back for fans who never receive it?

Yes. Recipients skipped because they are blocked, unsubscribed or cannot receive DMs are refunded automatically when the run completes, so the final cost is usually a little below the amount debited at trigger. The run record shows both creditsDebited and skippedCount so you can reconcile.

Can I just loop over chats instead?

You can, but it is worse on every axis, cost included: POST /accounts/{id}/chats/{chatId}/messages is weighted at 2 credits a send, double the 1 credit a mailing pays per recipient, so the same 3,000 fans is 6,000 credits instead of 3,000. Thousands of individual calls also burn your per-minute rate limit and leave you with no run record, no skip refunds and no restart-safety. The mass DM API exists so a broadcast is one tracked operation.

Related guides

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