Writes and approvals
Writes wait for a person to approve them unless the key has auto-approve on. How actions work, from request to result.
Product page: OnlyFans messaging API
BetterFans Link never writes to OnlyFans or Fansly on its own. Your code or your agent asks for a write, a person on your team approves or rejects it, and only then does it run. MCP clients that sign in with OAuth always wait for that approval. Keys wait too, unless an owner or admin gave the key auto-approve. An auto-approve key's writes skip step 2: the request runs the action and answers 200 with the result.
The loop
- You create an action with Create action, or an MCP write tool such as
send_message. BetterFans Link checks the params, writes a one linesummarysuch as "Send a $12 message to @jess" and returns202with the action inpendingstatus and itsapprovalUrl. - A person opens the
approvalUrl, or the Approvals page in the dashboard, reads the summary and the details, and approves or rejects it. They can add a note of up to 280 characters. - An approved action runs on the account's platform and ends as
executed, withresult, orfailed, witherror. - You find out through webhooks or by calling Get action.
A pending action that nobody decides within 24 hours becomes expired. Nothing is sent. No webhook event is sent for an expired action, so if you track actions yourself, treat expiresAt as the deadline or read the action again after it.
What you need
| Requirement | Without it |
|---|---|
A key with the write scope, or an MCP client given write access. | 403 missing_scope |
| Full access, or writes turned on for the account. An owner or admin can allow custom-access keys to write on the account's Settings page. | 403 writes_disabled |
An Idempotency-Key header on the request. | 400 idempotency_key_required |
| Someone who can approve. Owners and admins can; developers and workspace viewers cannot. | The action waits, then expires. |
Action types
| Type | What it does | Platforms | Params |
|---|---|---|---|
send_message | Sends a message to one fan. Add mediaIds to attach vault media. On OnlyFans, add priceCents to make it a paid message. On Fansly the message goes to the chat the account already has with the fan, is always free and attaches only free vault media. | OnlyFans and Fansly | Send message |
send_mass_message | Sends one message to fan lists or a set of fans. The action shows an estimate of how many fans it will reach. | OnlyFans | Send mass message |
unsend_message | Takes back a message. On OnlyFans, pass a massMessageId to take back every copy of a mass message. | OnlyFans and Fansly | Unsend message |
add_fan_to_list | Adds a fan to one of the fan lists. | OnlyFans | Add fan to list |
remove_fan_from_list | Removes a fan from one of the fan lists. | OnlyFans | Remove fan from list |
Fansly accounts
A Fansly account takes two action types: send_message and unsend_message. A Fansly message:
- goes to the chat the account already has with the fan,
- is always free, so leave out
priceCents(a price above 0 is400invalid_parameter), - attaches only free vault media.
If the account has no chat with the fan yet, or a media item is not free, nothing is sent and the approved action ends failed with the reason in error.
Any other type on a Fansly account is 400 not_supported_on_platform when you create it, and no action is created. Every action records its account's platform.
Action statuses
| Status | Label | Meaning |
|---|---|---|
pending | Waiting for approval | Waiting for a person to approve or reject it. It expires after 24 hours. |
approved | Approved | A person approved it and it is about to run. |
rejected | Rejected | A person rejected it. Nothing was sent. |
expired | Expired | Nobody decided within 24 hours. Nothing was sent. |
executing | Running | Running on the account's platform. |
executed | Done | Done. result holds what the write returned. |
failed | Failed | It ran and did not succeed. error says why. |
Retrying safely
Create one new Idempotency-Key for each write you intend, such as a UUID, and store it with your own record of the request. If the request times out or you are not sure it arrived, send it again with the same key.
- The same key with the same body returns the original action. Nothing is created twice.
- The same key with a different body is
409idempotency_conflict. - A key is unique across your whole workspace, in both modes, and never expires. Reusing one for another account, the other mode, another type or other params is also
idempotency_conflict. Use a new UUID for every write you intend.
What the approver sees
The approval page shows the summary, the account, the fan, the full text and price, any media, and who asked: the key name, or the MCP client and the tool it used. For a mass message it also shows estimatedRecipients. Write your text as you want it sent. The approver can approve or reject it, not edit it.
Following the outcome
Prefer webhooks. action.pending tells approvers there is something to decide. action.executed, action.rejected and action.failed tell your code how it ended.
If you poll Get action instead, wait a few seconds between calls and stop when the status is executed, rejected, expired or failed. A person may take hours to decide, so do not hold a request open waiting for one.
Test mode
In test mode the loop is the same, but an approved action never calls OnlyFans or Fansly. It ends as executed, and its result is only {"simulated": true}. See test mode.
Drafts are not writes
The MCP tool draft_message gathers what a reply needs: the chat so far, what the fan has spent and bought, and paid messages they have not bought yet. The agent writes the reply from that. It never creates an action and nobody needs to approve it. An agent can draft a reply, show it to you, and then ask to send it with send_message. See approvals over MCP.