Skip to content

Longitudinal API

Most analyze endpoints score one recording on its own. The Longitudinal API adds a second question: how does this recording compare to the same person's previous ones?

You collect recordings into a group, and Amplifier maintains a personal baseline for that group. Each new submission can then be scored against that history — returning a baseline, the deviation from it, and a trend — alongside the usual per-recording result.

Two ways to read a group:

EndpointAnswers
POST .../analyze/longitudinalHow does this recording compare to the subject's history?
GET /v2/groups/{group_id}/longitudinalWhat does the whole trajectory look like so far?

Info

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.


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.

History window. Group computations read a bounded window: completed jobs recorded within the last 270 days, capped at the 1,000 most recent. Everything the trajectory endpoint returns — data_points included — comes from that window (done_job_count excepted; it counts the whole group), and recency-weighting applies within it, so the oldest readings in the window influence the baseline least.

Credits and rate limits. Each analyze endpoint bills and rate-limits exactly like its own non-group counterpart: the model and use-case variants like POST /v2/models/{model_name}/analyze, the sign and condition variants like POST /v2/signs/{sign_name}/analyze. Group lifecycle, membership, and state endpoints consume no credits. See Billing & Cost.


POST /v2/groups

POST/v2/groups
Create a group explicitly, before any audio is submitted to it.

Creating a group up front is optional — the analyze endpoints create one implicitly when the group_id is new. Use this endpoint when you want to register the group as a discrete step.

Request Body

Requests use Content-Type: application/json.

API Parameters
FieldTypeRequiredDescription
group_idstringRequiredYour identifier for the group. Must be unique within your account. Up to 512 characters. Must start with a letter or digit; remaining characters may also include . _ ~ : -

Response

Returns 201 Created.

FieldTypeDescription
group_idstringThe group identifier you supplied.
job_countintegerJobs currently in the group. 0 for a new group.
created_atstringISO 8601 creation timestamp.

Example

curl -X POST https://api.amplifierhealth.com/v2/groups \
-H "X-Account-ID: your-account-id" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"group_id": "daily-checkins"}'

Example response

{
  "group_id": "daily-checkins",
  "job_count": 0,
  "created_at": "2026-05-29T10:00:00Z"
}

Errors

HTTPCondition
409A group with this group_id already exists for your account.
400group_id is empty, too long, or contains unsupported characters.

GET /v2/groups

GET/v2/groups
List the groups belonging to your account, with job counts and observed date range.

Query Parameters

API Parameters
FieldTypeRequiredDescription
pageintegerOptional0-based page number. Default: 0.

Response

FieldTypeDescription
groupsarrayGroup objects, described below.
pageintegerThe page returned.
total_pagesintegerTotal pages available.

Each object in groups:

FieldTypeDescription
group_idstringThe group identifier.
job_countintegerJobs in the group, in any status.
done_job_countintegerJobs that have completed and contribute to group computations.
earliest_recorded_atstring | nullEarliest recording time in the group.
latest_recorded_atstring | nullMost recent recording time in the group.
created_atstringWhen the group was created.
updated_atstringWhen the group last changed — membership edits and recomputes of its cached state both move it.

Example

curl -X GET "https://api.amplifierhealth.com/v2/groups" \
-H "X-Account-ID: your-account-id" \
-H "X-API-Key: your-api-key"

Example response

{
  "groups": [
    {
      "group_id": "daily-checkins",
      "job_count": 12,
      "done_job_count": 10,
      "earliest_recorded_at": "2026-05-01T08:30:00Z",
      "latest_recorded_at": "2026-05-29T08:30:00Z",
      "created_at": "2026-05-01T09:00:00Z",
      "updated_at": "2026-05-29T09:00:00Z"
    }
  ],
  "page": 0,
  "total_pages": 1
}

DELETE /v2/groups/{group_id}

DELETE/v2/groups/{group_id}
Delete a group, its membership records, and its stored longitudinal and aggregate results.

The jobs themselves are kept — they remain available through GET /v2/jobs/{job_id}. Only the group and its computed state are removed.

Path Parameters

ParameterTypeDescription
group_idstringThe group to delete.

Response

FieldTypeDescription
group_idstringThe group that was deleted.
deletedbooleantrue on success.

Example

curl -X DELETE https://api.amplifierhealth.com/v2/groups/daily-checkins \
-H "X-Account-ID: your-account-id" \
-H "X-API-Key: your-api-key"

Example response

{ "group_id": "daily-checkins", "deleted": true }

Errors

HTTPCondition
404No group with this group_id exists for your account.

GET /v2/groups/{group_id}/jobs

GET/v2/groups/{group_id}/jobs
List the jobs in a group, ordered by recording time. Returns job metadata only.

Always synchronous. For the full result of any listed job, call GET /v2/jobs/{job_id}.

Path Parameters

ParameterTypeDescription
group_idstringThe group to list.

Query Parameters

API Parameters
FieldTypeRequiredDescription
pageintegerOptional0-based page number. Default: 0.
fromstringOptionalISO 8601 date. Include jobs recorded at or after this time.
tostringOptionalISO 8601 date. Include jobs recorded at or before this time.
statusstringOptionalFilter by the membership status recorded for the job: queued, running, done, failed, or timed-out. Jobs registered at submission are recorded as queued and move to done on completion, so filter on done to select the jobs that feed group computations.

Response

FieldTypeDescription
group_idstringThe group listed.
total_jobsintegerJobs matching the filters.
jobsarrayJob metadata: job_id, status, recorded_at, created_at, use_case, condition, audio_duration_seconds.
pageintegerThe page returned.
total_pagesintegerTotal pages available.

Example

curl -X GET "https://api.amplifierhealth.com/v2/groups/daily-checkins/jobs?status=done" \
-H "X-Account-ID: your-account-id" \
-H "X-API-Key: your-api-key"

Example response

{
  "group_id": "daily-checkins",
  "total_jobs": 10,
  "jobs": [
    {
      "job_id": "job_aaa",
      "status": "done",
      "recorded_at": "2026-05-01T08:30:00Z",
      "created_at": "2026-05-01T09:14:00Z",
      "use_case": null,
      "condition": null,
      "audio_duration_seconds": 27.4
    },
    { "...": "..." }
  ],
  "page": 0,
  "total_pages": 1
}

Errors

HTTPCondition
400from or to is not a valid ISO 8601 datetime.
404No group with this group_id exists for your account.

POST /v2/groups/{group_id}/jobs

POST/v2/groups/{group_id}/jobs
Add jobs you have already submitted into a group.

Use this to backfill a group from existing jobs. To register a job into a group at submission time instead, use the analyze extensions.

Each job_id is handled independently and reported in exactly one of added, skipped, or not_found — a job id that isn't found leaves the rest of the batch unaffected, and the request still returns 200.

Path Parameters

ParameterTypeDescription
group_idstringThe group to add jobs to.

Request Body

Requests use Content-Type: application/json.

API Parameters
FieldTypeRequiredDescription
job_idsstring[]RequiredJob IDs to add. Between 1 and 100 per request.

Response

FieldTypeDescription
statusstringAlways "accepted".
addedstring[]Job IDs added to the group.
skippedobjectJob ID → reason, for jobs already in the group ("already_in_group").
not_foundstring[]Job IDs that don't exist for your account.
not_yet_donestring[]Added jobs that are still queued or running. They contribute to group computations once they reach done. This is a subset of added.

Example

curl -X POST https://api.amplifierhealth.com/v2/groups/daily-checkins/jobs \
-H "X-Account-ID: your-account-id" \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"job_ids": ["job_ddd", "job_eee", "job_fff", "job_ggg", "job_hhh"]}'

Example response

{
  "status": "accepted",
  "added": ["job_ddd", "job_eee", "job_hhh"],
  "skipped": { "job_fff": "already_in_group" },
  "not_found": ["job_ggg"],
  "not_yet_done": ["job_hhh"]
}

Errors

HTTPCondition
404No group with this group_id exists for your account.

DELETE /v2/groups/{group_id}/jobs/{job_id}

DELETE/v2/groups/{group_id}/jobs/{job_id}
Remove a job from a group. The job itself is kept.

Removing a job clears the group's stored longitudinal and aggregate results, so the next read recomputes them without that job.

Path Parameters

ParameterTypeDescription
group_idstringThe group to remove from.
job_idstringThe job to remove.

Example

curl -X DELETE https://api.amplifierhealth.com/v2/groups/daily-checkins/jobs/job_bbb \
-H "X-Account-ID: your-account-id" \
-H "X-API-Key: your-api-key"

Example response

{ "status": "accepted", "removed": "job_bbb" }

Errors

HTTPCondition
404The group, or this job's membership in it, does not exist.

Analyze extensions

Four endpoints mirror the standard analyze endpoints, adding groups/{group_id} to the path. They register the job into the group at submission — creating the group if it is new — and score the recording on its own, without historical context.

EndpointPath parameterResult shape
POST /v2/models/{model_name}/groups/{group_id}/analyzemodel_nameresult.summary + result.signals[]
POST /v2/signs/{sign_name}/groups/{group_id}/analyzesign_nameresult.signal
POST /v2/use-cases/{use_case_name}/groups/{group_id}/analyzeuse_case_nameresult.summary + result.signals[]
POST /v2/conditions/{condition_name}/groups/{group_id}/analyzecondition_nameresult.signal
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.

Request Body

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

API Parameters
FieldTypeRequiredDescription
audiofileRequiredAudio file (WAV, FLAC, MP3, or M4A). Max 32 MB per file. See the Audio Requirements guide for size and format limits.
recorded_atstringOptionalISO 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.
diarizebooleanOptionalWhether to apply speaker diarization. Default: false.
webhook_urlstringOptionalPer-request webhook URL, overriding the account-level webhook for this job. Must be provided together with webhook_secret_key — include both or omit both.
webhook_secret_keystringOptionalRequired when webhook_url is provided. Signs the webhook payload (HMAC-SHA256) so your server can verify it came from Amplifier.

Response

The standard v2 job object, plus two fields:

FieldTypeDescription
group_idstringThe group this job was registered into.
recorded_atstring | nullThe capture time you supplied, or null.

Example

curl -X POST https://api.amplifierhealth.com/v2/models/apex/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"

Example response

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

Longitudinal analyze

The same four endpoints with /longitudinal appended. These score the new recording against the group's history, adding baseline and deviation fields to each signal. The job is registered into the group as well, so it becomes part of the history for later calls.

EndpointPath parameter
POST /v2/models/{model_name}/groups/{group_id}/analyze/longitudinalmodel_name
POST /v2/signs/{sign_name}/groups/{group_id}/analyze/longitudinalsign_name
POST /v2/use-cases/{use_case_name}/groups/{group_id}/analyze/longitudinaluse_case_name
POST /v2/conditions/{condition_name}/groups/{group_id}/analyze/longitudinalcondition_name
POST/v2/models/{model_name}/groups/{group_id}/analyze/longitudinal
Submit audio and score it against the group's history. Returns a job object immediately; the completed result carries baseline and deviation fields.

Request Body

Identical to the analyze extensions above.

Longitudinal result fields

On model and use-case jobs these appear on each result.signals[] entry; on sign and condition jobs, on the singular result.signal.

FieldTypeDescription
score, level, flaggedUnchanged from every other v2 job: this recording's own reading.
latest_scorenumber | nullThe smoothed current estimate after this recording — precision- and time-weighted across the window. May differ from the raw score.
baseline_scorenumber | nullThe subject's personal baseline, blended toward a population prior while personal history is still accumulating.
deviation_from_baselinenumber | nulllatest_score − baseline_score. Positive means above the subject's own baseline.
anomalyboolean | nulltrue 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_scorenumber | nullDeviation expressed in units of the subject's own variability.
population_znumber | nullThe 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

curl -X POST https://api.amplifierhealth.com/v2/models/apex/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"

Example completed job

Poll GET /v2/jobs/{job_id} until status is done. The job read returns the standard v2 job object — group_id and recorded_at are returned on the submission response above, so keep them alongside the job_id you stored:

{
  "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": { "...": "..." }
  }
}

GET /v2/groups/{group_id}/longitudinal

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.

Path Parameters

ParameterTypeDescription
group_idstringThe group to read.

Query Parameters

API Parameters
FieldTypeRequiredDescription
signalstringOptionalReturn only this signal, matched against signals[].name. Repeat the parameter for several signals.
fromstringOptionalISO 8601 date. Show data points from this time onward. Filters the stored result for display; summary statistics are unchanged.
tostringOptionalISO 8601 date. Show data points up to this time. Filters the stored result for display; summary statistics are unchanged.

Response

FieldTypeDescription
statusstringrunning while computing, done when ready, failed if the computation could not complete.
group_idstringThe group read.
computed_atstringWhen this result was computed.
min_data_points_metbooleanConvenience 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_requiredintegerThe 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_countintegerCompleted jobs in the group.
signalsarrayPer-signal trajectory objects, described below.

Each object in signals:

FieldTypeDescription
namestringSignal 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_pointsarrayUsable readings ordered by recording time: job_id, recorded_at, score, level, flagged, anomaly.
data_points[].anomalyboolean | nullThe 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 analyze extension are scored and do get a stored value; it is only their own returned result that omits the longitudinal fields.
trajectory.directionstringup | down | flat | 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.slopenumber | nullChange in score per day. null when direction is insufficient_data.
trajectory.resolvablebooleanWhether a trend was actually fitted. Gate any trend display on this rather than on data_points_used.
trajectory.data_points_usedintegerTime bins the trend was fitted over, not a raw job count. Populated even when resolvable is false.
baseline_scorenumber | nullThe subject's personal baseline, blended toward a population prior while personal history is still accumulating.
latest_scorenumber | nullThe smoothed current estimate. May differ from the most recent raw score.
change_absolutenumberNet change across the stored series in the window: last reading minus first. The from and to filters affect display only, not this value.
flagged_ratenumberShare of the stored readings that were flagged, 01. Computed over the whole series in the window; the from and to filters do not narrow it.
z_scorenumber | nullLatest deviation in units of the subject's own variability.
population_znumber | nullThe subject's current position relative to the cohort. null when no population reference is available.
statusstringok | establishing_baseline | 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_metbooleanWhether this signal's baseline is established.
baseline_personal_weightnumber | nullHow much of baseline_score comes from this subject rather than the population prior, 01. Useful for deciding when to present the baseline as "your usual".

Example

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"

Example response

{
  "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 computing

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

Errors

HTTPCondition
400from or to is not a valid ISO 8601 datetime.
404No 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 /v2/groups/{group_id}/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

ParameterTypeDescription
group_idstringThe group to summarize.

Response

FieldTypeDescription
statusstringrunning, done, or failed.
group_idstringThe group read.
computed_atstringWhen this result was computed.
job_countintegerCompleted jobs in the history window.
signalsarrayPer-signal statistics, described below.

Each object in signals:

FieldTypeDescription
namestringSignal 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.
distributionobjectShare of readings at each level — none, low, consider, moderate, elevated. Values sum to 1.0.
mean_scorenumberMean score across usable readings.
std_scorenumberStandard deviation across the same readings.
flagged_ratenumberShare of readings flagged for this signal, 01.
job_countintegerCompleted jobs that produced a usable reading for this signal. May be lower than the group's total.

Example

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"

Example response

{
  "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

HTTPCondition
404No group with this group_id exists for your account.

Errors

The analyze endpoints on this page return the same codes as POST /v2/models/{model_name}/analyze — including AUDIO_TOO_SHORT, AUDIO_TOO_LONG, UNSUPPORTED_FORMAT, and AUDIO_POOR_QUALITY.

Group-specific conditions:

HTTPCondition
400group_id in the request body is empty, too long, or contains unsupported characters (POST /v2/groups); or from/to is not a valid ISO 8601 datetime.
404The group, job, or membership does not exist for your account, or a group_id in the path is malformed.
409POST /v2/groups was called with a group_id that already exists.

For the full list and retry guidance, see Error Reference.