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

# Messaging

> Read sanitized client chat threads and message metadata through the FITsociety Public API.

Messaging endpoints are read-only in Public API v1. They expose chat and message
DTOs for integrations that need conversation context, without unread-state
internals, push tokens, participant auth fields, raw media URLs, moderation
state, or delivery provider payloads.

The authenticated company is always derived from the Bearer token. Requests must
not include `companyId`.

## Scopes

| Scope           | Allows                                                    |
| :-------------- | :-------------------------------------------------------- |
| `messages:read` | List client chat threads and read sanitized message DTOs. |

<Note>
  `messages:read` is consent-gated: the API client must be created with
  `"consents": { "privateCommunication": true }`. Without that stored consent,
  these endpoints return `403 scopes.private_communication_consent_required`.
  See [Authentication](/public-api/authentication#consent-gated-scopes).
</Note>

## List client chats

```http theme={null}
GET /public/v1/clients/{clientId}/chats?page=1&limit=50
Authorization: Bearer <access_token>
```

Required scope: `messages:read`

Validation:

| Parameter  | Type            | Required | Rule                                             |
| :--------- | :-------------- | :------- | :----------------------------------------------- |
| `clientId` | ObjectId string | yes      | Client must belong to the authenticated company. |
| `page`     | integer         | no       | Minimum `1`.                                     |
| `limit`    | integer         | no       | Default `50`, maximum `100`.                     |

Response fields:

| Field                                | Type    | Nullable | Description                         |
| :----------------------------------- | :------ | :------- | :---------------------------------- |
| `data.page`                          | integer | no       | Current page.                       |
| `data.limit`                         | integer | no       | Page size after server cap.         |
| `data.total`                         | integer | no       | Total chat threads for this client. |
| `data.totalPages`                    | integer | no       | Total pages, minimum `1`.           |
| `data.hasNextPage`                   | boolean | no       | Whether another page exists.        |
| `data.hasPrevPage`                   | boolean | no       | Whether a previous page exists.     |
| `data.chats[].chatId`                | string  | no       | Chat thread ID.                     |
| `data.chats[].createdAt`             | string  | yes      | Chat creation timestamp.            |
| `data.chats[].participants[]`        | array   | no       | Public participant summaries.       |
| `data.chats[].participants[].userId` | string  | no       | Participant user ID.                |
| `data.chats[].participants[].role`   | string  | no       | Participant role.                   |
| `data.chats[].participants[].name`   | string  | no       | Display name.                       |

Not exposed: participant email, device tokens, unread counters, muted states,
deletion markers, role permissions, or raw participant documents.

<CodeGroup>
  ```bash cURL theme={null}
  curl -H "Authorization: Bearer $FITSOCIETY_ACCESS_TOKEN" \
    "https://api.fitsociety.io/public/v1/clients/64b64c0f2f5f4c0012345678/chats?page=1&limit=50"
  ```

  ```javascript JavaScript theme={null}
  const clientId = "64b64c0f2f5f4c0012345678";
  const response = await fetch(
    `https://api.fitsociety.io/public/v1/clients/${clientId}/chats?page=1&limit=50`,
    {
      headers: {
        Authorization: `Bearer ${process.env.FITSOCIETY_ACCESS_TOKEN}`,
      },
    },
  );

  const body = await response.json();
  console.log(body.data.total, body.data.chats);
  ```

  ```python Python theme={null}
  import os
  import requests

  client_id = "64b64c0f2f5f4c0012345678"
  response = requests.get(
      f"https://api.fitsociety.io/public/v1/clients/{client_id}/chats",
      headers={"Authorization": f"Bearer {os.environ['FITSOCIETY_ACCESS_TOKEN']}"},
      params={"page": 1, "limit": 50},
  )
  response.raise_for_status()

  body = response.json()
  print(body["data"]["total"], body["data"]["chats"])
  ```
</CodeGroup>

Response:

```json theme={null}
{
  "data": {
    "page": 1,
    "limit": 50,
    "total": 1,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false,
    "chats": [
      {
        "chatId": "64b64c0f2f5f4c00123456f0",
        "createdAt": "2026-06-01T08:00:00.000Z",
        "participants": [
          {
            "userId": "64b64c0f2f5f4c0012345678",
            "role": "client",
            "name": "Jane Doe"
          },
          {
            "userId": "64b64c0f2f5f4c0012345671",
            "role": "coach",
            "name": "John Doe"
          }
        ]
      }
    ]
  },
  "meta": {
    "requestId": "req_0123456789abcdef",
    "rateLimit": { "limit": 10, "remaining": 9, "resetSeconds": 1 }
  }
}
```

## List chat messages

```http theme={null}
GET /public/v1/clients/{clientId}/chats/{chatId}/messages?page=1&limit=50
Authorization: Bearer <access_token>
```

Required scope: `messages:read`

Validation:

| Parameter  | Type            | Required | Rule                                                                  |
| :--------- | :-------------- | :------- | :-------------------------------------------------------------------- |
| `clientId` | ObjectId string | yes      | Client must belong to the authenticated company.                      |
| `chatId`   | ObjectId string | yes      | Chat must include the client and belong to the authenticated company. |
| `page`     | integer         | no       | Minimum `1`.                                                          |
| `limit`    | integer         | no       | Default `50`, maximum `100`.                                          |

Response fields:

| Field                        | Type    | Nullable | Description                               |
| :--------------------------- | :------ | :------- | :---------------------------------------- |
| `data.page`                  | integer | no       | Current page.                             |
| `data.limit`                 | integer | no       | Page size after server cap.               |
| `data.total`                 | integer | no       | Total matching messages.                  |
| `data.totalPages`            | integer | no       | Total pages, minimum `1`.                 |
| `data.hasNextPage`           | boolean | no       | Whether another page exists.              |
| `data.hasPrevPage`           | boolean | no       | Whether a previous page exists.           |
| `data.messages[].messageId`  | string  | no       | Message ID.                               |
| `data.messages[].senderRole` | string  | no       | Public sender role.                       |
| `data.messages[].type`       | string  | no       | Message type.                             |
| `data.messages[].text`       | string  | no       | Text body when the message is text-based. |
| `data.messages[].hasMedia`   | boolean | no       | Whether media is attached.                |
| `data.messages[].fileName`   | string  | no       | Safe file name when stored.               |
| `data.messages[].sentAt`     | string  | yes      | Sent timestamp.                           |
| `data.messages[].editedAt`   | string  | yes      | Last edit timestamp.                      |

Not exposed: raw attachment URLs, storage keys, upload metadata, push delivery
state, read receipts, internal moderation fields, deleted-message audit data,
provider payloads, or staff-only support context.

<CodeGroup>
  ```bash cURL theme={null}
  curl -H "Authorization: Bearer $FITSOCIETY_ACCESS_TOKEN" \
    "https://api.fitsociety.io/public/v1/clients/64b64c0f2f5f4c0012345678/chats/64b64c0f2f5f4c00123456f0/messages?page=1&limit=50"
  ```

  ```javascript JavaScript theme={null}
  const clientId = "64b64c0f2f5f4c0012345678";
  const chatId = "64b64c0f2f5f4c00123456f0";

  const response = await fetch(
    `https://api.fitsociety.io/public/v1/clients/${clientId}/chats/${chatId}/messages?page=1&limit=50`,
    {
      headers: {
        Authorization: `Bearer ${process.env.FITSOCIETY_ACCESS_TOKEN}`,
      },
    },
  );

  const body = await response.json();
  console.log(body.data.total, body.data.messages);
  ```

  ```python Python theme={null}
  import os
  import requests

  client_id = "64b64c0f2f5f4c0012345678"
  chat_id = "64b64c0f2f5f4c00123456f0"

  response = requests.get(
      f"https://api.fitsociety.io/public/v1/clients/{client_id}/chats/{chat_id}/messages",
      headers={"Authorization": f"Bearer {os.environ['FITSOCIETY_ACCESS_TOKEN']}"},
      params={"page": 1, "limit": 50},
  )
  response.raise_for_status()

  body = response.json()
  print(body["data"]["total"], body["data"]["messages"])
  ```
</CodeGroup>

Response:

```json theme={null}
{
  "data": {
    "page": 1,
    "limit": 50,
    "total": 1,
    "totalPages": 1,
    "hasNextPage": false,
    "hasPrevPage": false,
    "messages": [
      {
        "messageId": "64b64c0f2f5f4c00123456f1",
        "senderRole": "coach",
        "type": "text",
        "text": "See you at the 8:00 session tomorrow!",
        "hasMedia": false,
        "fileName": "",
        "sentAt": "2026-07-20T15:00:00.000Z",
        "editedAt": null
      }
    ]
  },
  "meta": {
    "requestId": "req_0123456789abcdef",
    "rateLimit": { "limit": 10, "remaining": 8, "resetSeconds": 1 }
  }
}
```

## Rate limits

Messaging endpoints use the standard Public API client rate limit: 10 requests
per second per OAuth client. Responses include the standard `RateLimit-*` and
`X-RateLimit-*` headers documented in [Contracts](/public-api/contracts).
