ofapisv1
solution

Updated

Build OnlyFans Chatter Software

Chatter software is a people problem wearing an API costume. The messaging calls are trivial; what is hard is making sure two chatters never answer the same fan, that the 3am shift knows what the 9pm shift promised, and that six months later you can prove who sent a particular line. This page covers those three — assignment, handover, audit — plus the one piece of API physics that determines how large a desk you can run on a single token. For where the desk sits in the wider operation, see who does what in an agency — chatters, ops owner, creator, and what each one is allowed to touch.

The ceiling nobody plans for

Rate limits are per token per minute, not per user: 60 on Free, 100 on Starter, 500 on Pro. Every chatter in your app shares that budget, and so does every background process you run alongside them.

Work it out before you hire, using your own send-rate numbers. Take a desk where each chatter averages six sends a minute at peak: twelve chatters is 72 requests a minute before you count thread opens, fan lookups and inbox refreshes, and each of those is a request too. On Starter's 100 rpm that desk is throttled during peak; on Pro's 500 it has room. The fix is not only a plan upgrade: serve the inbox from your own store rather than the API, so a chatter scrolling the queue costs you zero requests and only a send or a cold-thread backfill touches the ceiling. The rate limits page has the backoff contract for when you do hit it.

Assignment and locking

The API has no notion of thread ownership. It will happily accept two sends to the same chat from two people a second apart, which is exactly the failure mode fans notice. Ownership lives in your schema:

CREATE TABLE conversation (
  account_id      BIGINT NOT NULL,
  chat_id         BIGINT NOT NULL,
  assignee_id     BIGINT,              -- NULL = unassigned, in the pool
  lock_expires_at TIMESTAMPTZ,         -- soft lock, renewed while the tab is open
  status          TEXT NOT NULL,       -- open | snoozed | escalated | closed
  last_message_at TIMESTAMPTZ NOT NULL,
  PRIMARY KEY (account_id, chat_id)
);
CREATE INDEX ON conversation (assignee_id, last_message_at DESC);

Make the lock a lease, not a flag. A chatter whose laptop dies holds a boolean lock forever; a lease with a two-minute expiry, renewed by the open tab, releases itself. Claim with a conditional update — UPDATE … WHERE assignee_id IS NULL OR lock_expires_at < now() — so the claim and the check are one atomic statement rather than a read followed by a hopeful write.

Route by creator rather than round-robin where you can. A chatter who works the same three creators every shift remembers what was promised; one who gets a random thread from a roster of twenty does not, and the fan can tell.

Shift handover

The handover is where most desks leak money, because the state that matters is not in any message: what was promised, what was priced, what is pending. Store it as structured fields on the conversation, written by the outgoing chatter and shown to the incoming one before they can take the thread:

Backfill the message history itself from GET /accounts/{id}/chats/{chatId}/messages, which pages newest-first with limit (default 20, clamped to 1–100) and an olderThanMessageId cursor rather than an offset. The conversation list behind the queue comes from GET /accounts/{id}/chats with limit and offset. Both are account-scoped, so one desk and one token span the whole roster — the pattern described on the agency automation page.

Keep the inbox live by ingesting message.received deliveries into your own store instead of polling. Deliveries are free, signed, and at-least-once, so dedupe on X-Ofapis-Delivery; the webhooks page has the verification contract. The draft-and-route layer that sits on top of that feed is worked through in automating DMs end to end.

What automation takes off a chatter, and what it does not

Automation here is not a chatter replacement. It is the removal of work a chatter should never have been doing in the first place, and three jobs qualify because none of them needs judgement:

What stays human: pricing a custom, reading a fan who is going cold, deciding when to push and when to let a thread rest, and anything a complaint could later be built on. The honest ceiling is roughly the first two exchanges plus the mechanical sends; a desk that automates past that collects refund requests, not revenue.

The audit trail

Log every outbound call as a row at the moment you make it, not derived afterwards from message history. Message history tells you what was said; it does not tell you which of your staff said it.

CREATE TABLE send_log (
  id              BIGSERIAL PRIMARY KEY,
  account_id      BIGINT NOT NULL,
  chat_id         BIGINT NOT NULL,
  chatter_id      BIGINT NOT NULL,
  template_id     BIGINT,
  text            TEXT   NOT NULL,     -- the rendered text, not the template
  price_cents     INT    NOT NULL DEFAULT 0,
  idempotency_key TEXT   NOT NULL UNIQUE,
  request_id      TEXT,                -- X-Request-Id, echoed back by the API
  http_status     INT,
  sent_at         TIMESTAMPTZ NOT NULL DEFAULT now()
);

request_id is the field that makes a support conversation short: it is echoed on the response, so one id ties your row to a specific gateway call. idempotency_key should be deterministic — a hash of chatter, chat and draft — so that a chatter who double-clicks send gets the original response replayed with Idempotency-Replayed: true rather than sending twice, and a request fired while the first is still in flight gets 409 IDEMPOTENCY_CONFLICT instead of a duplicate DM.

Store the rendered text, not just the template_id. Templates get edited; a dispute six months later is about the exact words that went out.

Per-chatter attribution, honestly

You can attribute sends exactly: every row above has a chatter_id. Attributing revenue is harder and worth being precise about, because there is no aggregate earnings or statistics endpoint to reconcile against — as the statistics page explains, revenue has to be reconstructed from the monetary fields on messages and notifications. That means a nightly job that re-reads recently touched threads, matches the paid messages back to your send_log rows by message id, and credits the chatter who sent them.

Two caveats to put in front of whoever reads the dashboard. That reconciliation costs a credit per page read, so scope it to threads touched in the last 48 hours rather than the whole inbox. And last-touch attribution flatters closers at the expense of whoever warmed the fan up over three shifts — if you pay commission on it, expect the incentive to show up in behaviour within a month.

Why per-call billing suits a desk

Chatter tooling is traditionally sold per seat, which taxes you for staffing the night shift. Billing here is per call regardless of how many people are logged in — one credit for most routes, two for an account-scoped send. Rejections at the gate (401, 402, 403, 429) never reach metering, and 5xx, 424 (the creator's OnlyFans session expired) and 409 are refunded — but a 400, 404 or 422 is charged like any other call. A desk's bill therefore tracks messages handled rather than headcount. A mass send debits one credit per recipient, so a 3,000-fan blast is 3,000 credits, a tenth of a Starter month, and a careless campaign can consume what the desk needs for its shifts; see pricing for the allowances. The sizing test worth running before you commit: one creator and two chatters for a week with your own send log on, which gives you the real requests-per-send multiplier and the real credit cost of a handled conversation — the two numbers that pick the plan.

FAQ

How do I stop two chatters answering the same fan?

Hold ownership in your own database as a short lease — an assignee_id plus a lock_expires_at renewed by the open tab — and claim it with a single conditional UPDATE. The API enforces nothing about ownership, so a boolean lock without an expiry will strand threads whenever a browser crashes.

What has to survive a shift handover?

The things no message contains: open promises with due dates, the last price quoted, the fan's mood, and anything they have asked not to receive. Store these as fields on the conversation and show them before the incoming chatter can take the thread, or the next shift re-quotes a price and loses the sale.

Can I prove which chatter sent a given message?

Only if you log it yourself at send time. Record chatter_id, the rendered text, the idempotency key and the echoed X-Request-Id on every outbound call; message history read back later shows the creator account as the sender and nothing about your staff.

Will this replace my chatters?

No, and anyone selling that is selling you refund requests. What it removes is the mechanical part of a shift: the templated opener on a first inbound DM, the same line pasted to 300 fans, the threads nobody has touched in an hour. Pricing a custom, reading a fan going cold and handling a complaint stay with the person — the API has no opinion about any of them.

Does a bigger team cost more?

Not in licensing — billing is per call, not per seat, so staffing the night shift costs nothing extra. It is not only successful calls, though: a 400, 404 or 422 is charged, while gate rejections (401, 402, 403, 429) are free and 5xx, 424 and 409 are refunded. The real constraint is the shared per-token rate limit of 60, 100 or 500 requests a minute, so a large desk needs a higher plan and an inbox served from your own store rather than from live API reads.

How do I attribute revenue to a chatter?

Reconcile after the fact: re-read recently active threads, match paid messages back to your send log by message id, and credit the sender. There is no earnings endpoint to query directly, so budget a credit per page of that nightly read and scope it to threads touched in the last two days.

Related guides

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