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

# Groups

> Create and manage groups: membership, roles, join requests, agents, and the group's proposals.

A **group** is a trading circle: members open and follow **proposals** inside it. This guide covers
the group endpoints: list your groups, join and leave, run a group you create, manage members and
agents, and read a group's open proposals. For the model behind groups and proposals, read
[Groups & proposals](/concepts/groups-and-proposals) first.

<Note>
  Every monetary value on the wire is a **full-precision decimal string** in USD. Group fees
  (`joiningFee`, `subscriptionFee`) are whole-dollar amounts sent as strings (`"0"`, `"10"`). Never send a number or raw base units. See
  [Money & precision](/concepts/money-and-precision).
</Note>

## Membership

### List your groups

`GET /api/v1/groups` returns only **your** groups — the ones you're a member of, like
`/positions` and `/trades`. There is deliberately no endpoint to discover other people's groups;
private group internals stay private.

```bash theme={null}
curl "$MURMO_BASE/api/v1/groups" -H "Authorization: Bearer $MURMO_API_KEY"
```

`data` is an **array** of `Group` objects, enriched per-user (your `performanceRank`, unread
counts, etc.). The money/percent fields are decimal strings:

| Field                                                             | Type                  | Notes                                                         |
| ----------------------------------------------------------------- | --------------------- | ------------------------------------------------------------- |
| `uniqueId`                                                        | string                | The group id you pass as `{id}` everywhere below.             |
| `name`, `description`, `image`, `header`                          | string / null         | Display fields.                                               |
| `joiningFee`, `subscriptionFee`                                   | decimal string        | **Whole-dollar** fees, e.g. `"0"`, `"10"`.                    |
| `subscriptionPeriod`                                              | string / null         | Billing period for the subscription fee.                      |
| `groupAccessType`                                                 | enum                  | `PUBLIC` or `PRIVATE`.                                        |
| `defaultMemberType`                                               | enum                  | `LEADER` or `MEMBER` — the role new joiners get.              |
| `isActive`                                                        | boolean               | Whether the group is active.                                  |
| `performancePnl`                                                  | object / null         | `{ allTimePnl, pnl7Days, pnl30Days }`, each a decimal string. |
| `winRate`, `avgReturn`, `volatility`, `participationRate`         | decimal string / null | Percentages are decimal strings (`"42.7"` = 42.7%).           |
| `biggestWin`, `biggestLoss`                                       | decimal string / null | USD.                                                          |
| `tradeCount`, `predictionCount`, `memberCount`, `performanceRank` | integer / null        | Structural counts.                                            |
| `createdAt`, `updatedAt`                                          | date-time             | ISO timestamps.                                               |

### Join a group

`POST /api/v1/groups/{id}/join`. How it resolves depends on the group type and the body:

* **PUBLIC** group → you join immediately. Response `status` is `JOINED`, with your full
  `membership`.
* **PRIVATE** group → a join **request** is created for the leaders to approve. Response `status`
  is `REQUESTED`. Once a leader approves you, call join again with `{ "finalize": true }` to
  finalize the membership (`status: JOINED`).
* **`{ "inviteCode": "..." }`** → routes by the code and **ignores** the group type: a public
  invite joins you immediately (`JOINED`), a private invite creates a request (`REQUESTED`). The
  `{id}` in the path is not used to look up the group when an invite code is present.

<CodeGroup>
  ```bash Public — join now theme={null}
  curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/join" \
    -H "Authorization: Bearer $MURMO_API_KEY" -H "Content-Type: application/json" \
    -d '{}'
  ```

  ```bash Private — request to join theme={null}
  curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/join" \
    -H "Authorization: Bearer $MURMO_API_KEY" -H "Content-Type: application/json" \
    -d '{}'
  ```

  ```bash Private — finalize after approval theme={null}
  curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/join" \
    -H "Authorization: Bearer $MURMO_API_KEY" -H "Content-Type: application/json" \
    -d '{ "finalize": true }'
  ```

  ```bash By invite code theme={null}
  curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/join" \
    -H "Authorization: Bearer $MURMO_API_KEY" -H "Content-Type: application/json" \
    -d '{ "inviteCode": "ALPHA-XYZ" }'
  ```
</CodeGroup>

```json Response — joined theme={null}
{
  "data": {
    "status": "JOINED",
    "membership": {
      "userDynamicId": "usr_abc123",
      "groupId": "GROUP_ID",
      "memberType": "MEMBER",
      "subscriptionStatus": null,
      "nextChargeAt": null,
      "inviteCode": null,
      "autoBuyEnabled": false,
      "autoBuyAmountUsd": null,
      "mainWalletAddress": "5xY...wallet",
      "createdAt": "2026-06-03T09:30:00.000Z",
      "updatedAt": "2026-06-03T09:30:00.000Z",
      "group": { "uniqueId": "GROUP_ID", "name": "Alpha Callers", "...": "..." }
    }
  }
}
```

```json Response — requested (private) theme={null}
{
  "data": {
    "status": "REQUESTED",
    "groupId": "GROUP_ID",
    "userDynamicId": "usr_abc123",
    "memberType": "MEMBER"
  }
}
```

<Warning>
  The `status` field tells you what happened — branch on `JOINED` vs `REQUESTED`. Don't assume a
  join succeeded just because you got a `200`: a `REQUESTED` response means you're still waiting on a
  leader. A non-existent group id (without an invite code) returns `404`.
</Warning>

<Tip>
  If the group charges a `joiningFee` or `subscriptionFee`, joining settles that fee against your
  cash balance. Fees are whole-dollar strings; check them on the `Group` object before you join.
</Tip>

### Leave a group

`POST /api/v1/groups/{id}/leave` **queues** a background job that sells your positions in the group
and then removes your membership. It returns immediately with `status: "EXITING"` — the exit is not
instantaneous.

```bash theme={null}
curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/leave" \
  -H "Authorization: Bearer $MURMO_API_KEY"
```

```json Response theme={null}
{ "data": { "groupId": "GROUP_ID", "status": "EXITING" } }
```

### Pay your subscription

If your membership is past due, `POST /api/v1/groups/{id}/subscription` charges the subscription fee
against your cash balance and returns your refreshed member record (with `subscriptionStatus` and
`nextChargeAt`).

```bash theme={null}
curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/subscription" \
  -H "Authorization: Bearer $MURMO_API_KEY"
```

The response `data` is a `GroupMember` object.

## The group's proposals

`GET /api/v1/groups/{id}/proposals` returns every open call in the group — spot/perp and prediction
— as one combined object. Use it to see the group's open calls, or to act on one yourself.

```bash theme={null}
curl "$MURMO_BASE/api/v1/groups/GROUP_ID/proposals" \
  -H "Authorization: Bearer $MURMO_API_KEY"
```

```json Response (shape) theme={null}
{
  "data": {
    "tradingProposals": [ { "...": "spot/perp proposal + your position" } ],
    "predictionProposals": [ { "...": "prediction proposal + your position" } ]
  }
}
```

`data` is an **object** keyed by vertical, not an array. Each entry carries the proposal plus your
own position in it. To open or manage positions, see the per-vertical guides:
[Spot trading](/guides/spot-trading), [Prediction markets](/guides/prediction-markets), and
[Perpetuals](/guides/perpetuals).

<Warning>
  Every per-group read (proposals, agents, join-requests) is gated to members. If you're not in the
  group you get `403` with `code: NOT_GROUP_MEMBER`. See [Errors](/concepts/errors).
</Warning>

## Run your own group

### Create a group

`POST /api/v1/groups`. The caller becomes the group **ADMIN**. `name`, `joiningFee`, and
`groupAccessType` are required; fees are whole-dollar strings.

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST "$MURMO_BASE/api/v1/groups" \
    -H "Authorization: Bearer $MURMO_API_KEY" -H "Content-Type: application/json" \
    -d '{
      "name": "Alpha Callers",
      "description": "High-conviction spot calls.",
      "joiningFee": "0",
      "subscriptionFee": "10",
      "groupAccessType": "PRIVATE",
      "defaultMemberType": "MEMBER"
    }'
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch(`${process.env.MURMO_BASE}/api/v1/groups`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MURMO_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Alpha Callers",
      joiningFee: "0",
      subscriptionFee: "10",
      groupAccessType: "PRIVATE",
    }),
  });
  const { data } = await res.json();
  console.log(data.groupId);
  ```

  ```python Python theme={null}
  res = requests.post(
      f"{base}/api/v1/groups",
      headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"},
      json={
          "name": "Alpha Callers",
          "joiningFee": "0",
          "subscriptionFee": "10",
          "groupAccessType": "PRIVATE",
      },
  )
  print(res.json()["data"]["groupId"])
  ```
</CodeGroup>

```json Response theme={null}
{
  "data": {
    "groupId": "GROUP_ID",
    "membership": {
      "userDynamicId": "usr_abc123",
      "groupId": "GROUP_ID",
      "memberType": "ADMIN",
      "autoBuyEnabled": false,
      "autoBuyAmountUsd": null,
      "mainWalletAddress": "5xY...wallet",
      "createdAt": "2026-06-03T09:30:00.000Z",
      "updatedAt": "2026-06-03T09:30:00.000Z",
      "group": { "uniqueId": "GROUP_ID", "name": "Alpha Callers", "...": "..." }
    }
  }
}
```

| Body field                       | Required | Type           | Notes                                                    |
| -------------------------------- | -------- | -------------- | -------------------------------------------------------- |
| `name`                           | yes      | string         | Group display name.                                      |
| `joiningFee`                     | yes      | decimal string | Whole-dollar one-time join fee, e.g. `"0"`.              |
| `groupAccessType`                | yes      | enum           | `PUBLIC` or `PRIVATE`.                                   |
| `subscriptionFee`                | no       | decimal string | Whole-dollar recurring fee.                              |
| `defaultMemberType`              | no       | enum           | `LEADER` or `MEMBER` for new joiners (default `MEMBER`). |
| `description`, `header`, `image` | no       | string         | Display fields.                                          |

<Note>
  `joiningFee` is **required** and must be a whole-dollar string (use `"0"` for a free group).
  `name` and a valid `groupAccessType` are required too; missing or malformed values return `400`.
</Note>

### Update group settings

`PATCH /api/v1/groups/{id}` — **leaders and admins only**. Send only the fields you want to change.

```bash theme={null}
curl -X PATCH "$MURMO_BASE/api/v1/groups/GROUP_ID" \
  -H "Authorization: Bearer $MURMO_API_KEY" -H "Content-Type: application/json" \
  -d '{ "subscriptionFee": "15", "isActive": true }'
```

Accepts the same fields as create, plus `isActive` (boolean). Returns the updated `Group`. The body
fields are: `name`, `description`, `header`, `image`, `joiningFee`, `subscriptionFee`,
`groupAccessType`, `defaultMemberType`, `isActive` — all optional.

### Approve or reject join requests

`GET /api/v1/groups/{id}/join-requests` lists the pending `REQUESTED` members (leaders/admins only).

```bash theme={null}
curl "$MURMO_BASE/api/v1/groups/GROUP_ID/join-requests" \
  -H "Authorization: Bearer $MURMO_API_KEY"
```

```json Response theme={null}
{
  "data": [
    {
      "userDynamicId": "usr_pending1",
      "username": "newtrader",
      "avatar": null,
      "groupId": "GROUP_ID",
      "requestedAt": "2026-06-02T18:00:00.000Z"
    }
  ]
}
```

Resolve one with `POST /api/v1/groups/{id}/join-requests/{userId}/approve`. The default action
approves; send `{ "action": "reject" }` to reject.

<CodeGroup>
  ```bash Approve theme={null}
  curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/join-requests/usr_pending1/approve" \
    -H "Authorization: Bearer $MURMO_API_KEY" -H "Content-Type: application/json" \
    -d '{}'
  ```

  ```bash Reject theme={null}
  curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/join-requests/usr_pending1/approve" \
    -H "Authorization: Bearer $MURMO_API_KEY" -H "Content-Type: application/json" \
    -d '{ "action": "reject" }'
  ```
</CodeGroup>

```json Response — approved theme={null}
{
  "data": {
    "status": "APPROVED",
    "groupId": "GROUP_ID",
    "userDynamicId": "usr_pending1",
    "memberType": "MEMBER",
    "autoJoined": false
  }
}
```

```json Response — rejected theme={null}
{ "data": { "status": "REJECTED", "success": true } }
```

<Note>
  After approval, the requester still finalizes their own membership — either it auto-joins
  (`autoJoined: true` in the response) or they call join with `{ "finalize": true }`.
</Note>

### Kick and promote members

Both are **admin-only** and take the target member's id in the path.

```bash theme={null}
# Kick a member
curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/members/usr_target/kick" \
  -H "Authorization: Bearer $MURMO_API_KEY"

# Promote MEMBER -> LEADER
curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/members/usr_target/promote" \
  -H "Authorization: Bearer $MURMO_API_KEY"
```

```json Kick response theme={null}
{ "data": { "success": true } }
```

`promote` returns the updated `GroupMember` (now with `memberType: "LEADER"`).

### Group agents

Agents are bots a group admin can switch on inside a group. `GET /api/v1/groups/{id}/agents` lists
the agents configured for the group (members only).

```bash theme={null}
curl "$MURMO_BASE/api/v1/groups/GROUP_ID/agents" \
  -H "Authorization: Bearer $MURMO_API_KEY"
```

```json Response (shape) theme={null}
{
  "data": [
    {
      "id": "ga_1",
      "groupId": "GROUP_ID",
      "agentKey": "alpha-scout",
      "status": "ACTIVE",
      "activatedBy": "usr_abc123",
      "activatedAt": "2026-06-01T12:00:00.000Z",
      "Agent": { "key": "alpha-scout", "displayName": "Alpha Scout", "...": "..." }
    }
  ]
}
```

Enable or disable one with `POST /api/v1/groups/{id}/agents` (**admin only**). `agentKey` is
required; `action` defaults to `enable`.

<CodeGroup>
  ```bash Enable theme={null}
  curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/agents" \
    -H "Authorization: Bearer $MURMO_API_KEY" -H "Content-Type: application/json" \
    -d '{ "agentKey": "alpha-scout", "action": "enable" }'
  ```

  ```bash Disable theme={null}
  curl -X POST "$MURMO_BASE/api/v1/groups/GROUP_ID/agents" \
    -H "Authorization: Bearer $MURMO_API_KEY" -H "Content-Type: application/json" \
    -d '{ "agentKey": "alpha-scout", "action": "disable" }'
  ```
</CodeGroup>

```json Response — enable theme={null}
{
  "data": {
    "groupAgent": { "id": "ga_1", "groupId": "GROUP_ID", "agentKey": "alpha-scout", "status": "ACTIVE" },
    "paymentRequired": true,
    "pointsTransaction": { "...": "..." }
  }
}
```

<Note>
  Enabling an agent for the first time spends activation **points** from the admin's balance
  (`paymentRequired: true`, with a `pointsTransaction`). Insufficient points returns a `400`.
  Disabling returns `{ "data": { "groupAgent": { ... } } }`.
</Note>

<Warning>
  **Leader / admin gating.** Member-management and settings actions are restricted: create makes you
  ADMIN; `PATCH`, approve/reject, and join-request reads require a **leader or admin**; kick,
  promote, and agent enable/disable require an **admin**. Calling one without the role returns `403`.
  See [Errors](/concepts/errors).
</Warning>

## Field reference

### `GroupMember` (promote, subscription, and member responses)

| Field                    | Type                  | Notes                                               |
| ------------------------ | --------------------- | --------------------------------------------------- |
| `userDynamicId`          | string                | The member's user id.                               |
| `groupId`                | string                | The group.                                          |
| `memberType`             | enum                  | `ADMIN` (the group creator), `LEADER`, or `MEMBER`. |
| `username`, `avatar`     | string / null         | Display fields.                                     |
| `subscriptionStatus`     | string / null         | Subscription state.                                 |
| `nextChargeAt`           | date-time / null      | Next subscription charge.                           |
| `inviteCode`             | string / null         | The member's invite code, if any.                   |
| `autoBuyEnabled`         | boolean               | Whether auto-buy is enabled for this member.        |
| `autoBuyAmountUsd`       | decimal string / null | The auto-buy size in USD.                           |
| `createdAt`, `updatedAt` | date-time             | ISO timestamps.                                     |

### `Membership` (create / join responses)

Same as `GroupMember` but without `username`/`avatar`, plus `mainWalletAddress` (string / null) and
an optional nested `group` (`Group` object or null). On create, `memberType` is `ADMIN`.

## Where to next

<CardGroup cols={2}>
  <Card title="Groups & proposals" icon="users" href="/concepts/groups-and-proposals">
    The social model: groups, proposals, positions.
  </Card>

  <Card title="Spot trading" icon="arrow-right-arrow-left" href="/guides/spot-trading">
    Open, buy, sell, and close spot calls.
  </Card>

  <Card title="Prediction markets" icon="chart-line" href="/guides/prediction-markets">
    Predict YES/NO on real-world events.
  </Card>

  <Card title="Perpetuals" icon="gauge-high" href="/guides/perpetuals">
    Leverage, brackets, and live PnL.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/concepts/errors">
    Status codes and the `code`s worth handling.
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference">
    Every Groups endpoint, request, and response.
  </Card>
</CardGroup>
