---
title: Longitudinal API
description: Group-aware analyze, trajectory, and cohort aggregate endpoints.
type: doc
icon: trending-up
sidebar:
  label: Longitudinal API
  order: 5
  icon: trending-up
search:
  tags: [api]
---
These endpoints analyze audio in the context of a **group** and read that group's history.

A group is a caller-named container for jobs. Create and manage membership with the [Groups API](/reference/groups). For what a group is, how raw and longitudinal analyze differ, how `recorded_at` is used, and the history window, see [Longitudinal concepts](/guides/longitudinal#concepts).

| Call | What it does |
|---|---|
| Analyze into a group (raw) | Registers the job in the group and scores the recording on its own. |
| Longitudinal analyze | Registers the job and scores it against that subject's history. |
| Get group trajectory | Reads the whole trajectory so far for one subject. |
| Get group aggregate | Cohort statistics across many subjects. |

> **Note**
> 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 [`GET /v2/groups/{group_id}/aggregate`](#get-group-aggregate).

## Analyze into a group (model)

`POST /v2/models/{model_name}/groups/{group_id}/analyze`

Submit audio using a named model and register the job into a group. Returns a job object immediately; poll `GET /v2/jobs/{job_id}` or use a webhook for the completed result.

This is the raw group-aware form: it registers the job into the group — creating the group if it is new — and scores the recording on its own, without historical context. The completed result carries `result.summary` and `result.signals[]`.

### Path Parameters

| Parameter | Type | Description |
|---|---|---|
| `model_name` | string | The named model to run. See [Valid model names](/reference/models#valid-model-names). |
| `group_id` | string | The group to register the job into. Created if it is new. |

### Request Body

Requests use `Content-Type: multipart/form-data`.

| Field | Type | Required | Description |
|---|---|---|---|
| `audio` | file | No | Audio file (WAV, FLAC, MP3, or M4A). Max 32 MB per file. Provide this **or** `audio_upload_ref`. See [Audio Requirements](/guides/audio-requirements#file-size) for size and format limits. |
| `audio_upload_ref` | string | No | The `upload_ref` value from [`POST /v2/audio/uploads`](/reference/audio). Provide this **or** an `audio` file. |
| `recorded_at` | string | No | ISO 8601 time the audio was captured. Used for temporal ordering in longitudinal computation. Falls back to the job's `created_at` when omitted. Accepted as supplied, since capture time cannot be confirmed server-side. |
| `diarize` | boolean | No | Whether to apply speaker diarization. Default: false. See [Speaker Diarization](/guides/audio-requirements#speaker-diarization). |
| `webhook_url` | string | No | Per-request webhook URL, overriding the account-level webhook for this job. Optional on its own in the schema. When you set it, also send `webhook_secret_key`. |
| `webhook_secret_key` | string | No | Optional on its own in the schema. Required in practice when `webhook_url` is provided. Signs the webhook payload (HMAC-SHA256). See [Jobs, Polling, and Webhooks](/guides/jobs#webhooks). |

### Response

The standard job object described in [Jobs API](/reference/jobs#retrieve-a-job), plus two fields:

| Field | Type | Description |
|---|---|---|
| `group_id` | string | The group this job was registered into. |
| `recorded_at` | string or null | The capture time you supplied, or `null`. |

### Example

<CodeGroup param="lang">

```bash cURL
curl -X POST https://api.amplifierhealth.com/v2/models/pulse/groups/daily-checkins/analyze \
  -H "X-Account-ID: your-account-id" \
  -H "X-API-Key: your-api-key" \
  -F "audio=@recording.wav;type=audio/wav" \
  -F "recorded_at=2026-05-01T08:30:00Z"
```

```javascript JavaScript
const fs = require("fs");

const form = new FormData();
form.append("audio", new Blob([fs.readFileSync("recording.wav")], { type: "audio/wav" }), "recording.wav");
form.append("recorded_at", "2026-05-01T08:30:00Z");

const response = await fetch("https://api.amplifierhealth.com/v2/models/pulse/groups/daily-checkins/analyze", {
  method: "POST",
  headers: {
    "X-Account-ID": process.env.AMPLIFIER_ACCOUNT_ID,
    "X-API-Key": process.env.AMPLIFIER_API_KEY,
  },
  body: form,
});

const data = await response.json();
```

```python Python
# Requires httpx: pip install httpx
import os
import httpx

with open("recording.wav", "rb") as f:
    audio_bytes = f.read()

response = httpx.post(
  "https://api.amplifierhealth.com/v2/models/pulse/groups/daily-checkins/analyze",
  headers={
      "X-Account-ID": os.environ["AMPLIFIER_ACCOUNT_ID"],
      "X-API-Key": os.environ["AMPLIFIER_API_KEY"],
  },
  files={"audio": ("recording.wav", audio_bytes, "audio/wav")},
  data={
      "recorded_at": "2026-05-01T08:30:00Z"
  },
)

data = response.json()
```

</CodeGroup>

### Example response

```json
{
  "job_id": "job_aaa",
  "status": "queued",
  "group_id": "daily-checkins",
  "recorded_at": "2026-05-01T08:30:00Z",
  "created_at": "2026-05-01T09:14:00Z"
}
```

### Errors

Same codes as [`POST /v2/models/{model_name}/analyze`](/reference/models#analyze-with-a-model), plus the [group conditions](#group-error-conditions) below.

## Analyze into a group (sign)

`POST /v2/signs/{sign_name}/groups/{group_id}/analyze`

Submit audio for a single sign and register the job into a group. Returns a job object immediately.

Identical in behaviour, request body, and response to [`POST /v2/models/{model_name}/groups/{group_id}/analyze`](#analyze-into-a-group-model), with two differences: the path parameter is `sign_name` rather than `model_name`, and the completed result carries a singular `result.signal`.

### Path Parameters

| Parameter | Type | Description |
|---|---|---|
| `sign_name` | string | The sign to analyze. See [Valid sign names](/reference/signs#valid-sign-names). |
| `group_id` | string | The group to register the job into. Created if it is new. |

### Example

<CodeGroup param="lang">

```bash cURL
curl -X POST https://api.amplifierhealth.com/v2/signs/stress/groups/daily-checkins/analyze \
  -H "X-Account-ID: your-account-id" \
  -H "X-API-Key: your-api-key" \
  -F "audio=@recording.wav;type=audio/wav" \
  -F "recorded_at=2026-05-01T08:30:00Z"
```

```javascript JavaScript
const fs = require("fs");

const form = new FormData();
form.append("audio", new Blob([fs.readFileSync("recording.wav")], { type: "audio/wav" }), "recording.wav");
form.append("recorded_at", "2026-05-01T08:30:00Z");

const response = await fetch("https://api.amplifierhealth.com/v2/signs/stress/groups/daily-checkins/analyze", {
  method: "POST",
  headers: {
    "X-Account-ID": process.env.AMPLIFIER_ACCOUNT_ID,
    "X-API-Key": process.env.AMPLIFIER_API_KEY,
  },
  body: form,
});

const data = await response.json();
```

```python Python
# Requires httpx: pip install httpx
import os
import httpx

with open("recording.wav", "rb") as f:
    audio_bytes = f.read()

response = httpx.post(
  "https://api.amplifierhealth.com/v2/signs/stress/groups/daily-checkins/analyze",
  headers={
      "X-Account-ID": os.environ["AMPLIFIER_ACCOUNT_ID"],
      "X-API-Key": os.environ["AMPLIFIER_API_KEY"],
  },
  files={"audio": ("recording.wav", audio_bytes, "audio/wav")},
  data={
      "recorded_at": "2026-05-01T08:30:00Z"
  },
)

data = response.json()
```

</CodeGroup>

### Errors

Same codes as [`POST /v2/signs/{sign_name}/analyze`](/reference/signs#analyze-with-a-sign), plus the [group conditions](#group-error-conditions) below.

## Longitudinal analyze (model)

`POST /v2/models/{model_name}/groups/{group_id}/analyze/longitudinal`

Submit audio using a named model and score it against the group's history. Returns a job object immediately; the completed result carries baseline and deviation fields.

The job is registered into the group as well, so it becomes part of the history for later calls.

### Path Parameters

Same as [`POST /v2/models/{model_name}/groups/{group_id}/analyze`](#analyze-into-a-group-model).

### Request Body

Identical to [`POST /v2/models/{model_name}/groups/{group_id}/analyze`](#analyze-into-a-group-model).

### Response

The submission response is the same job object, with `group_id` and `recorded_at`. Once the job reaches `done`, each `result.signals[]` entry carries the longitudinal fields below. On sign jobs they appear on the singular `result.signal`.

| Field | Type | Description |
|---|---|---|
| `score`, `level`, `flagged` | — | Unchanged from any other job: this recording's own reading. See [Response Schema](/reference/response-schema#signals-array) and [Interpreting Results](/guides/interpreting-results#signal-levels). |
| `latest_score` | number or null | The smoothed current estimate after this recording — precision- and time-weighted across the window. May differ from the raw `score`. |
| `baseline_score` | number or null | The subject's personal baseline, blended toward a population prior while personal history is still accumulating. |
| `deviation_from_baseline` | number or null | `latest_score − baseline_score`. Positive means above the subject's own baseline. |
| `anomaly` | boolean or null | `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` | number or null | Deviation expressed in units of the subject's own variability. |
| `population_z` | number or null | 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.

### Example

<CodeGroup param="lang">

```bash cURL
curl -X POST https://api.amplifierhealth.com/v2/models/pulse/groups/subject-8f2a/analyze/longitudinal \
  -H "X-Account-ID: your-account-id" \
  -H "X-API-Key: your-api-key" \
  -F "audio=@recording.wav;type=audio/wav" \
  -F "recorded_at=2026-05-29T08:30:00Z"
```

```javascript JavaScript
const fs = require("fs");

const form = new FormData();
form.append("audio", new Blob([fs.readFileSync("recording.wav")], { type: "audio/wav" }), "recording.wav");
form.append("recorded_at", "2026-05-29T08:30:00Z");

const response = await fetch("https://api.amplifierhealth.com/v2/models/pulse/groups/subject-8f2a/analyze/longitudinal", {
  method: "POST",
  headers: {
    "X-Account-ID": process.env.AMPLIFIER_ACCOUNT_ID,
    "X-API-Key": process.env.AMPLIFIER_API_KEY,
  },
  body: form,
});

const data = await response.json();
```

```python Python
# Requires httpx: pip install httpx
import os
import httpx

with open("recording.wav", "rb") as f:
    audio_bytes = f.read()

response = httpx.post(
  "https://api.amplifierhealth.com/v2/models/pulse/groups/subject-8f2a/analyze/longitudinal",
  headers={
      "X-Account-ID": os.environ["AMPLIFIER_ACCOUNT_ID"],
      "X-API-Key": os.environ["AMPLIFIER_API_KEY"],
  },
  files={"audio": ("recording.wav", audio_bytes, "audio/wav")},
  data={
      "recorded_at": "2026-05-29T08:30:00Z"
  },
)

data = response.json()
```

</CodeGroup>

### Example response

Poll [`GET /v2/jobs/{job_id}`](/reference/jobs#retrieve-a-job) until `status` is `done`. The job read returns the standard job object — `group_id` and `recorded_at` are returned on the submission response, so keep them alongside the `job_id` you stored:

```json
{
  "job_id": "job_xxx",
  "status": "done",
  "created_at": "2026-05-29T09:00:00Z",
  "completed_at": "2026-05-29T09:00:45Z",
  "result": {
    "summary": { "...": "..." },
    "signals": [
      {
        "name": "stress",
        "score": 0.71,
        "level": "elevated",
        "flagged": true,
        "latest_score": 0.68,
        "baseline_score": 0.35,
        "deviation_from_baseline": 0.33,
        "anomaly": true,
        "z_score": 2.4,
        "population_z": 1.93
      }
    ],
    "audio_quality": { "...": "..." },
    "extended_metrics": { "...": "..." }
  }
}
```

### Errors

Same codes as [`POST /v2/models/{model_name}/analyze`](/reference/models#analyze-with-a-model), plus the [group conditions](#group-error-conditions) below.

## Longitudinal analyze (sign)

`POST /v2/signs/{sign_name}/groups/{group_id}/analyze/longitudinal`

Submit audio for a single sign and score it against the group's history.

Identical in behaviour, request body, and longitudinal fields to [`POST /v2/models/{model_name}/groups/{group_id}/analyze/longitudinal`](#longitudinal-analyze-model), with two differences: the path parameter is `sign_name` rather than `model_name`, and the longitudinal fields appear on the singular `result.signal`.

### Example

<CodeGroup param="lang">

```bash cURL
curl -X POST https://api.amplifierhealth.com/v2/signs/stress/groups/subject-8f2a/analyze/longitudinal \
  -H "X-Account-ID: your-account-id" \
  -H "X-API-Key: your-api-key" \
  -F "audio=@recording.wav;type=audio/wav" \
  -F "recorded_at=2026-05-29T08:30:00Z"
```

```javascript JavaScript
const fs = require("fs");

const form = new FormData();
form.append("audio", new Blob([fs.readFileSync("recording.wav")], { type: "audio/wav" }), "recording.wav");
form.append("recorded_at", "2026-05-29T08:30:00Z");

const response = await fetch("https://api.amplifierhealth.com/v2/signs/stress/groups/subject-8f2a/analyze/longitudinal", {
  method: "POST",
  headers: {
    "X-Account-ID": process.env.AMPLIFIER_ACCOUNT_ID,
    "X-API-Key": process.env.AMPLIFIER_API_KEY,
  },
  body: form,
});

const data = await response.json();
```

```python Python
# Requires httpx: pip install httpx
import os
import httpx

with open("recording.wav", "rb") as f:
    audio_bytes = f.read()

response = httpx.post(
  "https://api.amplifierhealth.com/v2/signs/stress/groups/subject-8f2a/analyze/longitudinal",
  headers={
      "X-Account-ID": os.environ["AMPLIFIER_ACCOUNT_ID"],
      "X-API-Key": os.environ["AMPLIFIER_API_KEY"],
  },
  files={"audio": ("recording.wav", audio_bytes, "audio/wav")},
  data={
      "recorded_at": "2026-05-29T08:30:00Z"
  },
)

data = response.json()
```

</CodeGroup>

### Errors

Same codes as [`POST /v2/signs/{sign_name}/analyze`](/reference/signs#analyze-with-a-sign), plus the [group conditions](#group-error-conditions) below.

## Get group trajectory

`GET /v2/groups/{group_id}/longitudinal`

Return the subject's trajectory over the group's history window: per-signal history, baseline, latest estimate, and trend.

This 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`.

Everything returned comes from the history window — completed jobs recorded within the last 270 days, capped at the 1,000 most recent — including `data_points`. The one exception is `done_job_count`, which counts every completed job in the group. See [Longitudinal concepts](/guides/longitudinal#concepts).

### Path Parameters

| Parameter | Type | Description |
|---|---|---|
| `group_id` | string | The group to read. |

### Query Parameters

| Parameter | Type | Required | Description |
|---|---|---|---|
| `signal` | string | No | Return only this signal, matched against `signals[].name`. Repeat the parameter for several signals. |
| `from` | string | No | ISO 8601 date. Show data points from this time onward. Filters the stored result for display; summary statistics are unchanged. |
| `to` | string | No | ISO 8601 date. Show data points up to this time. Filters the stored result for display; summary statistics are unchanged. |

### Response

| Field | Type | Description |
|---|---|---|
| `status` | string | `running` while computing, `done` when ready, `failed` if the computation could not complete. |
| `group_id` | string | The group read. |
| `computed_at` | string | When this result was computed. |
| `min_data_points_met` | boolean | Convenience roll-up over every signal in the group: `true` only when all of them have an established baseline, and `false` when the group has no signals yet. It is not narrowed by the `signal` filter, so read the per-signal `min_data_points_met` as the source of truth. |
| `min_data_points_required` | integer | The configured minimum number of independent readings (`3`). Signals with no population reference are held at `establishing_baseline` until it is met; where a population reference exists the baseline starts from it and firms up as readings accumulate. Read the per-signal `status` for where a given signal actually stands. Recordings taken close together pool into roughly one independent reading, so three recordings minutes apart count as about one. |
| `done_job_count` | integer | Completed jobs in the group. |
| `signals` | array | Per-signal trajectory objects, described below. |

Each object in `signals`:

| Field | Type | Description |
|---|---|---|
| `name` | string | Signal name. For the `apex` and `harbor` models a few signs are reported under a model-specific name on job results and under the canonical name here, so match on the value this endpoint returns rather than joining by the job result's name. |
| `data_points` | array | Usable readings ordered by recording time: `job_id`, `recorded_at`, `score`, `level`, `flagged`, `anomaly`. |
| `data_points[].anomaly` | boolean or null | The anomaly value recorded when that job was scored in this group, stored rather than recomputed. `null` means no anomaly was determined for that job and signal — either the job was added to the group after the fact and never scored in it, or it was scored with no basis for an anomaly yet: no prior history in the group, or no established baseline for that signal at the time. Jobs submitted through a raw group analyze endpoint are scored and do get a stored value; it is only their own returned result that omits the longitudinal fields. |
| `trajectory.direction` | string | `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. |
| `trajectory.slope` | number or null | Change in score per day. `null` when `direction` is `insufficient_data`. |
| `trajectory.resolvable` | boolean | Whether a trend was actually fitted. Gate any trend display on this rather than on `data_points_used`. |
| `trajectory.data_points_used` | integer | Time bins the trend was fitted over, not a raw job count. Populated even when `resolvable` is `false`. |
| `baseline_score` | number or null | The subject's personal baseline, blended toward a population prior while personal history is still accumulating. |
| `latest_score` | number or null | The smoothed current estimate. May differ from the most recent raw `score`. |
| `change_absolute` | number | Net change across the stored series in the window: last reading minus first. The `from` and `to` filters affect display only, not this value. |
| `flagged_rate` | number | Share of the stored readings that were flagged, `0`–`1`. Computed over the whole series in the window; the `from` and `to` filters do not narrow it. |
| `z_score` | number or null | Latest deviation in units of the subject's own variability. |
| `population_z` | number or null | The subject's current position relative to the cohort. `null` when no population reference is available. |
| `status` | string | `ok`, `establishing_baseline`, or `inconclusive`. `ok` once the baseline is established, and `establishing_baseline` while it is still forming. 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. `inconclusive` is reserved for a reading that could not be attributed to a known signal. |
| `min_data_points_met` | boolean | Whether this signal's baseline is established. |
| `baseline_personal_weight` | number or null | How much of `baseline_score` comes from this subject rather than the population prior, `0`–`1`. Useful for deciding when to present the baseline as "your usual". |

`level` values inside `data_points` are defined in [Interpreting Results](/guides/interpreting-results#signal-levels).

### Example

<CodeGroup param="lang">

```bash cURL
curl -X GET "https://api.amplifierhealth.com/v2/groups/daily-checkins/longitudinal?signal=stress" \
  -H "X-Account-ID: your-account-id" \
  -H "X-API-Key: your-api-key"
```

```javascript JavaScript
const response = await fetch("https://api.amplifierhealth.com/v2/groups/daily-checkins/longitudinal?signal=stress", {
  headers: {
    "X-Account-ID": process.env.AMPLIFIER_ACCOUNT_ID,
    "X-API-Key": process.env.AMPLIFIER_API_KEY,
  },
});

const data = await response.json();
```

```python Python
# Requires httpx: pip install httpx
import os
import httpx

response = httpx.get(
  "https://api.amplifierhealth.com/v2/groups/daily-checkins/longitudinal?signal=stress",
  headers={
      "X-Account-ID": os.environ["AMPLIFIER_ACCOUNT_ID"],
      "X-API-Key": os.environ["AMPLIFIER_API_KEY"],
  },
)

data = response.json()
```

</CodeGroup>

### Example response

```json
{
  "status": "done",
  "group_id": "daily-checkins",
  "computed_at": "2026-05-29T09:00:00Z",
  "min_data_points_met": true,
  "min_data_points_required": 3,
  "done_job_count": 10,
  "signals": [
    {
      "name": "stress",
      "data_points": [
        { "job_id": "job_aaa", "recorded_at": "2026-05-01T08:30:00Z", "score": 0.22, "level": "low",       "flagged": false, "anomaly": null  },
        { "job_id": "job_bbb", "recorded_at": "2026-05-15T08:30:00Z", "score": 0.61, "level": "moderate", "flagged": true,  "anomaly": false },
        { "job_id": "job_ccc", "recorded_at": "2026-05-29T08:30:00Z", "score": 0.81, "level": "elevated", "flagged": true,  "anomaly": false }
      ],
      "trajectory": { "direction": "up", "slope": 0.0211, "data_points_used": 3, "resolvable": true },
      "baseline_score": 0.28,
      "latest_score": 0.81,
      "change_absolute": 0.59,
      "flagged_rate": 0.67,
      "z_score": 1.9,
      "population_z": 2.37,
      "status": "ok",
      "min_data_points_met": true,
      "baseline_personal_weight": 0.37
    }
  ]
}
```

While the trajectory is still being computed:

```json
{ "status": "running", "group_id": "daily-checkins" }
```

### Errors

| HTTP | Condition |
|---|---|
| 400 | `from` or `to` is not a valid ISO 8601 datetime. |
| 404 | No group with this `group_id` exists for your account. |

A `200` response with `status: "failed"` carries an `error` object with `code` and `message`. The failed state is held for about five minutes before a retry triggers a fresh computation, so poll on that interval rather than in a tight loop.

## Get group aggregate

`GET /v2/groups/{group_id}/aggregate`

Return cohort-level statistics across the completed jobs in a group's history window: level distribution, mean, and spread per signal.

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

Statistics count usable readings only, drawn from the same history window as the trajectory endpoint.

### Path Parameters

| Parameter | Type | Description |
|---|---|---|
| `group_id` | string | The group to summarize. |

### Response

| Field | Type | Description |
|---|---|---|
| `status` | string | `running`, `done`, or `failed`. |
| `group_id` | string | The group read. |
| `computed_at` | string | When this result was computed. |
| `job_count` | integer | Completed jobs in the history window. |
| `signals` | array | Per-signal statistics, described below. |

Each object in `signals`:

| Field | Type | Description |
|---|---|---|
| `name` | string | Signal name, the same identifier the trajectory endpoint returns. For the `apex` and `harbor` models a few signs are reported under a model-specific name on job results and under the canonical name here, so match on the value this endpoint returns rather than joining by the job result's name. |
| `distribution` | object | Share of readings at each level — `none`, `low`, `consider`, `moderate`, `elevated`. Values sum to `1.0`. See [Interpreting Results](/guides/interpreting-results#signal-levels). |
| `mean_score` | number | Mean score across usable readings. |
| `std_score` | number | Standard deviation across the same readings. |
| `flagged_rate` | number | Share of readings flagged for this signal, `0`–`1`. |
| `job_count` | integer | Completed jobs that produced a usable reading for this signal. May be lower than the group's total. |

### Example

<CodeGroup param="lang">

```bash cURL
curl -X GET "https://api.amplifierhealth.com/v2/groups/study-cohort-a/aggregate" \
  -H "X-Account-ID: your-account-id" \
  -H "X-API-Key: your-api-key"
```

```javascript JavaScript
const response = await fetch("https://api.amplifierhealth.com/v2/groups/study-cohort-a/aggregate", {
  headers: {
    "X-Account-ID": process.env.AMPLIFIER_ACCOUNT_ID,
    "X-API-Key": process.env.AMPLIFIER_API_KEY,
  },
});

const data = await response.json();
```

```python Python
# Requires httpx: pip install httpx
import os
import httpx

response = httpx.get(
  "https://api.amplifierhealth.com/v2/groups/study-cohort-a/aggregate",
  headers={
      "X-Account-ID": os.environ["AMPLIFIER_ACCOUNT_ID"],
      "X-API-Key": os.environ["AMPLIFIER_API_KEY"],
  },
)

data = response.json()
```

</CodeGroup>

### Example response

```json
{
  "status": "done",
  "group_id": "study-cohort-a",
  "computed_at": "2026-05-28T10:05:00Z",
  "job_count": 150,
  "signals": [
    {
      "name": "stress",
      "distribution": {
        "none": 0.32,
        "low": 0.22,
        "consider": 0.21,
        "moderate": 0.18,
        "elevated": 0.07
      },
      "mean_score": 0.41,
      "std_score": 0.18,
      "flagged_rate": 0.46,
      "job_count": 150
    }
  ]
}
```

### Errors

| HTTP | Condition |
|---|---|
| 404 | No group with this `group_id` exists for your account. |

## Group error conditions

Analyze endpoints return the same audio and auth codes as [`POST /v2/models/{model_name}/analyze`](/reference/models#analyze-with-a-model). Shared codes: [Errors](/reference/errors#error-codes).

Group-specific conditions:

| HTTP | Condition |
|---|---|
| 400 | `from` or `to` is not a valid ISO 8601 datetime. |
| 404 | The group does not exist for your account, or a `group_id` in the path is malformed. |

For the full list and retry guidance, see [Errors](/reference/errors#error-codes).

## Related pages

- [Groups API](/reference/groups) — create groups and manage job membership.
- [Longitudinal concepts](/guides/longitudinal) — groups, baselines, deviation, trend, and the history window as concepts.
- [Billing and Cost](/guides/billing#token-rates) — group analyze endpoints bill exactly like their non-group counterparts; group lifecycle, membership, and state endpoints consume no credits.
- [Response Schema](/reference/response-schema) — the standard fields every job result carries.
