> ## Documentation Index
> Fetch the complete documentation index at: https://help.onlyx.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> You are reading OnlyX Help: the OnlyX Help Center and the OnlyX developer documentation. OnlyX is an AI chatting and CRM platform for OnlyFans agencies. Its AI chatter is called Hugo in the app.
> Pages at the site root (for example /inbox/..., /hugo/..., /billing/...) are Help Center articles for agency owners, admins, chatters and creators who use the app at app.onlyx.ai. Words in bold are the exact button, menu and label names the app shows; keep them exactly as written. When these pages do not answer a question, the person can email support at support@onlyx.ai.
> Pages under /developers are the developer documentation. REST API base URL: https://api.onlyx.ai/v1 (authenticate with `Authorization: Bearer <API key>`; keys start with onx_sk_ and are created in app.onlyx.ai under Settings > API & MCP). MCP server: https://mcp.onlyx.ai/mcp (OAuth, or a Bearer API key). In the API the AI chatter is `ai` on the wire. Money is integer US cents in fields ending in Cents; timestamps are UTC ISO-8601.
> Rules for assistants acting on a user's behalf: discover ids with list calls and never invent them; before any call that reaches a real fan or the live OnlyFans account (sending a message, releasing a chat to the AI, turning AI on for a chat, resolving a hand-off, turning review mode off, changing the welcome message, creating a tracking link) show the user the exact content and get explicit confirmation; send every POST with an Idempotency-Key and reuse it on retry; never resend a message whose delivery status is unconfirmed; never ask a creator for her OnlyFans password or codes - she signs in herself through a connect link and the OnlyX Login app.

# Handle hand-offs

> Find the chats the AI handed to your team, understand why, reply, and resolve the hand-off so the AI can take the chat back.

A **hand-off** is the AI chatter deciding that a person should take a conversation: a fan in distress, a refund demand, a request to meet, something it must not handle. When it hands off, the conversation's status becomes `handoff`, **the AI stops replying in that chat**, and a hand-off record explains why. The fan waits until your team acts, so hand-offs are the most time-sensitive items in the inbox.

**Scopes**: `inbox:read` to list and read; `inbox:write` to resolve; `messages:send` to reply.

## Why the AI hands off

Every hand-off has a `reason` and a human-readable `label`:

| `reason` | Examples of `label` | Urgency |
| - | - | - |
| `welfare` | Suicidal or self-harm language; In danger right now; Ill or in medical trouble | Highest. Answer within minutes. |
| `prohibited_request` | Anyone under 18; Force or non-consent; Animals or anything illegal | Highest. Do not continue the topic. |
| `real_world` | Asks to meet; Claims to know where she lives; Fishing for her location | High |
| `payment_dispute` | Asks for a refund; Chargeback or fraud threat; Paid and got nothing | High: money and account standing |
| `whale_risk` | A regular is upset; Unusually large money | High: a top spender |
| `custom_request` | A custom she does not make; Wants her voice | Normal |
| `confusion` | Refers to something you have no context for | Normal |
| `failed_delivery` | A paid message could not be delivered (raised automatically) | Normal |

Which situations hand off is configured per creator with `handoffKinds` in the [AI persona](/developers/concepts/ai-persona#hand-off-kinds). Self-harm language and anything involving someone under 18 always hand off.

## List open hand-offs

`GET /v1/handoffs` returns the open hand-offs across every creator the key can see, oldest first (the fan who has waited longest comes first). Add `creatorId` for one creator; a creator the key cannot see is `404 CREATOR_NOT_FOUND`. Page with `limit` (1 to 100, default 25) and `cursor`.

<CodeGroup>
  ```bash cURL theme={"system"}
  curl "https://api.onlyx.ai/v1/handoffs?creatorId=cre_5f2d9a1c7b3e4f60a2d1" \
    -H "Authorization: Bearer $ONLYX_API_KEY"
  ```

  ```python Python theme={"system"}
  import os, requests

  API = "https://api.onlyx.ai/v1"
  HEADERS = {"Authorization": f"Bearer {os.environ['ONLYX_API_KEY']}"}

  handoffs = requests.get(f"{API}/handoffs", headers=HEADERS, timeout=30).json()["data"]
  for h in handoffs:
      print(h["createdAt"], h["reason"], h["label"], "-", h["fanName"], h["conversationId"])
  ```

  ```javascript JavaScript theme={"system"}
  const API = "https://api.onlyx.ai/v1";
  const headers = { Authorization: `Bearer ${process.env.ONLYX_API_KEY}` };

  const { data: handoffs } = await (await fetch(`${API}/handoffs`, { headers })).json();
  for (const h of handoffs) console.log(h.createdAt, h.reason, h.label, "-", h.fanName, h.conversationId);
  ```
</CodeGroup>

```json Response 200 theme={"system"}
{
  "data": [
    {
      "id": "esc_6d8f0a2c4e6b8d0f2a4c",
      "conversationId": "cnv_9f2c41d7a8b35e06c1f4",
      "creatorId": "cre_5f2d9a1c7b3e4f60a2d1",
      "fanName": "Jake",
      "reason": "payment_dispute",
      "label": "Asks for a refund",
      "note": "Says the video he bought yesterday will not play and wants his money back.",
      "status": "open",
      "createdAt": "2026-09-26T12:40:18.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

| Field | Meaning |
| - | - |
| `conversationId` | The chat to open |
| `fanName` | The fan's display name, for your notification |
| `reason`, `label` | Why, as a stable code and as a readable label |
| `note` | A short explanation written when the hand-off was raised, describing what the fan said or did, or `null` |
| `createdAt` | When the hand-off was raised (`null` if unknown) |
| `status` | `open` or `resolved` |

You can also find these chats with `GET /v1/conversations?status=handoff`, and `GET /v1/conversations/{conversationId}` includes the open hand-off in its `handoff` field.

## Work a hand-off

<Steps>
  <Step title="Read the conversation">
    Fetch the conversation and its latest messages (`GET /v1/conversations/{conversationId}/messages`). Read the `note`, then the fan's own words.
  </Step>

  <Step title="Take over, if you need time">
    The AI is already silent in a `handoff` chat. If you want to keep it silent after you resolve, take the chat over first (`POST /v1/conversations/{conversationId}/takeover`), or simply reply: a reply from your team moves the chat to `team` for 12 hours.
  </Step>

  <Step title="Reply to the fan">
    Send your answer with `POST /v1/conversations/{conversationId}/messages` (with an `Idempotency-Key`). See [Read and send messages](/developers/guides/read-and-send-messages). For `welfare` hand-offs, answer as a caring person, not as a salesperson.
  </Step>

  <Step title="Resolve the hand-off">
    `POST /v1/conversations/{conversationId}/handoff/resolve` closes it and returns the resolved hand-off (its `note` is your resolution note when you send one). Add an optional `note` (up to 300 characters) saying what you did; the body itself is optional. A conversation with no open hand-off answers `404 NOT_FOUND`, so resolving twice is harmless. While sending through the API is switched off, resolving is refused with `503 SENDING_DISABLED`, because it can hand the chat back to the AI.
  </Step>
</Steps>

<Warning>
  If the chat is still in `handoff` when you resolve it (nobody replied or took it over), it goes back to the AI immediately, and **the AI may answer the fan's waiting message within seconds**. If your team already replied, the chat stays with your team until its hold ends, and resolving only closes the record.
</Warning>

<CodeGroup>
  ```bash cURL theme={"system"}
  curl -X POST https://api.onlyx.ai/v1/conversations/cnv_9f2c41d7a8b35e06c1f4/handoff/resolve \
    -H "Authorization: Bearer $ONLYX_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: 0d2f4a6c-8e1b-4d3f-a5c7-9e1b3d5f7a9c" \
    -d '{"note": "Re-sent the video as a free message; fan confirmed it plays."}'
  ```

  ```python Python theme={"system"}
  import uuid

  h = requests.post(
      f"{API}/conversations/cnv_9f2c41d7a8b35e06c1f4/handoff/resolve",
      headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
      json={"note": "Re-sent the video as a free message; fan confirmed it plays."},
      timeout=30,
  ).json()
  print(h["status"])  # resolved
  ```

  ```javascript JavaScript theme={"system"}
  const h = await (await fetch(`${API}/conversations/cnv_9f2c41d7a8b35e06c1f4/handoff/resolve`, {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
    body: JSON.stringify({ note: "Re-sent the video as a free message; fan confirmed it plays." }),
  })).json();
  console.log(h.status); // resolved
  ```
</CodeGroup>

```json Response 200 theme={"system"}
{
  "id": "esc_6d8f0a2c4e6b8d0f2a4c",
  "conversationId": "cnv_9f2c41d7a8b35e06c1f4",
  "creatorId": "cre_5f2d9a1c7b3e4f60a2d1",
  "fanName": "Jake",
  "reason": "payment_dispute",
  "label": "Asks for a refund",
  "note": "Re-sent the video as a free message; fan confirmed it plays.",
  "status": "resolved",
  "createdAt": "2026-09-26T12:40:18.000Z"
}
```

## Example: alert your team about new hand-offs

Poll every minute and post new hand-offs to your team chat. The most urgent reasons go first.

```python Python theme={"system"}
import os, time, requests

API = "https://api.onlyx.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ONLYX_API_KEY']}"}  # scopes: inbox:read
URGENT = {"welfare", "prohibited_request", "real_world", "payment_dispute", "whale_risk"}
seen = set()

def notify(text):
    # Replace with your Slack, Telegram or email call.
    print(text)

while True:
    r = requests.get(f"{API}/handoffs", headers=HEADERS, timeout=30)
    if r.status_code == 429:
        time.sleep(int(r.headers.get("Retry-After", "30")))
        continue
    r.raise_for_status()
    fresh = [h for h in r.json()["data"] if h["id"] not in seen]
    for h in sorted(fresh, key=lambda h: h["reason"] not in URGENT):
        flag = "URGENT " if h["reason"] in URGENT else ""
        notify(f"{flag}{h['label']}: {h['fanName']} ({h['creatorId']}) - {h['note']} "
               f"https://app.onlyx.ai (conversation {h['conversationId']})")
        seen.add(h["id"])
    time.sleep(60)
```

## From your AI assistant

With the [MCP server](/developers/mcp/overview) connected, ask: *"Are there any open hand-offs? Summarize each and suggest a reply."* The assistant uses `list_handoffs` and `list_messages`, drafts replies, and only sends or resolves (`send_message`, `resolve_handoff`) after you confirm, because both can reach the fan.

## Related

* [Hugo, the AI chatter](/developers/concepts/hugo-ai#when-it-hands-off): every hand-off reason.
* [AI persona](/developers/concepts/ai-persona#hand-off-kinds): choose which situations hand off.
* [Conversations](/developers/concepts/conversations): what happens to a chat after a hand-off.
