# Cherry API: chat rooms, bots and moderation

> How the Cherry API works: project and bot keys, room and member endpoints, Telegram-style bot updates, in-room moderation and per-app rate limits.


The Cherry API is a REST API your backend calls to create chat rooms, invite wallets, post messages, moderate members and run bots inside Cherry. Cherry is a wallet-to-wallet messenger for crypto: you sign in with a wallet, DM any address, and join or create token-gated, NFT-gated and paid group chats, with no phone number, email or KYC. For a developer that means a room per match, order or community, addressed by wallet, with no accounts or presence to build. The API, its keys and its bots are free, and everything below is current as of 11 September 2026.

## What the Cherry API does

Two surfaces, two key formats, two base URLs. A project key manages the rooms your project owns; a bot key acts as a bot identity.

| Surface | Base URL | Key format | What it does |
|---|---|---|---|
| Apps API | `https://api.cherry.fun/api/v1/apps` | `cherry_sk_<projectId>_<secret>` | Create, update and delete rooms; invite wallets; kick, ban, mute, set roles; send, read and delete messages as the project |
| Bot API | `https://api.cherry.fun/api/v1/bots` | `cherry_bot_<botId>_<secret>` | DM a wallet; post into a group; inline buttons and callbacks; edit sent messages; moderate where the bot holds a role; long-poll updates or register a webhook |

Both are server-to-server. A key is a single opaque token you copy from the portal, shown once, and it belongs on your server, not in a browser bundle.

## How it works

The permission model has three parts: which key you use, which scopes it holds, and for bots, what role it has in the room.

### Two keys and the /me check

`GET /me` on either surface returns that key's context: the fastest way to confirm credentials and read back granted scopes.

```bash
curl https://api.cherry.fun/api/v1/apps/me \
  -H "Authorization: Bearer cherry_sk_<projectId>_<secret>"

curl https://api.cherry.fun/api/v1/bots/me \
  -H "Authorization: Bearer cherry_bot_<botId>_<secret>"
```

A project key's response names its scopes, its rate limits (`{ "perMinute": 600, "perDay": 50000 }`), the `keyId` that authenticated the call, and the bots your project owns with their ids and wallets. Every project also has an app identity: a wallet minted on your first send, or when you create your first API key, whichever happens first, whose display label you set in the portal under **Developer profile → App identity**. That identity posts into rooms without joining them, so a backend that runs a room per clan or per listing does not add a bot member to each one. Both key types rotate in place: rotation issues a fresh secret for the same key, keeps its id, name and scopes, and invalidates the old secret immediately.

### Scopes per endpoint

Each endpoint requires one scope, and a call without it returns `403 INSUFFICIENT_SCOPE`. Scopes are keyed to their surface: granting an Apps API scope to a bot key does nothing.

| Key | Scopes | Example endpoints |
|---|---|---|
| Project (`cherry_sk_`) | `groups:create`, `groups:manage`, `members:invite`, `members:moderate`, `messages:send`, `messages:read`, `messages:delete` | `POST /apps/groups`, `POST /apps/groups/:roomId/members/:wallet/ban` |
| Bot (`cherry_bot_`) | `bots:dm:send`, `bots:dm:read`, `bots:groups:send`, `bots:groups:moderate`, `bots:interactive`, `bots:callback:answer`, `bots:updates:poll`, `bots:webhook:manage`, `messages:edit` | `POST /bots/sendDirectMessage`, `GET /bots/getUpdates` |

You grant these in the portal, project keys under the project's **API keys** section and bot keys under the bot's **Keys** section, or by PATCHing the key with the set it should hold. The signing scopes `bots:sign:request` and `bots:tx:request` need admin review first.

### Rooms, members and messages

The core server flow is three calls: create a room owned by a wallet you name, seed it with participants, post into it.

```ts
const OWNER = {
  Authorization: `Bearer ${process.env.CHERRY_PROJECT_KEY}`,
  'Content-Type': 'application/json',
};

const { roomId } = await (
  await fetch('https://api.cherry.fun/api/v1/apps/groups', {
    method: 'POST',
    headers: OWNER,
    body: JSON.stringify({
      ownerWallet: hostWallet,
      title: `Match #${matchId}`,
      initialMembers: [playerA, playerB],
    }),
  })
).json();

await fetch(`https://api.cherry.fun/api/v1/apps/groups/${roomId}/messages`, {
  method: 'POST',
  headers: OWNER,
  body: JSON.stringify({ content: 'Match starting. Good luck.' }),
});
```

`initialMembers` invites and accepts in one step; a later `POST /apps/groups/:roomId/members` with `autoAccept: true` does the same for latecomers, and its response splits them into `invited`, `skipped` and `accepted`. Reading history is cursor-paginated, newest first, 50 messages a page by default and 100 at most. Moderation is one route per verb: a ban with `deleteMessages: true` purges that member's messages and reports a `deletedCount`, while a mute leaves their old messages standing until you unmute. Rooms your project did not create are off limits: `403 ROOM_NOT_MANAGED_BY_APP`.

### The Bot API

A bot is a separate identity with its own wallet, name, handle and avatar. Its endpoints take a JSON body with no path parameters, and the Telegram-shaped vocabulary is deliberate: `sendInteractiveMessage` attaches an inline keyboard, `answerCallbackQuery` responds when someone presses a button, and `editMessageText` and `editMessageReplyMarkup` change what the bot already sent.

Bot moderation is gated twice. The `bots:groups:moderate` scope authorizes the call, and the bot's in-room role, the same owner/admin/moderator roles a human moderator holds, authorizes the action on another member. So a moderation bot is set up in two halves: a project key creates the room, invites the bot's wallet with `autoAccept: true` and promotes it with `POST /apps/groups/:roomId/members/:wallet/role`, then the bot key does the work.

```ts
await fetch('https://api.cherry.fun/api/v1/bots/banGroupMember', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CHERRY_BOT_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ roomId, wallet, deleteMessages: true }),
});
```

Four invariants fall out of that second gate. A bot cannot moderate the room owner, and a moderator bot cannot act on admins. A bot cannot set roles, so none can escalate itself. A muted bot's moderation calls are rejected, which makes muting a kill switch. And a bot acts as its own wallet: it never inherits the owner's authority.

### Updates: long-poll or webhook

A bot hears about new messages in one of two ways. `GET /api/v1/bots/getUpdates` long-polls for them under the `bots:updates:poll` scope. `POST /api/v1/bots/setWebhook`, under `bots:webhook:manage`, registers a webhook so they reach your server as they arrive, and `POST /api/v1/bots/deleteWebhook` removes it.

A project key reads room history: `GET /api/v1/apps/groups/:roomId/messages` with `messages:read`, newest first. A project-key poller should pick an interval that stays well inside the rate limits below, because every poll draws on the same budget as your writes.

### Rate limits and errors

The budget is per app, not per key and not per IP: every request with the same app credentials draws on one pool. The defaults are 600 requests per minute and 50,000 per day as of 10 September 2026, both windows rolling, reads costing the same as writes. Exceeding one returns `429` with a `Retry-After` header in seconds and a body naming the window: `{ "error": "RATE_LIMITED", "reason": "PER_MINUTE_LIMIT_EXCEEDED", "retryAfterSec": 60 }`. The other codes worth handling are `401`, `403 INSUFFICIENT_SCOPE`, `403 ROOM_NOT_MANAGED_BY_APP`, `403 CANNOT_MODERATE_OWNER` and `403 CANNOT_DELETE_ASSIGNED_ROOM`. Every error body carries an `error` code and a readable `message`. For higher limits, ask the Cherry team in its public [Telegram group](https://t.me/cherrydotfun).

## What is different from a general chat API

General chat APIs start from a user table: you create a user, store a token, and map it to your own account. The Cherry API starts from a wallet address, because a wallet is already the account: you pass `ownerWallet` and a list of member wallets, and Cherry resolves display names from SNS (`.sol`, `.sns`), `.skr` on Solana Seeker and other AllDomains names, with the avatar from the domain record.

Three consequences follow. There is no user provisioning step and no identity store to keep in sync. Membership can be checked against the chain instead of your database, which is what [token-gated chat](https://cherry.fun/learn/features/token-gated-chat/) does with a `gatingRule` on the room. And the room needs no second system to reach your users: an embed on your page shows it to any viewer who is an active member.

The Bot API is Telegram-shaped by design, so a bot you have written before ports over, but the identity it acts as is a wallet.

## Who uses it and for what

A game backend runs a room per clan and a room per match. The clan room is long-lived and app-owned; the match room is created when the match starts with both rosters as `initialMembers` and deleted when the match ends. The [chat for web3 games guide](https://cherry.fun/learn/guides/chat-for-web3-games/) walks that pattern end to end.

A marketplace opens a room per order between buyer and seller; a support desk opens one per support thread. State changes post into each as the order or the thread moves. The thread becomes the dispute record, and because the app identity posts without joining, the room stays a two-party conversation.

A community team runs moderation with a bot: it long-polls `getUpdates`, deletes what breaks the rules, mutes repeat offenders and escalates a ban to a human. Cherry's server-side [anti-spam layers](https://cherry.fun/learn/features/anti-spam/) handle wallet-age gating and blocked words underneath, so the bot only encodes your own rules.

The fourth pattern is an AI agent in chat: a bot key, inline buttons, and the same wallet identity every member has.

## Limits and honest caveats

Group chats on Cherry are not end-to-end encrypted, by design, because moderation and bots need to read them. End-to-end encryption covers DMs between people, with keys derived from the wallet.

Your project can only manage the rooms it created, and deleting a room you did not create returns `403 CANNOT_DELETE_ASSIGNED_ROOM`. The three owner capabilities, `allowOwnerDetach`, `allowOwnerDelete` and `allowOwnerTransfer`, default to `false`, so set them at creation if the owner wallet should be able to detach, delete or transfer the room.

Pairing a room with the [embeddable chat widget](https://cherry.fun/learn/features/embeddable-chat/) works in `single` mode only: the `list` and `external-controlled` modes, and the `setRoom()` call with them, are documented ahead of runtime support and behave like `single` today.

Every wallet field in the API docs and examples is a Solana address. Legacy `cha_` keys still resolve during a migration grace window, but issue new keys in the `cherry_sk_` and `cherry_bot_` formats.

## How to start

1. Sign in at [portal.cherry.fun](https://portal.cherry.fun) with a Solana wallet and create a project, as [the developer portal guide](https://cherry.fun/learn/guides/cherry-portal-for-developers/) sets out.
2. In the project's **API keys** section, create a project key with only the scopes you need, and copy the token: it is shown once.
3. Call `GET /api/v1/apps/me` with it and check the `scopes` and `rateLimits` that come back.
4. Create your first room with `POST /api/v1/apps/groups`, passing `ownerWallet` and `title`, and store the returned `roomId` next to your own record.
5. Post into it with `POST /api/v1/apps/groups/:roomId/messages`, then surface it to users with the [chat widget in your dApp](https://cherry.fun/learn/guides/add-chat-to-your-dapp/) or inside [a Cherry mini app](https://cherry.fun/learn/features/mini-apps/).
6. Add a bot under **Bots** when you need a named identity, give its key the scopes it needs, promote its wallet in each room it moderates, and have it long-poll `getUpdates` or register a webhook with `setWebhook`.

Start at the [Cherry API endpoint reference](https://portal.cherry.fun/docs/api/reference) or hand your agent the [OpenAPI 3.1 spec](https://portal.cherry.fun/openapi.json) and let it generate the client.

## FAQ

### What is the Cherry API?

The Cherry API is a REST API your backend calls to manage Cherry chat programmatically. It has two surfaces: the Apps API at `https://api.cherry.fun/api/v1/apps`, with a project key, which manages rooms, members and messages your project owns; and the Bot API at `https://api.cherry.fun/api/v1/bots`, with a bot key, which acts as a named bot with its own wallet.

### How do I send a message as a bot in a group chat?

Two ways. With a project key, POST to `/api/v1/apps/groups/:roomId/messages` and pass a `botId` in the body to speak as one of your project's bots; leave `botId` out and the message comes from your project's app identity. With a bot key, POST to `/api/v1/bots/sendGroupMessage` with the `bots:groups:send` scope. The sender is resolved on Cherry's servers, so no caller can impersonate a user.

### Does the Cherry chat API support webhooks?

Yes, for bots. A bot key with the `bots:webhook:manage` scope registers a webhook with `POST /api/v1/bots/setWebhook`, removes it with `POST /api/v1/bots/deleteWebhook`, and receives new messages as they arrive. Without one, the bot long-polls `GET /api/v1/bots/getUpdates` under `bots:updates:poll`, and a project key can poll `GET /api/v1/apps/groups/:roomId/messages` with `messages:read`.

### Is the Cherry API free?

Yes. The Cherry API, bots, the Chat Embed SDK and Cherry Portal are free. Cherry's only charges are a share of paid-community payments, the paid-DM fee and a referral share on in-chat swaps, and none of them apply to the developer tools.

### What are the Cherry API rate limits?

600 requests per minute and 50,000 per day per app by default, as of 10 September 2026. The budget is shared by every key the app holds, the windows are rolling rather than calendar-based, and reads count the same as writes. Over the limit you get `429` with a `Retry-After` header and a body naming the window you hit.

### Is there an OpenAPI spec for the Cherry chat API?

Yes, an OpenAPI 3.1 document at `https://portal.cherry.fun/openapi.json`. It covers every endpoint on both surfaces, both bearer schemes, and the scope each operation requires, so you can import it into Postman or generate a client.

### Can a bot moderate a room my project does not own?

Yes, if the room's owner or an admin invites the bot's wallet and promotes it to moderator or admin. Bot moderation passes two independent checks: the `bots:groups:moderate` scope authorizes the call, and the bot's in-room role authorizes the action on other members. A bot with the scope but no role is denied with `403 INSUFFICIENT_ROOM_ROLE`.

### Do my users need a Cherry account to use a room the API created?

No. Their Solana wallet is the account: `initialMembers` invites the wallets you pass and accepts them on creation, and an embed on your page shows them the room once they are active members.

## Sources

- [Cherry API authentication: key formats, /me and rotation](https://portal.cherry.fun/docs/api/authentication)
- [Cherry API scopes](https://portal.cherry.fun/docs/api/scopes)
- [Create rooms and attach them to embeds](https://portal.cherry.fun/docs/api/rooms)
- [Messages and bot actions](https://portal.cherry.fun/docs/api/messages)
- [Members and moderation](https://portal.cherry.fun/docs/api/members)
- [Rate limits and errors](https://portal.cherry.fun/docs/api/rate-limits)
- [Cherry API endpoint reference](https://portal.cherry.fun/docs/api/reference)
- [Cherry API OpenAPI 3.1 spec](https://portal.cherry.fun/openapi.json)
- [Guide: build a moderation bot](https://portal.cherry.fun/docs/guides/moderation-bot)
- [Guide: a room per game, match or order](https://portal.cherry.fun/docs/guides/room-per-entity)
- [Cherry developer support on Telegram](https://portal.cherry.fun/docs/support)

