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

# Client Notes

> Read and manage client notes through the FITsociety Public API.

Client notes can contain private coaching context. Grant note scopes only to
trusted server-to-server integrations.

## List notes

```http theme={null}
GET /public/v1/clients/{clientId}/notes
Authorization: Bearer <access_token>
```

Required scope:

```txt theme={null}
client_notes:read
```

Query parameters:

| Parameter | Type    | Notes                                    |
| :-------- | :------ | :--------------------------------------- |
| `limit`   | integer | Cursor page size                         |
| `cursor`  | string  | Cursor returned by the previous response |
| `filter`  | string  | `all`, `notes`, `pinned`, or `shared`    |

The response returns sanitized note objects. Internal sort fields, coach avatar
URLs, and attachment media URLs are not included.

Output fields:

| Field                                   | Type      | Nullable | Description                                            |
| :-------------------------------------- | :-------- | :------- | :----------------------------------------------------- |
| `data.notes[].noteId`                   | string    | no       | Note ID.                                               |
| `data.notes[].title`                    | string    | no       | Note title.                                            |
| `data.notes[].notes`                    | string    | no       | Full note text.                                        |
| `data.notes[].date`                     | string    | yes      | Note date.                                             |
| `data.notes[].editedAt`                 | string    | yes      | Last edit timestamp.                                   |
| `data.notes[].pinned`                   | boolean   | no       | Pinned flag.                                           |
| `data.notes[].visibleToClient`          | boolean   | no       | Client visibility flag.                                |
| `data.notes[].tags[]`                   | string\[] | no       | Note tags.                                             |
| `data.notes[].mentions[].id`            | string    | no       | Mention ID when available.                             |
| `data.notes[].mentions[].name`          | string    | no       | Mention display name.                                  |
| `data.notes[].attachments[].documentId` | string    | yes      | Linked document ID.                                    |
| `data.notes[].attachments[].name`       | string    | no       | Attachment name.                                       |
| `data.notes[].attachments[].size`       | number    | no       | Attachment size.                                       |
| `data.notes[].attachments[].type`       | string    | no       | Attachment MIME/type string.                           |
| `data.notes[].coach.coachId`            | string    | yes      | Coach ID.                                              |
| `data.notes[].coach.name`               | string    | no       | Coach display name.                                    |
| `data.pagination.filter`                | string    | no       | Applied filter: `all`, `notes`, `pinned`, or `shared`. |
| `data.pagination.limit`                 | integer   | no       | Cursor page size after normalization.                  |
| `data.pagination.returnedItems`         | integer   | no       | Number of items returned in this page.                 |
| `data.pagination.totalItems`            | integer   | no       | Total items for the applied filter.                    |
| `data.pagination.nextCursor`            | string    | yes      | Cursor for the next page when available.               |
| `data.pagination.hasMore`               | boolean   | no       | Whether another cursor page exists.                    |
| `data.counts.all`                       | integer   | no       | Total notes including pinned and shared notes.         |
| `data.counts.notes`                     | integer   | no       | Non-pinned notes count.                                |
| `data.counts.pinned`                    | integer   | no       | Pinned notes count.                                    |
| `data.counts.shared`                    | integer   | no       | Client-visible notes count.                            |

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

  ```javascript JavaScript theme={null}
  const clientId = "64b64c0f2f5f4c0012345678";
  const headers = {
    Authorization: `Bearer ${process.env.FITSOCIETY_ACCESS_TOKEN}`,
  };

  // Cursor pagination: keep following nextCursor until hasMore is false.
  let cursor = "";
  do {
    const query = new URLSearchParams({ limit: "25", filter: "all" });
    if (cursor) query.set("cursor", cursor);

    const response = await fetch(
      `https://api.fitsociety.io/public/v1/clients/${clientId}/notes?${query}`,
      { headers },
    );
    const body = await response.json();

    console.log(body.data.notes);
    cursor = body.data.pagination.hasMore ? body.data.pagination.nextCursor : "";
  } while (cursor);
  ```

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

  client_id = "64b64c0f2f5f4c0012345678"
  headers = {"Authorization": f"Bearer {os.environ['FITSOCIETY_ACCESS_TOKEN']}"}

  # Cursor pagination: keep following nextCursor until hasMore is False.
  cursor = ""
  while True:
      params = {"limit": 25, "filter": "all"}
      if cursor:
          params["cursor"] = cursor

      response = requests.get(
          f"https://api.fitsociety.io/public/v1/clients/{client_id}/notes",
          headers=headers,
          params=params,
      )
      response.raise_for_status()
      body = response.json()

      print(body["data"]["notes"])
      if not body["data"]["pagination"]["hasMore"]:
          break
      cursor = body["data"]["pagination"]["nextCursor"]
  ```
</CodeGroup>

Response:

```json theme={null}
{
  "data": {
    "notes": [
      {
        "noteId": "64b64c0f2f5f4c0012345685",
        "title": "Check-in",
        "notes": "Follow up next week.",
        "date": "2026-08-20T09:00:00.000Z",
        "editedAt": null,
        "pinned": false,
        "visibleToClient": false,
        "tags": ["follow-up"],
        "mentions": [],
        "attachments": [],
        "coach": {
          "coachId": "64b64c0f2f5f4c0012345671",
          "name": "John Doe"
        }
      }
    ],
    "pagination": {
      "filter": "all",
      "limit": 25,
      "returnedItems": 1,
      "totalItems": 1,
      "nextCursor": null,
      "hasMore": false
    },
    "counts": { "all": 1, "notes": 1, "pinned": 0, "shared": 0 }
  },
  "meta": {
    "requestId": "req_0123456789abcdef",
    "rateLimit": { "limit": 10, "remaining": 9, "resetSeconds": 1 }
  }
}
```

## Create a note

```http theme={null}
POST /public/v1/clients/{clientId}/notes
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope:

```txt theme={null}
client_notes:write
```

```json theme={null}
{
  "title": "Check-in",
  "notes": "Follow up next week.",
  "visibleToClient": false,
  "pinned": false,
  "tags": ["follow-up"]
}
```

Request body fields:

| Field                      | Type                   | Required    | Rule                                                                                                                              |
| :------------------------- | :--------------------- | :---------- | :-------------------------------------------------------------------------------------------------------------------------------- |
| `title`                    | string                 | conditional | Trimmed string. A create request must contain at least one of `title`, `notes`, non-empty `tags[]`, or non-empty `attachments[]`. |
| `notes`                    | string                 | conditional | Trimmed string. A create request must contain at least one content field.                                                         |
| `visibleToClient`          | boolean                | no          | Preferred public field for client visibility.                                                                                     |
| `visibileToClient`         | boolean                | no          | Legacy misspelled alias accepted for backwards compatibility.                                                                     |
| `pinned`                   | boolean                | no          | Pinned flag.                                                                                                                      |
| `tags[]`                   | string\[]              | no          | Tags are trimmed, leading `#` is removed, empty tags are ignored, and duplicates are removed.                                     |
| `mentions[]`               | string\[] or object\[] | no          | Strings become `{ name }`; objects may use `id`/`_id` and `name`/`label`. Empty mentions are ignored.                             |
| `attachments[].documentId` | string                 | yes         | Optional linked document ID. Must be a valid ObjectId when supplied.                                                              |
| `attachments[].name`       | string                 | no          | Required for an attachment entry to be kept. Empty attachment entries are ignored.                                                |
| `attachments[].size`       | number                 | no          | Rounded positive number; invalid or missing values become `0`.                                                                    |
| `attachments[].type`       | string                 | no          | Attachment MIME/type string.                                                                                                      |

Validation:

| Rule          | Behavior                                                                                                                                                 |
| :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Client access | `clientId` must belong to the authenticated company. The request never accepts `companyId`.                                                              |
| Coach context | The OAuth client must have a usable default coach context for attribution.                                                                               |
| Content       | Create requires at least one content field: title, notes, tags, or attachments. Mentions alone are not enough.                                           |
| Field types   | Non-string `title`/`notes`, non-boolean visibility/pinned values, non-array tags/mentions/attachments, and invalid attachment document IDs are rejected. |
| Media URLs    | Attachment media values may be stored internally but are never returned by the Public API note DTO.                                                      |

Response fields:

| Field                                | Type      | Nullable | Description                  |
| :----------------------------------- | :-------- | :------- | :--------------------------- |
| `data.note.noteId`                   | string    | no       | Note ID.                     |
| `data.note.title`                    | string    | no       | Note title.                  |
| `data.note.notes`                    | string    | no       | Full note text.              |
| `data.note.date`                     | string    | yes      | Note date.                   |
| `data.note.editedAt`                 | string    | yes      | Last edit timestamp.         |
| `data.note.pinned`                   | boolean   | no       | Pinned flag.                 |
| `data.note.visibleToClient`          | boolean   | no       | Client visibility flag.      |
| `data.note.tags[]`                   | string\[] | no       | Note tags.                   |
| `data.note.mentions[].id`            | string    | no       | Mention ID when available.   |
| `data.note.mentions[].name`          | string    | no       | Mention display name.        |
| `data.note.attachments[].documentId` | string    | yes      | Linked document ID.          |
| `data.note.attachments[].name`       | string    | no       | Attachment name.             |
| `data.note.attachments[].size`       | number    | no       | Attachment size.             |
| `data.note.attachments[].type`       | string    | no       | Attachment MIME/type string. |
| `data.note.coach.coachId`            | string    | yes      | Coach ID.                    |
| `data.note.coach.name`               | string    | no       | Coach display name.          |

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://api.fitsociety.io/public/v1/clients/64b64c0f2f5f4c0012345678/notes" \
    -H "Authorization: Bearer $FITSOCIETY_ACCESS_TOKEN" \
    -H "Idempotency-Key: note-create-checkin-20260823" \
    -H "Content-Type: application/json" \
    -d '{
      "title": "Check-in",
      "notes": "Follow up next week.",
      "visibleToClient": false,
      "pinned": false,
      "tags": ["follow-up"]
    }'
  ```

  ```javascript JavaScript theme={null}
  const clientId = "64b64c0f2f5f4c0012345678";
  const response = await fetch(
    `https://api.fitsociety.io/public/v1/clients/${clientId}/notes`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.FITSOCIETY_ACCESS_TOKEN}`,
        "Idempotency-Key": "note-create-checkin-20260823",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        title: "Check-in",
        notes: "Follow up next week.",
        visibleToClient: false,
        pinned: false,
        tags: ["follow-up"],
      }),
    },
  );

  const body = await response.json();
  if (response.status === 201) {
    console.log("Created note", body.data.note.noteId);
  }
  ```

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

  client_id = "64b64c0f2f5f4c0012345678"
  response = requests.post(
      f"https://api.fitsociety.io/public/v1/clients/{client_id}/notes",
      headers={
          "Authorization": f"Bearer {os.environ['FITSOCIETY_ACCESS_TOKEN']}",
          "Idempotency-Key": "note-create-checkin-20260823",
      },
      json={
          "title": "Check-in",
          "notes": "Follow up next week.",
          "visibleToClient": False,
          "pinned": False,
          "tags": ["follow-up"],
      },
  )
  response.raise_for_status()

  body = response.json()
  if response.status_code == 201:
      print("Created note", body["data"]["note"]["noteId"])
  ```
</CodeGroup>

Response (`201 Created`):

```json theme={null}
{
  "data": {
    "note": {
      "noteId": "64b64c0f2f5f4c0012345685",
      "title": "Check-in",
      "notes": "Follow up next week.",
      "date": "2026-08-23T10:00:00.000Z",
      "editedAt": null,
      "pinned": false,
      "visibleToClient": false,
      "tags": ["follow-up"],
      "mentions": [],
      "attachments": [],
      "coach": {
        "coachId": "64b64c0f2f5f4c0012345671",
        "name": "John Doe"
      }
    }
  },
  "meta": {
    "requestId": "req_0123456789abcdef",
    "rateLimit": { "limit": 10, "remaining": 8, "resetSeconds": 1 }
  }
}
```

## Update a note

```http theme={null}
PATCH /public/v1/clients/{clientId}/notes/{noteId}
Authorization: Bearer <access_token>
Content-Type: application/json
```

Required scope:

```txt theme={null}
client_notes:write
```

```json theme={null}
{
  "pinned": true
}
```

Update accepts `title`, `notes`, `visibleToClient`, legacy
`visibileToClient`, `pinned`, `tags[]`, `mentions[]`, and `attachments[]` with
the validation rules described for create. Update requests are partial, but at
least one recognized mutable field must be supplied. The resulting note must
still contain content through title, notes, tags, or attachments; an update that
would leave the note empty is rejected.

Response fields:

| Field                                | Type      | Nullable | Description                  |
| :----------------------------------- | :-------- | :------- | :--------------------------- |
| `data.note.noteId`                   | string    | no       | Note ID.                     |
| `data.note.title`                    | string    | no       | Note title.                  |
| `data.note.notes`                    | string    | no       | Full note text.              |
| `data.note.date`                     | string    | yes      | Note date.                   |
| `data.note.editedAt`                 | string    | yes      | Last edit timestamp.         |
| `data.note.pinned`                   | boolean   | no       | Pinned flag.                 |
| `data.note.visibleToClient`          | boolean   | no       | Client visibility flag.      |
| `data.note.tags[]`                   | string\[] | no       | Note tags.                   |
| `data.note.mentions[].id`            | string    | no       | Mention ID when available.   |
| `data.note.mentions[].name`          | string    | no       | Mention display name.        |
| `data.note.attachments[].documentId` | string    | yes      | Linked document ID.          |
| `data.note.attachments[].name`       | string    | no       | Attachment name.             |
| `data.note.attachments[].size`       | number    | no       | Attachment size.             |
| `data.note.attachments[].type`       | string    | no       | Attachment MIME/type string. |
| `data.note.coach.coachId`            | string    | yes      | Coach ID.                    |
| `data.note.coach.name`               | string    | no       | Coach display name.          |

## Delete a note

```http theme={null}
DELETE /public/v1/clients/{clientId}/notes/{noteId}
Authorization: Bearer <access_token>
```

Required scope:

```txt theme={null}
client_notes:write
```

<Warning>
  Deleting a note follows the existing FITsociety note behavior: the embedded
  note is removed and the operation is irreversible.
</Warning>

The delete endpoint returns the standard success envelope with no `data`
payload. Invalid client or note IDs return client errors; inaccessible notes
return not found.
