---
title: Longitudinal concepts
description: Score each new recording against a subject's own history.
type: doc
icon: trending-up
sidebar:
  label: Longitudinal concepts
  order: 6
  icon: trending-up
search:
  tags: [results]
---
Most analyze endpoints score one recording on its own. Longitudinal analysis asks a second question: **how does this recording compare to the same person's previous ones?**

A group is one subject's job history. Longitudinal analyze adds baseline, deviation, and trend next to the usual result.

Two ways to read a group:

| Endpoint | Answers |
|---|---|
| `POST .../analyze/longitudinal` | How does *this* recording compare to the subject's history? |
| `GET /v2/groups/{group_id}/longitudinal` | What does the whole trajectory look like so far? |

Request shapes, responses, and examples are in the [Longitudinal API](/reference/longitudinal-api). Create and manage groups with the [Groups API](/reference/groups).

> **Important**
> A group used with the longitudinal endpoints represents **one subject**. Personal baselines are per-subject, so a group that mixes people describes no one in particular. Amplifier stores no personal identifiers and cannot verify subject identity — keeping a group to a single subject is the caller's responsibility. For cohort-level statistics across many subjects, use the aggregate endpoint described in [Cohort aggregates](#cohort-aggregates).

## Concepts

**Group.** A caller-named container for jobs, scoped to your account. You choose the `group_id` — a stable identifier from your own system (a user id, a participant code) works well. Groups are created explicitly with `POST /v2/groups`, or implicitly the first time you submit audio to one.

**Raw vs. longitudinal analyze.** Every analyze endpoint has two group-aware forms:

- `.../groups/{group_id}/analyze` — registers the job into the group, but scores the recording on its own.
- `.../groups/{group_id}/analyze/longitudinal` — registers the job **and** scores it against the group's history.

Both contribute to the group's history once the job reaches `done`, and both cost the same — the two forms bill and rate-limit identically. The only difference is the response: the raw form returns the standard result, the longitudinal form adds the baseline and deviation fields. Use the raw form when a given call does not need them.

**Recording time.** Pass `recorded_at` when the audio was captured earlier than it was uploaded. Longitudinal ordering and time-decay use it; it falls back to the job's `created_at` when omitted. It is accepted as supplied, since capture time cannot be confirmed server-side.

**History window.** Group computations read a bounded window: completed jobs recorded within the last 270 days, capped at the 1,000 most recent. Trajectory fields (including `data_points`) use that window. `done_job_count` is the whole group. Recency-weighting applies inside the window: older points count less.

**Credits and rate limits.** Each analyze endpoint bills and rate-limits exactly like its own non-group counterpart: the model variants like [`POST /v2/models/{model_name}/analyze`](/reference/models), the sign variants like [`POST /v2/signs/{sign_name}/analyze`](/reference/signs). Group lifecycle, membership, and state endpoints consume no credits. See [Billing and Cost](/guides/billing#token-rates) and [Rate limits](/reference#rate-limits).

## Baseline and deviation

A longitudinal analyze result carries the recording's own `score`, `level`, and `flagged` unchanged, plus a set of fields that place the reading against the subject's own history:

- **`latest_score`** — the smoothed current estimate after this recording, precision- and time-weighted across the window. It may differ from the raw `score`.
- **`baseline_score`** — the subject's personal baseline, blended toward a population prior while personal history is still accumulating.
- **`deviation_from_baseline`** — `latest_score − baseline_score`. Positive means above the subject's own baseline.
- **`anomaly`** — `true` when the change against the subject's own norm is significant. Slower-moving signs need the change to persist across two distinct timepoints before this is set.
- **`z_score`** — deviation expressed in units of the subject's own variability.
- **`population_z`** — the subject's current position relative to the cohort. `null` when no population reference is available for a signal.

> **Note**
> All of the longitudinal fields above are `null` on the first submission to a group — with no prior history the result is equivalent to a one-off analysis. Detect that case by checking `baseline_score` for `null`, and treat the other longitudinal fields as available only when it is non-null. Baselines become meaningful as spaced recordings accumulate.

**Establishing a baseline.** The configured minimum is `3` independent readings (`min_data_points_required`). Signals with no population reference are held at `establishing_baseline` until it is met; where a population reference exists the baseline starts from the population prior and firms up as readings accumulate. Recordings taken close together pool into roughly one independent reading, so three recordings minutes apart count as about one. `baseline_personal_weight` reports how much of `baseline_score` comes from this subject rather than the population prior, which is useful for deciding when to present the baseline as "your usual".

## Trend

The trajectory endpoint returns a fitted trend per signal under `trajectory`:

- **`direction`** — `up`, `down`, `flat`, or `insufficient_data`. `flat` is returned unless the fitted change clears a deadband set by the signal's own variability. `insufficient_data` is returned until enough spaced readings are available to fit a trend.
- **`slope`** — change in score per day. `null` when `direction` is `insufficient_data`.
- **`resolvable`** — whether a trend was actually fitted. Gate any trend display on this rather than on `data_points_used`.
- **`data_points_used`** — time bins the trend was fitted over, not a raw job count. Populated even when `resolvable` is `false`.

The trajectory endpoint follows a polling pattern. The first call for a group — or the first after its membership changes — returns `status: "running"` while the trajectory is computed. Poll until `status` is `done`.

A signal with no usable readings in the window is omitted from `signals[]` entirely rather than returned with a status, so treat an absent signal as "nothing measured in this window" — it can disappear between polls if its readings age out.

**Signal naming.** For the `apex` and `harbor` models a few signs are reported under a model-specific name on job results and under the canonical name on the trajectory and aggregate endpoints, so match on the value those endpoints return rather than joining by the job result's name.

## Cohort aggregates

`GET /v2/groups/{group_id}/aggregate` is the many-subjects view. It describes the group as a population rather than tracking one person, so it is the right endpoint for a group that intentionally holds recordings from several people. It follows the same polling pattern as the trajectory endpoint, and its statistics count usable readings only, drawn from the same history window.

Per signal it returns the `distribution` of readings across `none`, `low`, `consider`, `moderate`, and `elevated` (values sum to `1.0`), `mean_score`, `std_score`, `flagged_rate`, and the `job_count` that produced a usable reading for that signal.

## Next steps

- [Longitudinal API](/reference/longitudinal-api) — raw analyze into a group, longitudinal analyze, trajectory, and aggregate.
- [Groups API](/reference/groups) — create groups and manage job membership.
- [Interpreting Results](/guides/interpreting-results) — the `level` and `recommended_action` fields that longitudinal results carry unchanged.
- [Response Schema](/reference/response-schema) — field definitions for the standard job result.
