> ## 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.

# Authentication

> API keys and OAuth tokens: format, scopes, presets, creator restrictions, expiry, revocation, and the 401 and 403 errors.

Every request to `https://api.onlyx.ai/v1` carries one credential. There are two kinds, and both belong to a **workspace**:

| Credential | Looks like | Best for | How you get it |
| - | - | - | - |
| **API key** | `onx_sk_` + 40 letters and digits | Your own scripts, servers, integrations; AI clients that accept a header | Settings → API & MCP → **Create API key** |
| **OAuth access token** | `onx_at_` + 40 letters and digits | AI assistants (Claude, ChatGPT, Cursor, VS Code, Claude Code) and apps that sign in on your behalf | The app sends you to an OnlyX consent screen; you approve |

Both are sent the same way and are checked the same way: same scopes, same creator restrictions, same rate limits.

## API keys

### Create a key

Only workspace **owners** and **admins** can create, rename and revoke keys.

1. Open [app.onlyx.ai](https://app.onlyx.ai/settings?tab=developer) → **Settings → API & MCP**.
2. Click **Create API key**.
3. Fill in the dialog:
   * **Name**: what uses the key, for example `Nightly revenue export` or `Zapier`. You will see it in the key list and in `GET /v1/me`.
   * **Access**: **Full access** (every scope), **Read only** (every `:read` scope), or **Custom** (pick scopes one by one).
   * **Creators**: *All creators* (the default, including creators added later) or a fixed list. See [Creator restrictions](#creator-restrictions).
   * **Expires**: *Never*, *30 days*, *90 days* or *1 year*.
4. Click **Create key** and copy the secret immediately.

<Warning>
  The secret is shown **once**. OnlyX stores only a SHA-256 hash of it and cannot show it again. If you lose it, revoke the key and create a new one.
</Warning>

A workspace can have up to **25 active keys**. Creating a 26th returns `409 KEY_LIMIT` in the dashboard; revoke an unused key first.

### Keys belong to the workspace

A key is owned by the workspace it was created in, not by the person who created it:

* Every current owner and admin of the workspace can see and revoke every key of the workspace.
* A key keeps working if the person who created it leaves the workspace. The key list marks it **Left the workspace** so you can rotate it.
* A key can only ever reach the workspace it was created in, even if its creator is a member of several workspaces.
* Actions taken with a key are attributed to the key (`API · <key name>`) in the workspace's activity.

### Send the key

Use the standard `Authorization` header:

```http theme={"system"}
Authorization: Bearer onx_sk_...
```

`X-API-Key: onx_sk_...` is accepted too, for tools that cannot set an `Authorization` header. Never put a key in a URL query string.

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

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

  session = requests.Session()
  session.headers["Authorization"] = f"Bearer {os.environ['ONLYX_API_KEY']}"
  print(session.get("https://api.onlyx.ai/v1/me", timeout=30).json())
  ```

  ```javascript JavaScript theme={"system"}
  const res = await fetch("https://api.onlyx.ai/v1/me", {
    headers: { Authorization: `Bearer ${process.env.ONLYX_API_KEY}` },
  });
  console.log(await res.json());
  ```
</CodeGroup>

### Expiry, revocation and rotation

* **Expiry** is chosen at creation. After it passes, the key gets `401 INVALID_API_KEY`.
* **Revoke** a key from Settings → API & MCP: click the bin icon on the key's row, then **Revoke key**. It stops working on the next request, and revocation is permanent.
* The key list shows when each key was **last used** (updated at most once a minute), which tells you whether an old key is still in use.

To rotate a key without downtime:

1. Create a new key with the same scopes and creators.
2. Deploy it to your integration.
3. Check that the new key's **Last used** moves and the old key's stops.
4. Revoke the old key.

## Scopes

A scope is a permission. A request that needs a scope the credential lacks fails with `403 INSUFFICIENT_SCOPE`. Give each integration only what it needs.

| Scope | Grants | Reaches fans or OnlyFans? |
| - | - | - |
| `workspace:read` | `GET /v1/me`. Always granted, to every credential. | No |
| `creators:read` | List and read creators, their connection status and connect link | No |
| `creators:write` | Add a creator, rename, turn the AI on or off for a creator, create and revoke connect links | **Yes**: turning a creator's AI on lets the AI answer every waiting fan within seconds. A connect link also lets someone sign in a creator account |
| `inbox:read` | Conversations, messages, counts, hand-offs | No |
| `inbox:write` | Mark read or unread, take over a chat, release it to the AI, turn the AI on or off for a chat, resolve hand-offs | **Yes**: releasing a chat, turning its AI on, or resolving a hand-off can make the AI answer the fan immediately |
| `messages:send` | Send messages to fans: text, free media, paid messages | **Yes**: every call |
| `fans:read` | Fans, fan lists, purchase history | No |
| `fans:write` | A fan's custom name, notes, mute | No |
| `media:read` | Vault media lists and thumbnails | No |
| `stats:read` | Reports: today, revenue, audience, overview | No |
| `money:read` | Each creator's transaction ledger | No |
| `ai:read` | AI persona, AI content (ladders and levels), AI settings, welcome message | No |
| `ai:write` | Edit the AI persona, AI content and AI settings; change the OnlyFans welcome message | **Yes**: the welcome message is sent to every new subscriber. Turning review mode off also needs `messages:send` |
| `links:read` | Tracking links and their stats | No |
| `links:write` | Update tracking links; create new tracking links | **Yes**: creating a link creates a real link on the creator's OnlyFans account |

**Presets** in the create dialog:

* **Full access**: all 15 scopes.
* **Read only**: `workspace:read`, `creators:read`, `inbox:read`, `fans:read`, `media:read`, `stats:read`, `money:read`, `ai:read`, `links:read`.
* **Custom**: any combination.

Typical custom sets:

| Integration | Scopes |
| - | - |
| Revenue dashboard | `creators:read`, `stats:read`, `money:read` |
| Inbox monitor that alerts your team | `creators:read`, `inbox:read` |
| Chatting tool that sends as your team | `creators:read`, `inbox:read`, `inbox:write`, `messages:send`, `fans:read`, `media:read` |
| Onboarding bot that adds creators | `creators:read`, `creators:write` |
| Content manager | `creators:read`, `media:read`, `ai:read`, `ai:write` |

When a scope is missing, the response names it and also sets a standard header:

```http theme={"system"}
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", scope="messages:send"
Content-Type: application/json

{"error":{"code":"INSUFFICIENT_SCOPE","message":"This key does not have the `messages:send` scope.","requestId":"req-3f9a1c2b7d4e5f60a1b2c3d4"}}
```

You cannot add scopes to an existing key. Create a new key with the scopes you need and revoke the old one.

## Creator restrictions

A key (or an OAuth connection) can be limited to specific creators. Then:

* Lists (`/v1/creators`, `/v1/conversations`, `/v1/fans`, `/v1/handoffs` and others) only contain those creators and their data.
* Any id that belongs to another creator returns **404, exactly as if it did not exist**. The API never confirms that something outside your restriction exists.
* A `creatorId` filter naming a creator you cannot see returns `404 CREATOR_NOT_FOUND`, never an empty list.
* Creators added to the workspace later are **not** added to a restricted key.

`GET /v1/me` returns the list in `credential.creatorIds` (`null` means all creators).

## OAuth for AI assistants and apps

AI clients such as Claude, ChatGPT, Cursor, VS Code and Claude Code connect with OAuth 2.1, so nobody copies a key. The client handles the protocol; you only click through a consent screen. To set one up, see the [MCP guides](/developers/mcp/overview).

### What the consent screen shows

When a client sends you to `app.onlyx.ai/oauth/authorize`, you sign in to OnlyX (including 2FA if you use it) and see:

* **The app's name.** Apps register themselves, so the name is self-declared. The screen marks it as unverified; check that it is the app you just connected.
* **Where you will be sent back**: the host of the app's redirect address.
* **What the app asks for**, as plain sentences, grouped into read and write. Permissions that can reach fans (`messages:send`, `inbox:write`, `ai:write`, `links:write`) are highlighted.
* **A workspace picker.** Only workspaces where you are an owner or admin can be chosen.
* **An optional creator restriction**, to limit the app to some creators.

Click **Approve** to connect, or **Deny**. Approving never changes your active workspace in the dashboard.

### Tokens and how long they last

| Token | Format | Lifetime |
| - | - | - |
| Access token | `onx_at_...` | 1 hour |
| Refresh token | `onx_rt_...` | 60 days, replaced by a new one on every use |

Clients refresh automatically. Using an old refresh token a second time revokes the whole connection (it signals a stolen token); the app then asks you to sign in again.

A connection is tied to the person who approved it. If that person is no longer an owner or admin of the workspace, its tokens stop working with `401 INVALID_TOKEN`; someone with access reconnects the app.

### Connected apps

Settings → API & MCP → **Connected apps** lists every OAuth connection in the workspace: the app's name, its redirect host, who approved it, its scopes and creators, and when it was last used. Owners and admins can revoke any connection; its tokens stop working immediately.

### Building your own OAuth client

If you are building an app that other OnlyX workspaces connect to, use the same standards-based flow the AI clients use. OnlyX publishes its metadata at:

```
https://api.onlyx.ai/.well-known/oauth-authorization-server
```

| Endpoint | Purpose |
| - | - |
| `POST https://api.onlyx.ai/oauth/register` | Dynamic client registration (RFC 7591). Public clients only: `token_endpoint_auth_method` must be `none`. Redirect URIs must be `https`, `http` on a loopback host (`localhost`, `127.0.0.1` or `[::1]`, any port), or one of the app schemes `cursor:`, `vscode:`, `vscode-insiders:`, `windsurf:` and `claude:`; anything else is refused. Limited to 20 registrations per hour per IP. |
| `GET https://api.onlyx.ai/oauth/authorize` | Authorization code flow. PKCE with `S256` is **required**. Optional `scope` (space-separated scopes from the table above) and `resource` (`https://api.onlyx.ai/v1` or `https://mcp.onlyx.ai/mcp`). |
| `POST https://api.onlyx.ai/oauth/token` | Form-encoded. `grant_type=authorization_code` (with `code`, `redirect_uri`, `client_id`, `code_verifier`) or `grant_type=refresh_token` (with `refresh_token`, `client_id`). Returns `access_token`, `token_type: "Bearer"`, `expires_in: 3600`, `refresh_token`, `scope`. |
| `POST https://api.onlyx.ai/oauth/revoke` | Revoke a token (RFC 7009). Always answers 200. |

Authorization codes expire after 60 seconds and work once. Token errors follow RFC 6749: `invalid_grant`, `invalid_client`, `invalid_request`, `unsupported_grant_type`.

## Authentication errors

| Status | Code | Meaning | What to do |
| - | - | - | - |
| 401 | `UNAUTHORIZED` | No credential was sent | Add the `Authorization` header |
| 401 | `INVALID_API_KEY` | The key is wrong, expired or revoked (the same answer for all three) | Check the key; create a new one if it was revoked or expired |
| 401 | `INVALID_TOKEN` | The OAuth token is invalid or expired, the connection was revoked, or the approving person lost owner/admin access | Refresh the token; if that fails, reconnect the app |
| 403 | `INSUFFICIENT_SCOPE` | The credential lacks a scope; the message and `WWW-Authenticate` header name it | Use a key with that scope |
| 403 | `WORKSPACE_SUSPENDED` | The workspace is suspended | Contact OnlyX support from the dashboard |

Repeated failed authentication with credentials OnlyX does not recognize is limited to 30 attempts per minute per IP address; after that such requests get `429 RATE_LIMITED` instead of `401`. A valid credential is never refused because of other failures from the same address, and an expired or revoked credential always gets its normal `401`.

## Best practices

* **One key per integration**, named after it, so you can revoke one without breaking the others.
* **Least privilege.** Start from Read only and add write scopes only where needed. Give `messages:send` only to software that must send.
* **Restrict to creators** when an integration only serves some creators, for example a client-specific dashboard.
* **Set an expiry** on keys for contractors, trials and experiments.
* **Keep keys server-side.** Never ship a key in a browser app, a mobile app or a public repository. Store it in a secret manager or an environment variable.
* **Rotate** when someone with access to the key leaves, and after any suspected leak. Revoke first if you think it leaked.
* **Watch Last used** in the key list, and revoke keys that nothing uses.
