Skip to main content
Goals are explicit user configuration. They live in their own PostgreSQL tables, separately from observed health records. The read-only viewing UI can display goals and progress; clients create or change them through REST or MCP. health_get_goal_catalog / GET /v1/goals/catalog returns these definitions. The metric registry is the extension point for future goal types. Each definition fixes its unit, comparison and period; clients cannot change nutrition limits into minimum targets.

Setting and managing goals

health_set_goal / POST /v1/goals accepts metric, target, expected_version, optional unit, and a weight-only baseline. Targets and baselines must be finite, positive values up to 1e12. There is one persistent goal identity per metric. Use expected_version: 0 for its initial creation and the current version for later edits or reactivation. A conflicting edit returns VERSION_CONFLICT with current_version. An archived goal retains its identity and version. For example, this MCP request records an illustrative water target supplied by a user:
For REST, put the retry key in the Idempotency-Key header and omit idempotency_key from the body. As with recorded observations, a committed retry returns the original immutable goal snapshot, even after later edits. Reusing a key with a different request fails. Goal writes share the record write lock and commit their current projection, revision and retry snapshot atomically. Weight needs an explicit baseline on creation. The baseline represents the start of the user’s weight goal; the service does not infer it from their records. Editing the target retains this baseline unless the user supplies a replacement. A supplied baseline uses the request’s unit. Returned targets and baselines always use kg.
  • health_list_goals / GET /v1/goals: current active goals, or status=archived / status=all.
  • health_get_goal / GET /v1/goals/{id}: current goal; optional include_history=true, history_limit (1–100) and history_before_version for immutable revisions.
  • health_archive_goal / POST /v1/goals/{id}/archive: expected_version and a retry key; preserves all history.
Changes take effect on the server’s current local date in DEFAULT_TIMEZONE. The latest revision on that date applies to the whole day; edits do not alter earlier days. Archival removes the goal starting that day. Future scheduling and backdating are not exposed in this version. Goal revisions and health observations are read in one repeatable-read transaction for progress. All routes require Bearer authentication. MCP goal reads require health:read; goal writes require health:write. The viewing UI uses a separate read-only browser session; it has no goal or record mutation screens.

Reading progress

health_get_goal_progress({"date":"2026-10-05"}) / GET /v1/days/2026-10-05/goal-progress returns goals effective on that day, with actual, ratio, progress_percent, remaining, over_by, status, coverage, basis, source_ids, observed_on and warnings.
  • Nutrition uses limit utilization: actual / limit. Water and exercise use the same ratio for target completion. The ratio can exceed one; the display percentage is clamped to 0–100. Over-limit nutrition has over_limit status and an explicit overage. A partial subtotal below the limit has below_limit status, which does not assert that the complete day was within its limit.
  • Weight uses (actual - baseline) / (target - baseline). This supports both gain and loss; progress away from the target has a negative ratio and a zero display percentage. Reaching or crossing the target in the intended direction is met. When baseline equals target, only an equal observation is treated as met.
  • Unknown is null, including its display percentage. No records are interpreted as zero. An explicitly reported zero total is a known zero.
  • Nutrition and hydration reuse the existing summary rules, including daily-total precedence, kJ-to-kcal conversion, qualified values, incompatible definitions, excluded records and estimates. Calories exclude supplement contributions. Water includes logged water through oral/enteral routes.
  • Calories burned mean active energy. Gross/unknown workout energy and daily total energy are not substituted. A valid reported active daily total wins over workout subtotals; the two are never added.
  • Active minutes come from explicitly supplied exercise_seconds in workout metrics or daily_totals, divided by 60. Elapsed and moving time are not substituted. Workout exercise duration cannot exceed an explicitly supplied elapsed duration. Unresolved overlapping workouts produce unknown exercise progress unless a usable daily total provides the value.
  • Weight uses the latest active, valid, exact scalar weight on or before the requested date, converting lb to kg. Qualified, unsupported-unit and field-invalid values are skipped. The response retains the observation date so consumers can identify stale measurements.
The response is bounded to 200 goal metrics, 1000 day records and 1000 excluded weight candidates before a usable observation. Exceeding a necessary bound fails explicitly instead of returning partial progress. Historical progress uses the current corrected observation ledger and historical goal configuration; it is not a frozen report of past record versions.

Operations and verification

Migration 0009 adds goals, goal_revisions and goal_idempotency_requests. Existing records are unchanged. The standard migration entrypoint applies it before API startup; readiness checks all three tables. Operator exports and permanent erasure include goal data. PostgreSQL backups retain goal history and retry snapshots. Run npm run test:goals for disposable PostgreSQL verification of both transports, timezone boundaries, historical revisions, concurrent edits, retry replay, persistence, export, backup/restore and erasure. tests/goals.test.ts checks progress calculations and observation exclusions. OpenAPI and Postman files are generated from the shared operation registry with npm run docs:generate. The MCP surface contains 27 tools including goals and attachments; the remote discovery size checks still apply.