The code samples reuse this setup:
List conversations
GET /v1/conversations returns conversations across all creators the key can see, newest activity first.
Response 200
GET /v1/conversations/counts (optionally with creatorId). It scans the whole inbox, so it has its own limit of 30 calls per 60 seconds per key:
Response 200
Get one conversation
GET /v1/conversations/{conversationId} returns the conversation plus the full fan (subscription, spend, notes, lists, salesOptOut), the open handoff if any, and aiCanReply: whether the AI would answer this chat right now.
Read it before you send: it tells you whether the fan opted out of sales and whether a hand-off is open.
Read messages
GET /v1/conversations/{conversationId}/messages returns one page of messages, oldest first. Without before you get the newest page. To go further back, pass the id of the oldest message you have as before; hasMore: false means you reached the start of the chat.
Response 200
GET /v1/conversations/{conversationId}/messages/{messageId} returns a single message. Use it to follow a send.
Send a message
POST /v1/conversations/{conversationId}/messages sends a message to the fan as the creator. It needs the messages:send scope and an Idempotency-Key header, which is required on this endpoint.
The body
A message needs text, media, or both. Media ids are strings of digits, and the same media id cannot appear twice across
mediaIds and previewMediaIds. Any other body field is refused with 400 VALIDATION_ERROR. Media ids come from the creator’s vault (GET /v1/creators/{creatorId}/media).
The Idempotency-Key
- Use a new key for every new message: a UUID is ideal (8 to 64 characters, letters, digits,
-,_). - If the request fails with a network error or a
5xx, retry with the same key. OnlyX returns the original result instead of sending twice; the replayed response carriesIdempotent-Replayed: true. - Reusing a key with a different body returns
409 IDEMPOTENCY_KEY_REUSED. More in Idempotency.
Send text
Send free vault media
Put the media inpreviewMediaIds and leave out the price. The fan receives it unlocked.
Send a paid message
A paid message (PPV) has lockedmediaIds, a priceCents, optional free previewMediaIds to tease, and optional text shown with it.
Response 202
What sending also does
- The chat moves to
teamfor 12 hours (if it wasaiorhandoff), so the AI does not talk over you.takenOverUntilshows when it returns to the AI. - The AI’s pending follow-ups in that chat are cancelled, and a draft reply the AI had waiting for review in that chat is withdrawn.
- Unread state is not changed. Call
POST /v1/conversations/{conversationId}/readif you wantunreadCountback to 0.
Track delivery
202 means OnlyX accepted and queued the message. OnlyX then sends it on OnlyFans at a natural pace, so queued can last from a few seconds to several minutes. Follow it with GET /v1/conversations/{conversationId}/messages/{messageId}.
Send errors
Every error body has the same shape, for example
{"error": {"code": "SALES_OPTED_OUT", "message": "…", "requestId": "req-3f9a1c2b7d4e5f60a1b2c3d4"}}. See Errors.
Media sends also fail while the creator’s contentConsentRequired is true (OnlyFans is waiting for her to accept its consent prompt). Text still works.
Mark read and unread
POST /v1/conversations/{conversationId}/read sets unreadCount to 0; /unread flags the chat as unread again so your team comes back to it. Both take no body and return the updated Conversation. They change OnlyX only: OnlyFans shows the fan no read receipt.
These state changes (read, unread, take over, release, AI on or off) accept an optional Idempotency-Key header, which makes a retry return the stored response instead of acting twice.
cURL
Take over and release
Take over a chat before your team starts typing, so the AI does not answer in the meantime:holdMinutes is 15 to 1440 (default 720, twelve hours); the body is optional. The chat’s status becomes team, and the AI withdraws any reply it had waiting in it. Taking over a chat your team already holds restarts the hold. When the hold ends (takenOverUntil), the chat returns to the AI on its own. Nothing is sent to the fan.
Release gives the chat back to the AI now:
cURL
503 SENDING_DISABLED while sending through the API is switched off.
Turn the AI on or off for one chat
PUT /v1/conversations/{conversationId}/ai with {"enabled": false} sets the chat to ai_off: the AI never answers it until someone turns it back on. {"enabled": true} gives it back to the AI, which may answer the waiting message right away, so confirm with a person first. The creator’s own AI switch still applies on top of this. Switching it on is refused with 503 SENDING_DISABLED while sending through the API is switched off.
Fans who opted out of sales
When a fan asks not to be sold to, OnlyX records it and the fan’ssalesOptOut becomes true. From then on:
- the AI stops offering paid content to that fan;
- any API send with a
priceCentsabove 0 returns409 SALES_OPTED_OUT; - free messages (text and free media) still work.
fan.salesOptOut in GET /v1/conversations/{conversationId} before composing a paid message.
A safe sending pattern
For automations and AI assistants, follow this order every time:- Read the conversation (
GET /v1/conversations/{conversationId}) and its latest messages. - Check
fan.salesOptOutbefore adding a price, and the creator’scontentConsentRequiredbefore adding media. - Show a person the exact text, media and price. Send only on an explicit yes.
- Send with a fresh
Idempotency-Key, and keep the key with the draft until you get a response. - On a timeout or
5xx, retry with the same key. Never with a new one. - Poll delivery. On
unconfirmed, stop: do not resend.
Related
- Conversations: statuses and hand-back rules.
- Vault media: find media ids and thumbnails.
- Hand-offs: chats the AI passed to your team.
- Idempotency and Rate limits.