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

# Habits and goals

> Read client habits, habit entries, and public-safe goal summaries through the FITsociety Public API.

Habit and goal endpoints expose a narrow public write surface. Habit reminder
mutation, habit definition changes, workout goals, nutrition goals, medical
goals, and raw objective answer payloads remain internal.

## Scopes

| Scope          | Allows                                     |
| :------------- | :----------------------------------------- |
| `habits:read`  | Read client habits and habit entries.      |
| `habits:write` | Create, update, and archive habit entries. |
| `goals:read`   | Read public-safe client goal summaries.    |
| `goals:write`  | Update public-safe goal fields.            |

## List client habits

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

Required scope: `habits:read`

Response fields:

| Field                          | Type   | Nullable | Description          |
| :----------------------------- | :----- | :------- | :------------------- |
| `data.habits[].habitId`        | string | no       | Habit ID.            |
| `data.habits[].name`           | string | no       | Habit name.          |
| `data.habits[].description`    | string | no       | Habit description.   |
| `data.habits[].habitType`      | string | no       | Habit type.          |
| `data.habits[].goalPeriod`     | string | no       | Goal period.         |
| `data.habits[].logMode`        | string | no       | Log mode.            |
| `data.habits[].goalValue`      | number | yes      | Goal value.          |
| `data.habits[].allowableValue` | number | yes      | Allowance/tolerance. |
| `data.habits[].unit`           | string | no       | Unit.                |
| `data.habits[].color`          | string | no       | Display color.       |

Not exposed: reminder settings, notification copy, actor IDs, deleted flags,
audit fields, or scheduling internals.

## List habit entries

```http theme={null}
GET /public/v1/clients/{clientId}/habits/entries?dateFrom=2026-07-01T00:00:00.000Z&dateTo=2026-07-31T23:59:59.999Z
Authorization: Bearer <access_token>
```

Required scope: `habits:read`

Validation:

| Parameter  | Type          | Required | Rule                                                  |
| :--------- | :------------ | :------- | :---------------------------------------------------- |
| `dateFrom` | ISO date-time | yes      | Valid date-time.                                      |
| `dateTo`   | ISO date-time | yes      | Valid date-time, must be after `dateFrom`.            |
| `habitId`  | string        | no       | Valid habit ObjectId belonging to the client/company. |

Range limit: maximum 92 days.

Response fields:

| Field                             | Type    | Nullable | Description                                         |
| :-------------------------------- | :------ | :------- | :-------------------------------------------------- |
| `data.truncated`                  | boolean | no       | Whether the internal max entries limit was reached. |
| `data.entries[].entryId`          | string  | no       | Entry ID.                                           |
| `data.entries[].habitId`          | string  | yes      | Habit ID.                                           |
| `data.entries[].occurredAt`       | string  | yes      | Occurrence timestamp.                               |
| `data.entries[].localDate`        | string  | no       | Canonical local date.                               |
| `data.entries[].periodKey`        | string  | no       | Period bucket key.                                  |
| `data.entries[].value`            | number  | yes      | Logged value.                                       |
| `data.entries[].unit`             | string  | no       | Unit at log time.                                   |
| `data.entries[].note`             | string  | no       | Entry note.                                         |
| `data.periodSummary[].habitId`    | string  | yes      | Habit ID.                                           |
| `data.periodSummary[].periodKey`  | string  | no       | Period key.                                         |
| `data.periodSummary[].entryCount` | integer | no       | Entries in that period.                             |
| `data.periodSummary[].totalValue` | number  | no       | Total logged value.                                 |

Not exposed: source, actor IDs, reminder metadata, audit fields, or deleted
entries.

<CodeGroup>
  ```bash cURL theme={null}
  curl -H "Authorization: Bearer $FITSOCIETY_ACCESS_TOKEN" \
    "https://api.fitsociety.io/public/v1/clients/64b64c0f2f5f4c0012345678/habits/entries?dateFrom=2026-07-01T00:00:00.000Z&dateTo=2026-07-31T23:59:59.999Z"
  ```

  ```javascript JavaScript theme={null}
  const clientId = "64b64c0f2f5f4c0012345678";
  const query = new URLSearchParams({
    dateFrom: "2026-07-01T00:00:00.000Z",
    dateTo: "2026-07-31T23:59:59.999Z",
  });

  const response = await fetch(
    `https://api.fitsociety.io/public/v1/clients/${clientId}/habits/entries?${query}`,
    {
      headers: {
        Authorization: `Bearer ${process.env.FITSOCIETY_ACCESS_TOKEN}`,
      },
    },
  );

  const body = await response.json();
  console.log(body.data.entries, body.data.periodSummary);
  ```

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

  client_id = "64b64c0f2f5f4c0012345678"
  response = requests.get(
      f"https://api.fitsociety.io/public/v1/clients/{client_id}/habits/entries",
      headers={"Authorization": f"Bearer {os.environ['FITSOCIETY_ACCESS_TOKEN']}"},
      params={
          "dateFrom": "2026-07-01T00:00:00.000Z",
          "dateTo": "2026-07-31T23:59:59.999Z",
      },
  )
  response.raise_for_status()

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

Response:

```json theme={null}
{
  "data": {
    "truncated": false,
    "entries": [
      {
        "entryId": "64b64c0f2f5f4c00123456a0",
        "habitId": "64b64c0f2f5f4c00123456a1",
        "occurredAt": "2026-07-14T18:00:00.000Z",
        "localDate": "2026-07-14",
        "periodKey": "2026-W29",
        "value": 2500,
        "unit": "ml",
        "note": "Water intake"
      }
    ],
    "periodSummary": [
      {
        "habitId": "64b64c0f2f5f4c00123456a1",
        "periodKey": "2026-W29",
        "entryCount": 1,
        "totalValue": 2500
      }
    ]
  },
  "meta": {
    "requestId": "req_0123456789abcdef",
    "rateLimit": { "limit": 10, "remaining": 9, "resetSeconds": 1 }
  }
}
```

## Create habit entry

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

Required scope: `habits:write`

Request body:

| Field        | Type              | Required    | Rule                                                                                                            |
| :----------- | :---------------- | :---------- | :-------------------------------------------------------------------------------------------------------------- |
| `occurredAt` | UTC ISO date-time | yes         | Must match `YYYY-MM-DDTHH:mm:ss(.SSS)Z`.                                                                        |
| `value`      | number/string     | conditional | Required for value-based habits. Localized numeric strings such as `"82,5"` are accepted. Must be non-negative. |
| `note`       | string            | no          | Maximum 1000 characters.                                                                                        |

Validation:

| Rule              | Behavior                                                                                           |
| :---------------- | :------------------------------------------------------------------------------------------------- |
| Client access     | Client must belong to the authenticated company.                                                   |
| Habit access      | Habit must belong to the client and company and must not be deleted.                               |
| Date window       | `occurredAt` must be within habit `startDate` and `endDate` when configured.                       |
| Specific weekdays | Habits configured for specific weekdays can only be logged on those days in the client's timezone. |
| Max value         | `value` cannot exceed the habit `allowableValue` when configured.                                  |
| Unknown fields    | Rejected with `PUBLIC_API_UNKNOWN_FIELDS`.                                                         |

Create is idempotent per `clientId`, `habitId`, and calculated `localDate`.
When an entry already exists for that local day, it is replaced instead of
creating a duplicate.

Response fields:

| Field                   | Type    | Nullable | Description                                           |
| :---------------------- | :------ | :------- | :---------------------------------------------------- |
| `data.entry.entryId`    | string  | no       | Habit entry ID.                                       |
| `data.entry.habitId`    | string  | yes      | Habit ID.                                             |
| `data.entry.occurredAt` | string  | yes      | Occurrence timestamp.                                 |
| `data.entry.localDate`  | string  | no       | Canonical local date in the client's timezone.        |
| `data.entry.periodKey`  | string  | no       | Period bucket key.                                    |
| `data.entry.value`      | number  | yes      | Logged value.                                         |
| `data.entry.unit`       | string  | no       | Unit at log time.                                     |
| `data.entry.note`       | string  | no       | Entry note.                                           |
| `data.created`          | boolean | no       | `true` when a new entry was created.                  |
| `data.replaced`         | boolean | no       | `true` when an existing local-day entry was replaced. |

## Update habit entry

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

Required scope: `habits:write`

Allowed fields: `occurredAt`, `value`, and `note`. At least one field is
required. The same date, weekday, note length, and numeric value validations as
Create habit entry apply.

Response fields:

| Field                   | Type   | Nullable | Description                                    |
| :---------------------- | :----- | :------- | :--------------------------------------------- |
| `data.entry.entryId`    | string | no       | Habit entry ID.                                |
| `data.entry.habitId`    | string | yes      | Habit ID.                                      |
| `data.entry.occurredAt` | string | yes      | Occurrence timestamp.                          |
| `data.entry.localDate`  | string | no       | Canonical local date in the client's timezone. |
| `data.entry.periodKey`  | string | no       | Period bucket key.                             |
| `data.entry.value`      | number | yes      | Logged value.                                  |
| `data.entry.unit`       | string | no       | Unit at log time.                              |
| `data.entry.note`       | string | no       | Entry note.                                    |

## Archive habit entry

```http theme={null}
PATCH /public/v1/clients/{clientId}/habits/entries/{entryId}/archive
Authorization: Bearer <access_token>
```

Required scope: `habits:write`

Archive is a soft delete. The entry is excluded from Public API read endpoints
after archiving.

Response fields:

| Field                  | Type    | Nullable | Description                             |
| :--------------------- | :------ | :------- | :-------------------------------------- |
| `data.entry.entryId`   | string  | no       | Habit entry ID.                         |
| `data.entry.archived`  | boolean | no       | Always `true` for successful responses. |
| `data.entry.updatedAt` | string  | yes      | Update timestamp.                       |

## Get client goals

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

Required scope: `goals:read`

Response fields:

| Field                                      | Type   | Nullable | Description                         |
| :----------------------------------------- | :----- | :------- | :---------------------------------- |
| `data.goals.physicalActivityGoal`          | string | no       | Public physical activity goal text. |
| `data.goals.currentWeightKg`               | number | yes      | Current weight goal value.          |
| `data.goals.targetWeightKg`                | number | yes      | Target weight goal value.           |
| `data.goals.timeline.startDate`            | string | yes      | Goal start date.                    |
| `data.goals.timeline.endDate`              | string | yes      | Goal end date.                      |
| `data.goals.dailyGoals.waterIntakeMl`      | number | yes      | Water intake goal.                  |
| `data.goals.dailyGoals.dailySteps`         | number | yes      | Daily step goal.                    |
| `data.goals.dailyGoals.sleepDurationHours` | number | yes      | Sleep duration goal.                |
| `data.goals.measurementGoals[].typeId`     | string | yes      | Measurement type ID.                |
| `data.goals.measurementGoals[].typeName`   | string | no       | Measurement type name.              |
| `data.goals.measurementGoals[].goal`       | number | yes      | Goal value.                         |
| `data.goals.measurementGoals[].unit`       | string | no       | Goal unit.                          |

Not exposed: nutrition plans, macro targets, calorie targets, medical details,
injuries, medications, allergies, raw ObjectiveQuestion documents, objective
answer payloads, or internal notes.

## Update client goals

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

Required scope: `goals:write`

Only these public fields are accepted:

| Field                           | Type          | Required | Rule                                                           |
| :------------------------------ | :------------ | :------- | :------------------------------------------------------------- |
| `physicalActivityGoal`          | string        | no       | `default`, `Lose Weight`, `Maintain Weight`, or `Gain Weight`. |
| `currentWeightKg`               | number/string | no       | Non-negative number. Localized numeric strings are accepted.   |
| `targetWeightKg`                | number/string | no       | Non-negative number.                                           |
| `timeline.startDate`            | date/null     | no       | `YYYY-MM-DD` or `null`.                                        |
| `timeline.endDate`              | date/null     | no       | `YYYY-MM-DD` or `null`.                                        |
| `dailyGoals.waterIntakeMl`      | number/string | no       | Non-negative number.                                           |
| `dailyGoals.dailySteps`         | number/string | no       | Non-negative number.                                           |
| `dailyGoals.sleepDurationHours` | number/string | no       | Non-negative number.                                           |

Unknown fields are rejected with `PUBLIC_API_UNKNOWN_GOAL_FIELDS`. Empty patches
return `NOTHING_TO_UPDATE`. Nested objects are flattened before validation, so
callers may send either nested JSON or dotted field paths when supported by
their client.

Response fields:

| Field                                      | Type      | Nullable | Description                                |
| :----------------------------------------- | :-------- | :------- | :----------------------------------------- |
| `data.goals.physicalActivityGoal`          | string    | no       | Public physical activity goal text.        |
| `data.goals.currentWeightKg`               | number    | yes      | Current weight goal value.                 |
| `data.goals.targetWeightKg`                | number    | yes      | Target weight goal value.                  |
| `data.goals.timeline.startDate`            | string    | yes      | Goal start date.                           |
| `data.goals.timeline.endDate`              | string    | yes      | Goal end date.                             |
| `data.goals.dailyGoals.waterIntakeMl`      | number    | yes      | Water intake goal.                         |
| `data.goals.dailyGoals.dailySteps`         | number    | yes      | Daily step goal.                           |
| `data.goals.dailyGoals.sleepDurationHours` | number    | yes      | Sleep duration goal.                       |
| `data.goals.measurementGoals[].typeId`     | string    | yes      | Measurement type ID.                       |
| `data.goals.measurementGoals[].typeName`   | string    | no       | Measurement type name.                     |
| `data.goals.measurementGoals[].goal`       | number    | yes      | Goal value.                                |
| `data.goals.measurementGoals[].unit`       | string    | no       | Goal unit.                                 |
| `data.updatedFields[]`                     | string\[] | no       | Internal field paths updated by the patch. |
| `data.updatedAt`                           | string    | yes      | Objective document update timestamp.       |

Goal writes deliberately exclude nutrition, macros, calories, workout-plan
targets, medical fields, injuries, medications, allergies, and raw objective
answer payloads.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH "https://api.fitsociety.io/public/v1/clients/64b64c0f2f5f4c0012345678/goals" \
    -H "Authorization: Bearer $FITSOCIETY_ACCESS_TOKEN" \
    -H "Idempotency-Key: goals-update-jane-20260823" \
    -H "Content-Type: application/json" \
    -d '{
      "physicalActivityGoal": "Lose Weight",
      "targetWeightKg": 78,
      "timeline": { "startDate": "2026-08-01", "endDate": "2026-12-01" },
      "dailyGoals": { "dailySteps": 10000, "waterIntakeMl": 2500 }
    }'
  ```

  ```javascript JavaScript theme={null}
  const clientId = "64b64c0f2f5f4c0012345678";
  const response = await fetch(
    `https://api.fitsociety.io/public/v1/clients/${clientId}/goals`,
    {
      method: "PATCH",
      headers: {
        Authorization: `Bearer ${process.env.FITSOCIETY_ACCESS_TOKEN}`,
        "Idempotency-Key": "goals-update-jane-20260823",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        physicalActivityGoal: "Lose Weight",
        targetWeightKg: 78,
        timeline: { startDate: "2026-08-01", endDate: "2026-12-01" },
        dailyGoals: { dailySteps: 10000, waterIntakeMl: 2500 },
      }),
    },
  );

  const body = await response.json();
  console.log(body.data.updatedFields, body.data.goals);
  ```

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

  client_id = "64b64c0f2f5f4c0012345678"
  response = requests.patch(
      f"https://api.fitsociety.io/public/v1/clients/{client_id}/goals",
      headers={
          "Authorization": f"Bearer {os.environ['FITSOCIETY_ACCESS_TOKEN']}",
          "Idempotency-Key": "goals-update-jane-20260823",
      },
      json={
          "physicalActivityGoal": "Lose Weight",
          "targetWeightKg": 78,
          "timeline": {"startDate": "2026-08-01", "endDate": "2026-12-01"},
          "dailyGoals": {"dailySteps": 10000, "waterIntakeMl": 2500},
      },
  )
  response.raise_for_status()

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

Response:

```json theme={null}
{
  "data": {
    "goals": {
      "physicalActivityGoal": "Lose Weight",
      "currentWeightKg": 82.5,
      "targetWeightKg": 78,
      "timeline": { "startDate": "2026-08-01", "endDate": "2026-12-01" },
      "dailyGoals": {
        "waterIntakeMl": 2500,
        "dailySteps": 10000,
        "sleepDurationHours": 8
      },
      "measurementGoals": []
    },
    "updatedFields": [
      "goals.physicalActivity",
      "weight.targetWeightKg",
      "timeline.startDate",
      "timeline.endDate",
      "dailyGoals.dailySteps",
      "dailyGoals.waterIntake"
    ],
    "updatedAt": "2026-08-23T10:00:00.000Z"
  },
  "meta": {
    "requestId": "req_0123456789abcdef",
    "rateLimit": { "limit": 10, "remaining": 8, "resetSeconds": 1 }
  }
}
```
