Skip to content

Trends around a released session

GET
/api/portal/{client_id}/sessions/{session_id}/trends/
curl --request GET \
--url http://localhost:8000/api/portal/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/sessions/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/trends/ \
--header 'Authorization: Bearer <token>'

The coach’s session trends, over the card’s RELEASED sessions only and without the team’s hidden metrics. ?range= or ?from=&to=, as on the coach’s route; a year by default.

The signed-in person’s grant on the card in the path. Every route answers 404 while client access is switched off on the environment. 404 card_not_found for a card the account never held — another athlete’s, a card of the account’s own team, or an id that names nothing. 403 access_ended (reason: revoked · team_plan · minor) for a card it held and can no longer open, consent_required when the consent texts changed since the person accepted them (consent again with POST /api/portal/{client_id}/consent/). A partner API key gets 403. 429 past the account’s rate (client_portal, 120/min; the PDFs client_portal_pdf, 20/min).

Only what a coach released is visible, curated on the server: the team’s hidden metrics are absent, the coach’s AI text and internal fields are not sent, and media URLs are signed for two hours.

client_id
required
string format: uuid
session_id
required
string format: uuid
from
string format: date

First day, inclusive (YYYY-MM-DD).

range
string
to
string format: date

Last day, inclusive (YYYY-MM-DD).

tz
string

The reader’s IANA zone for the days.

Media typeapplication/json

Shape of GET /api/sessions/{id}/trends/ — across-session trajectories for the session’s visible metrics, in one call (#424).

object
session_id
required
string format: uuid
client_id
required

Null when the session has no client — metrics is then empty (no across-session history without a client).

string format: uuid
nullable
activity
required
  • running - Running
  • walking - Walking
  • jump - Vertical Jump
  • weightlifting - Weightlifting
  • mobility - Mobility Assessment
  • workspace_wellness - Workspace Wellness
  • agility - Agility
  • fencing - Fencing
  • cycling - Cycling
  • tennis - Tennis
  • padel - Padel Tennis
  • bowling - Bowling
  • baseball - Baseball
  • football - Football
  • badminton - Badminton
  • cricket - Cricket
  • basketball - Basketball
  • scuba_diving - Scuba Diving
  • ice_hockey - Ice Hockey
  • american_football - American Football
  • fri - Falls Risk Indicator
  • pickleball - Pickleball
string
Allowed values: running walking jump weightlifting mobility workspace_wellness agility fencing cycling tennis padel bowling baseball football badminton cricket basketball scuba_diving ice_hockey american_football fri pickleball
exercise
required
string
range_days
required
integer
range_start
required
string format: date
range_end
required
string format: date
generated_at
required
string format: date-time
metrics
required
Array<object>

One visible metric’s across-session trajectory.

object
key
required
string
label
required
string
unit
required

Canonical Unit literal (see domain/metrics/base.py).

string
higher_is_better
required

Drives the delta tone — improvement, not raw direction. Null for a directionless metric.

boolean
nullable
target_min
required

Lower green bound, resolved for THIS athlete (domain.target_bands, #1317), mirroring the per-client trend. Null when no curated zone applies.

number format: double
nullable
target_max
required
number format: double
nullable
baseline
required

First recorded value in the window — the progress reference the compact sparkline + Dynamics chart both draw.

number format: double
nullable
personal_best
required
number format: double
nullable
series
required
Array<object>
object
session_id
required
string format: uuid
created_at
required
string format: date-time
value
required

Metric value at this session. Null when the analyzer didn’t emit it (e.g. front-only metric on a side-view session).

number format: double
nullable
score
required

Composite score for the session, when present.

number format: double
nullable
target_min
required

Lower green bound, resolved for THIS athlete (domain.target_bands, #1317) — the same band the session card, the PDF and the Solutions report show, not the MetricSpec population default. Null when no curated zone applies.

number format: double
nullable
target_max
required
number format: double
nullable
in_zone
required

True / false / null. Null when value or zone is missing.

boolean
nullable
delta_vs_prev
required

This value minus the previous recorded (non-null) value in the window — null for the first reading or a null value. The per-session Δ. #609 Phase 3 (shared with #424).

number format: double
nullable
is_personal_best
required

True when this value equals the window’s best (by higher_is_better) — drives the personal-best marker. #609 Phase 3.

boolean
comparable
required

False when the session was analysed under an earlier definition of its exercise’s metrics (domain/metric_versions.py, #2297). Drawn, but left out of delta_vs_prev, the baseline and the personal best — its number measured something else. Re-analysing the session makes it comparable.

boolean
change_note

On a non-comparable point: the key of what changed in the exercise’s current definition (clientTrend.definitionChange.<key> in the web locales), so the chart can say WHY the session is not compared. Null when comparable or when the change has no note (#1818).

string
nullable

Examples

ExampleTrends
{
"session_id": "5e2b9c41-7d3a-4f80-9b6e-1c0d2e3f4a51",
"client_id": "a1f1c0a2-0000-4000-8000-0000000000aa",
"activity": "running",
"exercise": "Treadmill",
"range_days": 365,
"range_start": "2025-11-03",
"range_end": "2026-11-02",
"generated_at": "2026-11-02T10:20:00Z",
"metrics": []
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string

Example generated

{
"detail": "example"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string

Example generated

{
"detail": "example"
}
Media typeapplication/json
object
detail
required

access_ended, consent_required, or DRF’s message for a partner API key.

string
reason

With access_ended: why the card no longer opens — revoked, team_plan or minor.

string
client_name
string
team_name
string
consent_version

With consent_required: the version to accept.

string
kinds

With consent_required: the texts to show.

Array<string>

Examples

403 — access ended

The card no longer opens; the page says why and names nothing else of it.

{
"detail": "access_ended",
"reason": "revoked",
"client_name": "Sofia Lebedeva",
"team_name": "North Track Academy"
}
Media typeapplication/json
object
detail
required

Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).

string

Examples

404 — not a card of this account

{
"detail": "card_not_found"
}
Media typeapplication/json
object
detail
required

429 — throttled: this account’s (or address’s) rate for the route is used up; try again after wait seconds, which Retry-After repeats. A route with a limit of its own answers its own code instead, documented there.

string
wait

With throttled: whole seconds until the next request can pass.

integer
nullable

Example generated

{
"detail": "example",
"wait": 1
}