List notes
GET /public/v1/clients/{clientId}/notes
Authorization: Bearer <access_token>
client_notes:read
| Parameter | Type | Notes |
|---|---|---|
limit | integer | Cursor page size |
cursor | string | Cursor returned by the previous response |
filter | string | all, notes, pinned, or shared |
| 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. |
curl -H "Authorization: Bearer $FITSOCIETY_ACCESS_TOKEN" \
"https://api.fitsociety.io/public/v1/clients/64b64c0f2f5f4c0012345678/notes?limit=25&filter=all"
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);
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"]
{
"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
POST /public/v1/clients/{clientId}/notes
Authorization: Bearer <access_token>
Content-Type: application/json
client_notes:write
{
"title": "Check-in",
"notes": "Follow up next week.",
"visibleToClient": false,
"pinned": false,
"tags": ["follow-up"]
}
| 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. |
| 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. |
| 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. |
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"]
}'
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);
}
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"])
201 Created):
{
"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
PATCH /public/v1/clients/{clientId}/notes/{noteId}
Authorization: Bearer <access_token>
Content-Type: application/json
client_notes:write
{
"pinned": true
}
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
DELETE /public/v1/clients/{clientId}/notes/{noteId}
Authorization: Bearer <access_token>
client_notes:write
Deleting a note follows the existing FITsociety note behavior: the embedded
note is removed and the operation is irreversible.
data
payload. Invalid client or note IDs return client errors; inaccessible notes
return not found.